diff --git a/README.md b/README.md index edf5b1e..9fcd04e 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,15 @@ # Hermes -Hermes is the Go port the great [mailgen](https://github.com/eladnava/mailgen) engine for Node.js. -It's a package that generates clean, responsive HTML e-mails for sending transactional mail (welcome email, email validity checking, reset password and so on). +Hermes is the Go port of the great [mailgen](https://github.com/eladnava/mailgen) engine for Node.js. Check their work, it's awesome ! +It's a package that generates clean, responsive HTML e-mails for sending transactional e-mails (welcome e-mail, reset password e-mails, receipt e-mails and so on). # Demo -TODO +![Welcome](screens/default/welcome.png) + +![Reset](screens/default/reset.png) + +![Receipt](screens/default/receipt.png) # Usage @@ -15,7 +19,264 @@ First install the package: go get -u github.com/matcornic/hermes ``` -TODO +Then, start using the package by importing and configuring it: + +```go +// Configure hermes by setting a theme and your product info +h := hermes.Hermes{ + // Optional Theme + // Theme: new(Default) + Product: hermes.Product{ + // Appears in header & footer of e-mails + Name: "Hermes", + Link: "https://example-hermes.com/", + //Option product logo + //Logo: "http://www.duchess-france.org/wp-content/uploads/2016/01/gopher.png", + }, +} +``` + +Next, generate an e-mail using the following code: + +```go +email := hermes.Email{ + Body: hermes.Body{ + Name: "Jon Snow", + Intros: []string{ + "Welcome to Hermes! We're very excited to have you on board.", + }, + Actions: []hermes.Action{ + { + Instructions: "To get started with Hermes, please click here:", + Button: hermes.Button{ + Color: "#22BC66", // Optional action button color + Text: "Confirm your account", + Link: "https://hermes-example.com/confirm?token=d9729feb74992cc3482b350163a1a010", + }, + }, + }, + Outros: []string{ + "Need help, or have questions? Just reply to this email, we'd love to help.", + }, + }, + } + +// Generate an HTML email with the provided contents(for modern clients) +emailBody, err := h.GenerateHTML(email) +if err != nil { + panic(err) // Tip: Handle error with something else than a panic ;) +} + +// Generate the plaintext version of the e-mail (for clients that do not support xHTML) +emailText, err := h.GeneratePlainText(email) +if err != nil { + panic(err) // Tip: Handle error with something else than a panic ;) +} + +// Optionnaly, preview the generated HTML e-mail by writing it to a local file +err = ioutil.WriteFile("preview.html", []byte(emailBody), 0644) +if err != nil { + panic(err) // Tip: Handle error with something else than a panic ;) +} +``` + +This code would output the following HTML template: + +![Demo](screens/demo.png) + +> Theme templates will be incorporated in your application binary. If you want to use external templates (for configuration), use your own theme by implementing `hermes.Theme` interface with code searching for your files. + +## More Examples + +* [Welcome](examples/default/welcome.go) +* [Receipt](examples/default/receipt.go) +* [Password Reset](examples/default/reset.go) + +To run the examples, go to `examples//`, then run `go run *.go`. HTML and Plaintext example should be created in the folder. + +## Plaintext E-mails + +To generate a [plaintext version of the e-mail](https://litmus.com/blog/best-practices-for-plain-text-emails-a-look-at-why-theyre-important), simply call `GeneratePlainText` function: + +```go +// Generate plaintext email using hermes +emailText, err := h.GeneratePlainText(email) +if err != nil { + panic(err) // Tip: Handle error with something else than a panic ;) +} +``` + +## Supported Themes + +The following open-source themes are bundled with this package: + +* `default` by [Postmark Transactional Email Templates](https://github.com/wildbit/postmark-templates) + + + +## RTL Support + +To change the default text direction (left-to-right), simply override it as follows: + +```go +// Configure hermes by setting a theme and your product info +h := hermes.Hermes { + // Custom text direction + TextDirection: hermes.TDRightToLeft, +} +``` + +## Language Customizations + +To customize the e-mail greeting (Hi) or signature (Yours truly), supply custom strings within the e-mail `Body`: + +```go +email := hermes.Email{ + Body: hermes.Body{ + Greeting: "Dear", + Signature: "Sincerly", + }, + } +``` + +To use a custom title string rather than a greeting/name introduction, provide it instead of `Name`: + +```js +var email = { + body: { + // Title will override `name` + title: 'Welcome to Mailgen!' + } +}; +``` + +```go +email := hermes.Email{ + Body: hermes.Body{ + // Title will override `Name` + Title: "Welcome to Mailgen", + }, + } +``` + +To customize the `Copyright`, override it when initializing `Hermes` within your `Product` as follows: + +```js +// Configure mailgen +var mailGenerator = new Mailgen({ + theme: 'salted', + product: { + name: 'Mailgen', + link: 'https://mailgen.js/', + // Custom copyright notice + copyright: 'Copyright © 2016 Mailgen. All rights reserved.', + } +}); +``` + +```go +// Configure hermes by setting a theme and your product info +h := hermes.Hermes{ + // Optional Theme + // Theme: new(Default) + Product: hermes.Product{ + // Appears in header & footer of e-mails + Name: "Hermes", + Link: "https://example-hermes.com/", + // Custom copyright notice + Copyright: "Copyright © 2017 Dharma Initiative. All rights reserved." + }, +} +``` + +## Elements + +Mailgen supports injecting custom elements such as dictionaries, tables and action buttons into e-mails. + +### Action + +To inject an action button in to the e-mail, supply the `Actions` object as follows: + + +```go +email := hermes.Email{ + Body: hermes.Body{ + Actions: []hermes.Action{ + { + Instructions: "To get started with Hermes, please click here:", + Button: hermes.Button{ + Color: "#22BC66", // Optional action button color + Text: "Confirm your account", + Link: "https://hermes-example.com/confirm?token=d9729feb74992cc3482b350163a1a010", + }, + }, + }, + }, + } +``` + +To inject multiple action buttons in to the e-mail, supply another struct in Actions slice `Action`. + +### Table + +To inject a table into the e-mail, supply the `Table` object as follows: + +```go +email := hermes.Email{ + Body: hermes.Body{ + Table: hermes.Table{ + Data: [][]hermes.Entry{ + // List of rows + { + // Key is the column name, Value is the cell value + // First object defines what columns will be displayed + {Key: "Item", Value: "Node.js"}, + {Key: "Description", Value: "Event-driven I/O server-side JavaScript environment based on V8"}, + {Key: "Price", Value: "$10.99"}, + }, + { + {Key: "Item", Value: "Mailgen"}, + {Key: "Description", Value: "Programmatically create beautiful e-mails using plain old JavaScript."}, + {Key: "Price", Value: "$1.99"}, + }, + }, + Columns: hermes.Columns{ + // Custom style for each rows + CustomWidth: map[string]string{ + "Item": "20%", + "Price": "15%", + }, + CustomAlignement: map[string]string{ + "Price": "right", + }, + }, + }, + }, +} +``` + +> Note: Tables are currently not supported in plaintext versions of e-mails. + +### Dictionary + + To inject key-value pairs of data into the e-mail, supply the `dictionary` object as follows: + + ```go +email := hermes.Email{ + Body: hermes.Body{ + Dictionary: []hermes.Entry{ + {Key: "Date", Value: "20 November 1887"}, + {Key: "Address", Value: "221B Baker Street, London"}, + }, + }, + } +``` + +## Troubleshooting + +1. After sending multiple e-mails to the same Gmail / Inbox address, they become grouped and truncated since they contain similar text, breaking the responsive e-mail layout. + +> Simply sending the `X-Entity-Ref-ID` header with your e-mails will prevent grouping / truncation. ## Contributing diff --git a/default.go b/default.go index d112606..bef797d 100644 --- a/default.go +++ b/default.go @@ -1,14 +1,17 @@ package hermes -type DefaultTheme struct { +// Default is the theme by default +type Default struct { name string } -func (dt *DefaultTheme) Name() string { +// Name returns the name of the default theme +func (dt *Default) Name() string { return "default" } -func (dt *DefaultTheme) HtmlTemplate() string { +// HTMLTemplate returns a Golang template that will generate an HTML email. +func (dt *Default) HTMLTemplate() string { return ` @@ -233,14 +236,17 @@ func (dt *DefaultTheme) HtmlTemplate() string { -

{{.Email.Body.Greeting}} {{.Email.Body.Name}},

- {{ with .Email.Body.Intros }} +

{{if .Email.Body.Title }}{{ .Email.Body.Title }}{{ else }}{{ .Email.Body.Greeting }} {{ .Email.Body.Name }},{{ end }}

+ {{ with .Email.Body.Intros }} + {{ if gt (len .) 0 }} {{ range $line := . }}

{{ $line }}

{{ end }} {{ end }} + {{ end }} {{ with .Email.Body.Dictionary }} + {{ if gt (len .) 0 }}
{{ range $entry := . }}
{{ $entry.Key }}:
@@ -248,8 +254,62 @@ func (dt *DefaultTheme) HtmlTemplate() string { {{ end }}
{{ end }} + {{ end }} - {{ with .Email.Body.Actions }} + + {{ with .Email.Body.Table }} + {{ $data := .Data }} + {{ $columns := .Columns }} + {{ if gt (len $data) 0 }} + + + + +
+ + + {{ $col := index $data 0 }} + {{ range $entry := $col }} + + {{ end }} + + {{ range $row := $data }} + + {{ range $cell := $row }} + + {{ end }} + + {{ end }} +
+

{{ $entry.Key }}

+
+ {{ $cell.Value }} +
+
+ {{ end }} + {{ end }} + + + {{ with .Email.Body.Actions }} + {{ if gt (len .) 0 }} {{ range $action := . }}

{{ $action.Instructions }}

@@ -263,12 +323,15 @@ func (dt *DefaultTheme) HtmlTemplate() string {
{{ end }} {{ end }} + {{ end }} {{ with .Email.Body.Outros }} + {{ if gt (len .) 0 }} {{ range $line := . }}

{{ $line }}

{{ end }} {{ end }} + {{ end }}

{{.Email.Body.Signature}}, @@ -316,51 +379,21 @@ func (dt *DefaultTheme) HtmlTemplate() string { ` } -func (dt *DefaultTheme) PlainTextTemplate() string { - return ` -{{.Email.Body.Greeting}} {{.Email.Body.Name}}, -
-
- -{{ with .Email.Body.Intros }} -{{ range $line := . }} -{{ $line }}
-{{ end }} -
-{{ end }} - -{{ with .Email.Body.Dictionary }} -{{ range $entry := . }} -{{ $entry.Key }}: {{ $entry.Value }}
-{{ end }} -
-{{ end }} - -{{ with .Email.Body.Actions }} -{{ range $action := . }} +// PlainTextTemplate returns a Golang template that will generate an plain text email. +func (dt *Default) PlainTextTemplate() string { + return `{{.Email.Body.Greeting}} {{.Email.Body.Name}}, +{{ with .Email.Body.Intros }}{{ range $line := . }}{{ $line }}{{ end }}{{ end }} +{{ with .Email.Body.Dictionary }}{{ range $entry := . }} +{{ $entry.Key }}: {{ $entry.Value }}{{ end }}{{ end }} +{{ with .Email.Body.Actions }} {{ range $action := . }} {{ $action.Instructions }} -
-{{ $action.Button.Link }} -
- -
-{{ end }} -{{ end }} - -{{ with .Email.Body.Outros }} -{{ range $line := . }} -{{ $line }} -
-{{ end }} -
-{{ end }} +{{ $action.Button.Link }}{{ end }}{{ end }} +{{ with .Email.Body.Outros }} {{ range $line := . }} +{{ $line }}{{ end }}{{ end }} {{.Email.Body.Signature}}, -
{{.Hermes.Product.Name}} -
-
{{.Hermes.Product.Copyright}} ` } diff --git a/examples/.DS_Store b/examples/.DS_Store index 0f32880..2909d1a 100644 Binary files a/examples/.DS_Store and b/examples/.DS_Store differ diff --git a/examples/default/default.html b/examples/default/default.html deleted file mode 100644 index acd3f07..0000000 --- a/examples/default/default.html +++ /dev/null @@ -1,49 +0,0 @@ - -Hi Jon Snow, -
-
- - - -Welcome to Hermes! We're very excited to have you on board.
- -
- - - - -Firstname: Jon
- -Lastname: Snow
- -Birthday: 01/01/283
- -
- - - - -To get started with Hermes, please click here: -
-https://hermes-example.com/confirm?token=d9729feb74992cc3482b350163a1a010 -
- -
- - - - - -Need help, or have questions? Just reply to this email, we'd love to help. -
- -
- - -Yours truly, -
-Hermes - -
-
-Copyright © 2017 Hermes. All rights reserved. diff --git a/examples/default/default.receipt.html b/examples/default/default.receipt.html new file mode 100644 index 0000000..074ab19 --- /dev/null +++ b/examples/default/default.receipt.html @@ -0,0 +1,422 @@ + + + + + + + + + + + + + + + + diff --git a/examples/default/default.receipt.txt b/examples/default/default.receipt.txt new file mode 100644 index 0000000..48a6223 --- /dev/null +++ b/examples/default/default.receipt.txt @@ -0,0 +1,12 @@ +Hi Jon Snow, +Your order has been processed successfully. + + +You can check the status of your order and more in your dashboard: +https://hermes-example.com/dashboard + + +Yours truly, +Hermes + +Copyright © 2017 Hermes. All rights reserved. diff --git a/examples/default/default.reset.html b/examples/default/default.reset.html new file mode 100644 index 0000000..26b7f74 --- /dev/null +++ b/examples/default/default.reset.html @@ -0,0 +1,309 @@ + + + + + + + + + + + + + + + + diff --git a/examples/default/default.reset.txt b/examples/default/default.reset.txt new file mode 100644 index 0000000..f5d1d70 --- /dev/null +++ b/examples/default/default.reset.txt @@ -0,0 +1,13 @@ +Hi Jon Snow, +You have received this email because a password reset request for Hermes account was received. + + +Click the button below to reset your password: +https://hermes-example.com/reset-password?token=d9729feb74992cc3482b350163a1a010 + +If you did not request a password reset, no further action is required on your part. + +Thanks, +Hermes + +Copyright © 2017 Hermes. All rights reserved. diff --git a/examples/default/default.plaintext.html b/examples/default/default.welcome.html similarity index 94% rename from examples/default/default.plaintext.html rename to examples/default/default.welcome.html index e9f6540..9e6984b 100644 --- a/examples/default/default.plaintext.html +++ b/examples/default/default.welcome.html @@ -206,9 +206,9 @@ - @@ -221,28 +221,26 @@

Hi Jon Snow,

- + +

Welcome to Hermes! We're very excited to have you on board.

- - -
- -
Firstname:
-
Jon
- -
Lastname:
-
Snow
- -
Birthday:
-
01/01/283
- -
- + + + + + + + + + + + +

To get started with Hermes, please click here:

@@ -256,12 +254,15 @@
+ +

Need help, or have questions? Just reply to this email, we'd love to help.

+

Yours truly, diff --git a/examples/default/default.welcome.txt b/examples/default/default.welcome.txt new file mode 100644 index 0000000..1e33cb1 --- /dev/null +++ b/examples/default/default.welcome.txt @@ -0,0 +1,13 @@ +Hi Jon Snow, +Welcome to Hermes! We're very excited to have you on board. + + +To get started with Hermes, please click here: +https://hermes-example.com/confirm?token=d9729feb74992cc3482b350163a1a010 + +Need help, or have questions? Just reply to this email, we'd love to help. + +Yours truly, +Hermes + +Copyright © 2017 Hermes. All rights reserved. diff --git a/examples/default/main.go b/examples/default/main.go index f407d5a..71bceb5 100644 --- a/examples/default/main.go +++ b/examples/default/main.go @@ -1,66 +1,55 @@ package main import ( - "bytes" + "fmt" "github.com/matcornic/hermes" "io/ioutil" ) +type Example interface { + Email() hermes.Email + Name() string +} + func main() { h := hermes.Hermes{ Product: hermes.Product{ Name: "Hermes", - Link: "http://hermes.com", + Link: "https://example-hermes.com/", + Logo: "http://www.duchess-france.org/wp-content/uploads/2016/01/gopher.png", }, } - email := hermes.Email{ - Body: hermes.Body{ - Name: "Jon Snow", - Intros: []string{ - "Welcome to Hermes! We're very excited to have you on board.", - }, - Dictionary: []hermes.Entry{ - {Key: "Firstname", Value: "Jon"}, - {Key: "Lastname", Value: "Snow"}, - {Key: "Birthday", Value: "01/01/283"}, - }, - Actions: []hermes.Action{ - { - Instructions: "To get started with Hermes, please click here:", - Button: hermes.Button{ - Color: "#22BC66", - Text: "Confirm your account", - Link: "https://hermes-example.com/confirm?token=d9729feb74992cc3482b350163a1a010", - }, - }, - }, - Outros: []string{ - "Need help, or have questions? Just reply to this email, we'd love to help.", - }, - }, + examples := []Example{ + new(Welcome), + new(Reset), + new(Receipt), } - // Generate the HTML template - stream, err := h.GenerateHTML(email) - if err != nil { - panic(err) + for _, e := range examples { + generateEmails(h, e.Email(), e.Name()) } - buf := new(bytes.Buffer) - _, err = buf.ReadFrom(stream) - if err != nil { - panic(err) - } - err = ioutil.WriteFile("default.plaintext.html", buf.Bytes(), 0644) - stream, err = h.GeneratePlainText(email) - if err != nil { - panic(err) - } - buf = new(bytes.Buffer) - _, err = buf.ReadFrom(stream) - if err != nil { - panic(err) - } - err = ioutil.WriteFile("default.html", buf.Bytes(), 0644) +} + +func generateEmails(h hermes.Hermes, email hermes.Email, example string) { + // Generate the HTML template and save it + res, err := h.GenerateHTML(email) + if err != nil { + panic(err) + } + err = ioutil.WriteFile(fmt.Sprintf("%v.%v.html", h.Theme.Name(), example), []byte(res), 0644) + if err != nil { + panic(err) + } + + // Generate the plaintext template and save it + res, err = h.GeneratePlainText(email) + if err != nil { + panic(err) + } + err = ioutil.WriteFile(fmt.Sprintf("%v.%v.txt", h.Theme.Name(), example), []byte(res), 0644) + if err != nil { + panic(err) + } } diff --git a/examples/default/receipt.go b/examples/default/receipt.go new file mode 100644 index 0000000..2dbf347 --- /dev/null +++ b/examples/default/receipt.go @@ -0,0 +1,55 @@ +package main + +import ( + "github.com/matcornic/hermes" +) + +type Receipt struct { +} + +func (r *Receipt) Name() string { + return "receipt" +} + +func (r *Receipt) Email() hermes.Email { + return hermes.Email{ + Body: hermes.Body{ + Name: "Jon Snow", + Intros: []string{ + "Your order has been processed successfully.", + }, + Table: hermes.Table{ + Data: [][]hermes.Entry{ + { + {Key: "Item", Value: "Node.js"}, + {Key: "Description", Value: "Event-driven I/O server-side JavaScript environment based on V8"}, + {Key: "Price", Value: "$10.99"}, + }, + { + {Key: "Item", Value: "Mailgen"}, + {Key: "Description", Value: "Programmatically create beautiful e-mails using plain old JavaScript."}, + {Key: "Price", Value: "$1.99"}, + }, + }, + Columns: hermes.Columns{ + CustomWidth: map[string]string{ + "Item": "20%", + "Price": "15%", + }, + CustomAlignement: map[string]string{ + "Price": "right", + }, + }, + }, + Actions: []hermes.Action{ + { + Instructions: "You can check the status of your order and more in your dashboard:", + Button: hermes.Button{ + Text: "Go to Dashboard", + Link: "https://hermes-example.com/dashboard", + }, + }, + }, + }, + } +} diff --git a/examples/default/reset.go b/examples/default/reset.go new file mode 100644 index 0000000..7f06cc8 --- /dev/null +++ b/examples/default/reset.go @@ -0,0 +1,37 @@ +package main + +import ( + "github.com/matcornic/hermes" +) + +type Reset struct { +} + +func (r *Reset) Name() string { + return "reset" +} + +func (r *Reset) Email() hermes.Email { + return hermes.Email{ + Body: hermes.Body{ + Name: "Jon Snow", + Intros: []string{ + "You have received this email because a password reset request for Hermes account was received.", + }, + Actions: []hermes.Action{ + { + Instructions: "Click the button below to reset your password:", + Button: hermes.Button{ + Color: "#DC4D2F", + Text: "Reset your password", + Link: "https://hermes-example.com/reset-password?token=d9729feb74992cc3482b350163a1a010", + }, + }, + }, + Outros: []string{ + "If you did not request a password reset, no further action is required on your part.", + }, + Signature: "Thanks", + }, + } +} diff --git a/examples/default/welcome.go b/examples/default/welcome.go new file mode 100644 index 0000000..41b2ec7 --- /dev/null +++ b/examples/default/welcome.go @@ -0,0 +1,41 @@ +package main + +import ( + "github.com/matcornic/hermes" +) + +type Welcome struct { +} + +func (w *Welcome) Name() string { + return "welcome" +} + +func (w *Welcome) Email() hermes.Email { + return hermes.Email{ + Body: hermes.Body{ + Name: "Jon Snow", + Intros: []string{ + "Welcome to Hermes! We're very excited to have you on board.", + }, + Dictionary: []hermes.Entry{ + {Key: "Firstname", Value: "Jon"}, + {Key: "Lastname", Value: "Snow"}, + {Key: "Birthday", Value: "01/01/283"}, + }, + Actions: []hermes.Action{ + { + Instructions: "To get started with Hermes, please click here:", + Button: hermes.Button{ + Color: "#22BC66", + Text: "Confirm your account", + Link: "https://hermes-example.com/confirm?token=d9729feb74992cc3482b350163a1a010", + }, + }, + }, + Outros: []string{ + "Need help, or have questions? Just reply to this email, we'd love to help.", + }, + }, + } +} diff --git a/hermes.go b/hermes.go index 2d91381..1fc74d8 100644 --- a/hermes.go +++ b/hermes.go @@ -5,90 +5,127 @@ import ( "github.com/Masterminds/sprig" "github.com/imdario/mergo" "html/template" - "io" ) -type TextDirection string - -const TDLeftToRight TextDirection = "ltr" -const TDRightToLeft TextDirection = "rtl" - +// Hermes is an instance of the hermes email generator type Hermes struct { Theme Theme - TextDirection TextDirection // rtl (right to left) or ltr (left to right) + TextDirection TextDirection Product Product } +// Theme is an interface to implement when creating a new theme +type Theme interface { + Name() string // The name of the theme + HTMLTemplate() string // The golang template for HTML emails + PlainTextTemplate() string // The golang templte for plain text emails (can be basic HTML) +} + +// TextDirection of the text in HTML email@ +type TextDirection string + +// TDLeftToRight is the text direction from left to right (default) +const TDLeftToRight TextDirection = "ltr" + +// TDRightToLeft is the text direction from right to left +const TDRightToLeft TextDirection = "rtl" + +// Product represents your company product (brand) +// Appears in header & footer of e-mails type Product struct { - // Appears in header & footer of e-mails Name string Link string // e.g. https://matcornic.github.io Logo string // e.g. https://matcornic.github.io/img/logo.png Copyright string // Copyright © 2017 Hermes. All rights reserved. } +// Email is the email containing a body type Email struct { Body Body } +// Body is the body of the email, containing all interesting data type Body struct { - Name string - Intros []string - Dictionary []Entry - Actions []Action - Outros []string - Greeting string - Signature string - Title string + Name string // The name of the contacted person + Intros []string // Intro sentences, first displayed in the email + Dictionary []Entry // A list of key+value (usefull for displaying parameters/settings/personal info) + Table Table // Table is an table where you can put data (pricing grid, a bill, and so on) + Actions []Action // Actions are a list of actions that the user will be able to execute via a button click + Outros []string // Outro sentences, last displayed in the email + Greeting string // Greeting for the contacted person (default to 'Hi') + Signature string // Signature for the contacted person (default to 'Yours truly') + Title string // Title replaces the greeting+name when set } +// Entry is a simple entry of a map +// Allows using a slice of entries instead of a map +// Because Golang maps are not ordered type Entry struct { Key string Value string } +// Table is an table where you can put data (pricing grid, a bill, and so on) +type Table struct { + Data [][]Entry // Contains data + Columns Columns // Contains meta-data for display purpose (width, alignement) +} + +// Columns contains meta-data for the different columns +type Columns struct { + CustomWidth map[string]string + CustomAlignement map[string]string +} + +// Action is an action the user can do on the email (click on a button) type Action struct { Instructions string Button Button } +// Button defines an action to launch type Button struct { Color string Text string Link string } +// Template is the struct given to Golang templating +// Root object in a template is this struct type Template struct { Hermes Hermes Email Email } -type Theme interface { - Name() string - HtmlTemplate() string - PlainTextTemplate() string -} - func setDefaultEmailValues(e *Email) error { + // Default values of an email defaultEmail := Email{ Body: Body{ - Signature: "Yours truly", - Greeting: "Hi", + Intros: []string{}, + Dictionary: []Entry{}, + Outros: []string{}, + Signature: "Yours truly", + Greeting: "Hi", }, } + // Merge the given email with default one + // Default one overrides all zero values return mergo.Merge(e, defaultEmail) } +// default values of the engine func setDefaultHermesValues(h *Hermes) error { defaultTextDirection := TDLeftToRight defaultHermes := Hermes{ - Theme: new(DefaultTheme), + Theme: new(Default), TextDirection: defaultTextDirection, Product: Product{ Name: "Hermes", Copyright: "Copyright © 2017 Hermes. All rights reserved.", }, } + // Merge the given hermes engine coniguration with default one + // Default one overrides all zero values err := mergo.Merge(h, defaultHermes) if err != nil { return err @@ -99,30 +136,41 @@ func setDefaultHermesValues(h *Hermes) error { return nil } -func (h *Hermes) GenerateHTML(email Email) (io.Reader, error) { - return h.generateTemplate(email, func() string { - return h.Theme.HtmlTemplate() - }) -} - -func (h *Hermes) GeneratePlainText(email Email) (io.Reader, error) { - return h.generateTemplate(email, func() string { - return h.Theme.PlainTextTemplate() - }) -} - -func (h *Hermes) generateTemplate(email Email, tplt func() string) (io.Reader, error) { +// GenerateHTML generates the email body from data to an HTML Reader +// This is for modern email clients +func (h *Hermes) GenerateHTML(email Email) (string, error) { err := setDefaultHermesValues(h) if err != nil { - return nil, err + return "", err } - setDefaultEmailValues(&email) + return h.generateTemplate(email, h.Theme.HTMLTemplate()) +} - t, err := template.New("hermes").Funcs(sprig.FuncMap()).Parse(tplt()) +// GeneratePlainText generates the email body from data +// This is for old email clients +// Note : this mode is not able to print Tables +func (h *Hermes) GeneratePlainText(email Email) (string, error) { + err := setDefaultHermesValues(h) if err != nil { - return nil, err + return "", err + } + return h.generateTemplate(email, h.Theme.PlainTextTemplate()) +} + +func (h *Hermes) generateTemplate(email Email, tplt string) (string, error) { + + err := setDefaultEmailValues(&email) + if err != nil { + return "", err + } + + // Generate the email from Golang template + // Allow usage of simple function from sprig : https://github.com/Masterminds/sprig + t, err := template.New("hermes").Funcs(sprig.FuncMap()).Parse(tplt) + if err != nil { + return "", err } var b bytes.Buffer t.Execute(&b, Template{*h, email}) - return &b, nil + return b.String(), nil } diff --git a/hermes_test.go b/hermes_test.go index a0b02ae..30284e7 100644 --- a/hermes_test.go +++ b/hermes_test.go @@ -5,7 +5,7 @@ import ( "testing" ) -func TestHermes(t *testing.T) { +func TestHermes_ok(t *testing.T) { h := Hermes{ Product: Product{ @@ -44,4 +44,48 @@ func TestHermes(t *testing.T) { r, err := h.GenerateHTML(email) t.Log(r) assert.Nil(t, err) + assert.NotEmpty(t, r) + + r, err = h.GeneratePlainText(email) + t.Log(r) + assert.Nil(t, err) + assert.NotEmpty(t, r) + + assert.Equal(t, h.Theme.Name(), "default") +} + +func TestHermes_defaultTextDirection(t *testing.T) { + h := Hermes{ + Product: Product{ + Name: "Hermes", + Link: "http://hermes.com", + }, + TextDirection: "not-existing", // Wrong text-direction + } + + email := Email{ + Body{ + Name: "Jon Snow", + Intros: []string{ + "Welcome to Hermes! We're very excited to have you on board.", + }, + Actions: []Action{ + { + Instructions: "To get started with Hermes, please click here:", + Button: Button{ + Color: "#22BC66", + Text: "Confirm your account", + Link: "https://hermes-example.com/confirm?token=d9729feb74992cc3482b350163a1a010", + }, + }, + }, + Outros: []string{ + "Need help, or have questions? Just reply to this email, we'd love to help.", + }, + }, + } + + _, err := h.GenerateHTML(email) + assert.Nil(t, err) + assert.Equal(t, h.TextDirection, TDLeftToRight) } diff --git a/screens/default/receipt.png b/screens/default/receipt.png new file mode 100644 index 0000000..ffe2899 Binary files /dev/null and b/screens/default/receipt.png differ diff --git a/screens/default/reset.png b/screens/default/reset.png new file mode 100644 index 0000000..654acd4 Binary files /dev/null and b/screens/default/reset.png differ diff --git a/screens/default/welcome.png b/screens/default/welcome.png new file mode 100644 index 0000000..4273e87 Binary files /dev/null and b/screens/default/welcome.png differ diff --git a/screens/demo.png b/screens/demo.png new file mode 100644 index 0000000..57e4657 Binary files /dev/null and b/screens/demo.png differ