Hermes is the Go port of the great 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-mails, reset password e-mails, receipt e-mails and so on), and associated plain text fallback.
First install the package:
go get -u github.com/go-hermes/hermes/v2
Starting from release v2.0.0, Hermes uses Go modules. The latest version of Hermes requires at least Go 1.22 with gomodules enabled. You can still use an Hermes release compatible with prior Go versions by using v1.2.0 release
Then, start using the package by importing and configuring it:
// 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/",
// Optional product logo
Logo: "http://www.duchess-france.org/wp-content/uploads/2016/01/gopher.png",
},
}
Next, generate an e-mail using the following code:
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 ;)
}
// Optionally, 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:
And the following plain text:
------------
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 - https://example-hermes.com/
Copyright © 2017 Hermes. All rights reserved.
Theme templates will be embedded 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.
To run the examples, go to examples
folder, then run go run -a *.go
. HTML and Plaintext example should be created in given theme folders.
Optionaly you can set the following variables to send automatically the emails to one your mailbox. Nice for testing template in real email clients.
HERMES_SEND_EMAILS=true
HERMES_SMTP_SERVER=<smtp_server>
: for Gmail it'ssmtp.gmail.com
HERMES_SMTP_PORT=<smtp_port>
: for Gmail it's465
HERMES_SENDER_EMAIL=<your_sender_email>
HERMES_SENDER_IDENTITY=<the sender name>
HERMES_SMTP_USER=<smtp user>
: usually the same thanHERMES_SENDER_EMAIL
HERMES_TO=<recipients emails>
: split by commas like[email protected],[email protected]
The program will ask for your SMTP password. If needed, you can set it with HERMES_SMTP_PASSWORD
variable (but be careful where you put this information !)
To generate a plaintext version of the e-mail, simply call GeneratePlainText
function:
// Generate plaintext email using hermes
emailText, err := h.GeneratePlainText(email)
if err != nil {
panic(err) // Tip: Handle error with something else than a panic ;)
}
The following open-source themes are bundled with this package:
default
by Postmark Transactional Email Templates
flat
, slightly modified from Postmark Transactional Email Templates
If you want to use your own themes instead of the ones packaged by the Hermes project, you will need to define your own theme in your project. This will need to satisfy the hermes.Theme
interface.
The following methods will need to be defined:
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)
}
Example
package hermes_custom_theme
import (
"embed"
"fmt"
)
// assuming you have a templates folder as a sibling to this file
var (
//go:embed templates
staticFS embed.FS
)
const (
htmlEmail = "templates/%s.tpl.html"
plainTextEmail = "templates/%s.tpl.txt"
)
func getTemplate(name string) string {
htmlBytes, err := staticFS.ReadFile(name)
if err != nil {
logrus.Fatal(err)
}
return string(htmlBytes)
}
// CustomThemeOne is a user-defined custom theme
type CustomThemeOne struct{}
// Name returns the name of the custom theme
func (ct CustomThemeOne) Name() string {
return "custom_theme_one"
}
// HTMLTemplate returns a Golang template that will generate an HTML email.
func (ct CustomThemeOne) HTMLTemplate() string {
return getTemplate(fmt.Sprintf(htmlEmail, ct.Name()))
}
// PlainTextTemplate returns a Golang template that will generate an plain text email.
func (ct CustomThemeOne) PlainTextTemplate() string {
return getTemplate(fmt.Sprintf(plainTextEmail, ct.Name()))
}
// CustomThemeTwo is a user-defined custom theme
type CustomThemeTwo struct{}
// Name returns the name of the custom theme
func (ct CustomThemeTwo) Name() string {
return "custom_theme_two"
}
// HTMLTemplate returns a Golang template that will generate an HTML email.
func (ct CustomThemeTwo) HTMLTemplate() string {
return getTemplate(fmt.Sprintf(htmlEmail, ct.Name()))
}
// PlainTextTemplate returns a Golang template that will generate an plain text email.
func (ct CustomThemeTwo) PlainTextTemplate() string {
return getTemplate(fmt.Sprintf(plainTextEmail, ct.Name()))
}
Based on the above definitions, the expectations is that there is a file with the given return from the Name()
function with the extensions (in our case .tpl.(html | txt)
given the usage of getTemplate). You don't have to do it exactly the way with StaticFS, but you can use this as a quick starting point.
Now that we have our definitions, we can set the Theme field in hermes.Hermes
to use one of our custom definitions:
Example
h := hermes.Hermes{
// Set your custom theme here
Theme: new(CustomThemeOne),
// ... additional fields ...
}
To change the default text direction (left-to-right), simply override it as follows:
// Configure hermes by setting a theme and your product info
h := hermes.Hermes {
// Custom text direction
TextDirection: hermes.TDRightToLeft,
}
To customize the e-mail's greeting ("Hi") or signature ("Yours truly"), supply custom strings within the e-mail's Body
:
email := hermes.Email{
Body: hermes.Body{
Greeting: "Dear",
Signature: "Sincerely",
},
}
To use a custom title string rather than a greeting/name introduction, provide it instead of Name
:
email := hermes.Email{
Body: hermes.Body{
// Title will override `Name`
Title: "Welcome to Hermes",
},
}
To customize the Copyright
, override it when initializing Hermes
within your Product
as follows:
// 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."
},
}
To use a custom fallback text at the end of the email, change the TroubleText
field of the hermes.Product
struct. The default value is If you’re having trouble with the button '{ACTION}', copy and paste the URL below into your web browser.
. The {ACTION}
placeholder will be replaced with the corresponding text of the supplied action button:
// Configure hermes by setting a theme and your product info
h := hermes.Hermes{
// Optional Theme
// Theme: new(Default),
Product: hermes.Product{
// Custom trouble text
TroubleText: "If the {ACTION}-button is not working for you, just copy and paste the URL below into your web browser."
},
}
Since v2.1.0
, Hermes is automatically inlining all CSS to improve compatibility with email clients, thanks to Premailer.
You can disable this feature by setting DisableCSSInlining
of Hermes
struct to true
.
h := hermes.Hermes{
...
DisableCSSInlining: true,
}
Hermes supports injecting custom elements such as dictionaries, tables and action buttons into e-mails.
To inject an action button in to the e-mail, supply the Actions
object as follows:
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",
},
},
},
},
}
Alternatively, instead of having a button, an action can be an invite code as follows:
email := hermes.Email{
Body: hermes.Body{
Actions: []hermes.Action{
{
Instructions: "To get started with Hermes, please use the invite code:",
InviteCode: "123456",
},
},
},
}
To inject multiple action buttons in to the e-mail, supply another struct in Actions slice Action
.
Note The
Table
field has been deprecated. We currently are supporting backwards compatability so as not to break existing users. A warning will be logged out when this field is in use.
To inject a table into the e-mail, supply the Tables
object as follows:
email := hermes.Email{
Body: hermes.Body{
Tables: []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: "Golang"},
{Key: "Description", Value: "Open source programming language that makes it easy to build simple, reliable, and efficient software"},
{Key: "Price", Value: "$10.99"},
},
{
{Key: "Item", Value: "Hermes"},
{Key: "Description", Value: "Programmatically create beautiful e-mails using Golang."},
{Key: "Price", Value: "$1.99"},
},
},
Columns: hermes.Columns{
// Custom style for each rows
CustomWidth: map[string]string{
"Item": "20%",
"Price": "15%",
},
CustomAlignment: map[string]string{
"Price": "right",
},
},
}
},
},
}
To inject key-value pairs of data into the e-mail, supply the Dictionary
object as follows:
email := hermes.Email{
Body: hermes.Body{
Dictionary: []hermes.Entry{
{Key: "Date", Value: "20 November 1887"},
{Key: "Address", Value: "221B Baker Street, London"},
},
},
}
If you need more flexibility in the content of your generated e-mail, while keeping the same format than any other e-mail, use Markdown content. Supply the FreeMarkdown
object as follows:
email := hermes.Email{
Body: hermes.Body{
FreeMarkdown: `
> _Hermes_ service will shutdown the **1st August 2017** for maintenance operations.
Services will be unavailable based on the following schedule:
| Services | Downtime |
| :------:| :-----------: |
| Service A | 2AM to 3AM |
| Service B | 4AM to 5AM |
| Service C | 5AM to 6AM |
---
Feel free to contact us for any question regarding this matter at [[email protected]](mailto:[email protected]) or in our [Gitter](https://gitter.im/)
`,
},
}
}
This code would output the following HTML template:
And the following plaintext:
------------
Hi Jon Snow,
------------
>
>
>
> Hermes service will shutdown the *1st August 2017* for maintenance
> operations.
>
>
Services will be unavailable based on the following schedule:
----------- ------------
| SERVICES | DOWNTIME |
----------- ------------
| Service A | 2AM to 3AM |
| Service B | 4AM to 5AM |
| Service C | 5AM to 6AM |
----------- ------------
Feel free to contact us for any question regarding this matter at [email protected] ( [email protected] ) or in our Gitter ( https://gitter.im/ )
Yours truly,
Hermes - https://example-hermes.com/
Copyright © 2017 Hermes. All rights reserved.
Be aware that this content will replace existing tables, dictionary and actions. Only intros, outros, header and footer will be kept.
This is helpful when your application needs sending e-mails, wrote on-the-fly by adminstrators.
Markdown is rendered with Blackfriday, so every thing Blackfriday can do, Hermes can do it as well.
This feature is a bit freeform, yet opinionated. Currently, we support overriding the email body width and injecting additional styles.
Add the TemplateOverrides
field, type map[string]any
to the hermes.Body
struct definition.
Example
hermes.Email{
Body: hermes.Body{
// Assuming other definitions (e.g. Intros/Outros/Tables); omitted to showcase TemplateOverrides
TemplateOverrides: map[string]any{
"body_width": "1000px",
"additional_styles": `
*:not(br):not(tr):not(html) {
font-family: Comic Sans MS !important;
}
`,
}
},
}
With this definition, the entire email is now overridden with a width of 1000px, and the default font-family is now Comic Sans MS. This gives flexibility and customization to the user to provide their own stylings.
- 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.
See CONTRIBUTING.md
Apache 2.0