mailyak

package module
v3.6.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 31, 2023 License: MIT Imports: 19 Imported by: 0

README

Build Status GoDoc

An elegant MIME mail library with support for attachments




A simple, easy to use email library for Go (golang).
  • Full attachment support (attach anything that implements io.Reader)
  • Send to multiple addresses at the same time, including BCC addresses.
  • Supports composing multi-part messages (HTML and plain text emails for older clients)
  • Write templates directly to the email body (implements io.Writer for convenience)
  • Production ready - several million emails sent in a production environment
  • SMTP over TLS support, with automatic STARTTLS upgrades for plaintext connections

Installation

If you're using go mod:

go get -v github.com/domodwyer/mailyak/v3

Or with GOPATH:

go get -v github.com/domodwyer/mailyak

Usage

// Create a new email - specify the SMTP host:port and auth (if needed)
mail := mailyak.New("mail.host.com:25", smtp.PlainAuth("", "user", "pass", "mail.host.com"))

mail.To("dom@itsallbroken.com")
mail.From("jsmith@example.com")
mail.FromName("Bananas for Friends")

mail.Subject("Business proposition")

// mail.HTML() and mail.Plain() implement io.Writer, so you can do handy things like
// parse a template directly into the email body
if err := t.ExecuteTemplate(mail.HTML(), "htmlEmail", data); err != nil {
    panic(" 💣 ")
}

// Or set the body using a string setter
mail.Plain().Set("Get a real email client")

// And you're done! 
if err := mail.Send(); err != nil {
    panic(" 💣 ")
}

To send an attachment:

mail := mailyak.New("mail.host.com:25", smtp.PlainAuth("", "user", "pass", "mail.host.com"))

mail.To("dom@itsallbroken.com")
mail.From("oops@itsallbroken.com")
mail.Subject("I am a teapot")
mail.HTML().Set("Don't panic")

// input can be a bytes.Buffer, os.File, os.Stdin, etc.
// call multiple times to attach multiple files
mail.Attach("filename.txt", &input)

if err := mail.Send(); err != nil {
    panic(" 💣 ")
}

Notes

  • Why "MailYak"? Because "MailyMcMailFace" is annoyingly long to type.
  • You can use a single instance of mailyak to send multiple emails after changing the to/body/whatever fields, avoiding unnecessary allocation/GC pressure.
  • Attachments are read when you call Send() to prevent holding onto multiple copies of the attachment in memory (source and email) - this means changing the attachment data between calling Attach() and Send() will change what's emailed out!
  • For your own sanity you should vendor this, and any other libraries when going into production.
Maintenance Status

This library is fully maintained.

The (relatively) small API/scope and many years spent maturing means it doesn't receive frequent code changes any more. Bug fixes are definitely accepted (and appreciated!), and you can consider this a stable and maintained library.

Documentation

Overview

Package mailyak provides a simple interface for generating MIME compliant emails, and optionally sending them over SMTP.

Both plain-text and HTML email body content is supported, and their types implement io.Writer allowing easy composition directly from templating engines, etc.

Attachments are fully supported including inline attachments, with anything that implements io.Reader suitable as a source (like files on disk, in-memory buffers, etc).

The raw MIME content can be retrieved using MimeBuf(), typically used with an API service such as Amazon SES that does not require using an SMTP interface.

MailYak supports both plain-text SMTP (which is automatically upgraded to a secure connection with STARTTLS if supported by the SMTP server) and explicit TLS connections.

Example
// Create a new email - specify the SMTP host:port and auth (or nil if not
// needed).
//
// If you want to connect using TLS, use NewWithTLS() instead.
mail := New("mail.host.com:25", smtp.PlainAuth("", "user", "pass", "mail.host.com"))

mail.To("dom@itsallbroken.com")
mail.From("jsmith@example.com")
mail.FromName("Prince Anybody")

mail.Subject("Business proposition")

// Add a custom header
mail.AddHeader("X-TOTALLY-NOT-A-SCAM", "true")

// mail.HTMLWriter() and mail.PlainWriter() implement io.Writer, so you can
// do handy things like parse a template directly into the email body - here
// we just use io.WriteString()
if _, err := io.WriteString(mail.HTML(), "So long, and thanks for all the fish."); err != nil {
	panic(" :( ")
}

// Or set the body using a string helper
mail.Plain().Set("Get a real email client")

// And you're done!
if err := mail.Send(); err != nil {
	panic(" :( ")
}
Output:

Example (Attachments)
// This will be our attachment data
buf := &bytes.Buffer{}
_, _ = io.WriteString(buf, "We're in the stickiest situation since Sticky the Stick Insect got stuck on a sticky bun.")

// Create a new email - specify the SMTP host:port and auth (or nil if not
// needed).
mail := New("mail.host.com:25", smtp.PlainAuth("", "user", "pass", "mail.host.com"))

mail.To("dom@itsallbroken.com")
mail.From("jsmith@example.com")
mail.HTML().Set("I am an email")

// buf could be anything that implements io.Reader, like a file on disk or
// an in-memory buffer.
mail.Attach("sticky.txt", buf)

if err := mail.Send(); err != nil {
	panic(" :( ")
}
Output:

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type BodyPart

type BodyPart struct{ bytes.Buffer }

BodyPart is a buffer holding the contents of an email MIME part.

Example (String)
// Create a new email - specify the SMTP host and auth
mail := New("mail.host.com:25", smtp.PlainAuth("", "user", "pass", "mail.host.com"))

// Set the plain text email content using a string
mail.Plain().Set("Get a real email client")
Output:

Example (Templates)
// Create a new email
mail := New("mail.host.com:25", smtp.PlainAuth("", "user", "pass", "mail.host.com"))

// Our pretend template data
tmplData := struct {
	Language string
}{"Go"}

// Compile a template
tmpl, err := template.New("html").Parse("I am an email template in {{ .Language }}")
if err != nil {
	panic(" :( ")
}

// Execute the template directly into the email body
if err := tmpl.Execute(mail.HTML(), tmplData); err != nil {
	panic(" :( ")
}
Output:

func (*BodyPart) Set

func (w *BodyPart) Set(s string)

Set accepts a string s as the contents of a BodyPart, replacing any existing data.

type MailYak

type MailYak struct {
	// contains filtered or unexported fields
}

MailYak is an easy-to-use email builder.

func New

func New(host string, auth smtp.Auth) *MailYak

New returns an instance of MailYak using host as the SMTP server, and authenticating with auth if non-nil.

host must include the port number (i.e. "smtp.itsallbroken.com:25")

mail := mailyak.New("smtp.itsallbroken.com:25", smtp.PlainAuth(
    "",
    "username",
    "password",
    "smtp.itsallbroken.com",
))

MailYak instances created with New will switch to using TLS after connecting if the remote host supports the STARTTLS command. For an explicit TLS connection, or to provide a custom tls.Config, use NewWithTLS() instead.

func NewLegacy

func NewLegacy(host string, auth smtp.Auth) *MailYak

NewLegacy returns an instance of MailYak using host as the SMTP server, and authenticating with auth if non-nil.

host must include the port number (i.e. "smtp.itsallbroken.com:25")

mail := mailyak.New("smtp.itsallbroken.com:25", smtp.PlainAuth(
    "",
    "username",
    "password",
    "smtp.itsallbroken.com",
))

MailYak instances created with NewLegacy will not switch to using TLS after connecting, even if the remote host supports the STARTTLS command. For an explicit TLS connection, or to provide a custom tls.Config, use NewWithTLS() instead.

func NewWithTLS

func NewWithTLS(host string, auth smtp.Auth, tlsConfig *tls.Config) (*MailYak, error)

NewWithTLS returns an instance of MailYak using host as the SMTP server over an explicit TLS connection, and authenticating with auth if non-nil.

host must include the port number (i.e. "smtp.itsallbroken.com:25")

mail := mailyak.NewWithTLS("smtp.itsallbroken.com:25", smtp.PlainAuth(
    "",
    "username",
    "password",
    "smtp.itsallbroken.com",
), tlsConfig)

If tlsConfig is nil, a sensible default is generated that can connect to host.

Example
// Create a new MailYak instance that uses an explicit TLS connection. This
// ensures no communication is performed in plain-text.
//
// Specify the SMTP host:port to connect to, the authentication credentials
// (or nil if not needed), and use an automatically generated TLS
// configuration by passing nil as the tls.Config argument.
mail, err := NewWithTLS("mail.host.com:25", smtp.PlainAuth("", "user", "pass", "mail.host.com"), nil)
if err != nil {
	panic("failed to initialise a TLS instance :(")
}

mail.Plain().Set("Have some encrypted goodness")
if err := mail.Send(); err != nil {
	panic(" :( ")
}
Output:

Example (With_config)
// Create a new MailYak instance that uses an explicit TLS connection. This
// ensures no communication is performed in plain-text.
//
// Specify the SMTP host:port to connect to, the authentication credentials
// (or nil if not needed), and use the tls.Config provided.
mail, err := NewWithTLS(
	"mail.host.com:25",
	smtp.PlainAuth("", "user", "pass", "mail.host.com"),
	&tls.Config{
		// ServerName is used to verify the hostname on the returned
		// certificates unless InsecureSkipVerify is given. It is also included
		// in the client's handshake to support virtual hosting unless it is
		// an IP address.
		ServerName: "mail.host.com",

		// Negotiate a connection that uses at least TLS v1.2, or refuse the
		// connection if the server does not support it. Most do, and it is
		// a very good idea to enforce it!
		MinVersion: tls.VersionTLS12,
	},
)
if err != nil {
	panic("failed to initialise a TLS instance :(")
}

mail.Plain().Set("Have some encrypted goodness")
if err := mail.Send(); err != nil {
	panic(" :( ")
}
Output:

func (*MailYak) AddHeader

func (m *MailYak) AddHeader(name, value string)

AddHeader adds an arbitrary email header. It appends to any existing values associated with key.

If value contains non-ASCII characters, it is Q-encoded according to RFC1342. As always, validate any user input before adding it to a message, as this method may enable an attacker to override the standard headers and, for example, BCC themselves in a password reset email to a different user.

func (*MailYak) Attach

func (m *MailYak) Attach(name string, r io.Reader)

Attach adds the contents of r to the email as an attachment with name as the filename.

r is not read until Send is called and the MIME type will be detected using https://golang.org/pkg/net/http/#DetectContentType

func (*MailYak) AttachInline

func (m *MailYak) AttachInline(name string, r io.Reader)

AttachInline adds the contents of r to the email as an inline attachment. Inline attachments are typically used within the email body, such as a logo or header image. It is up to the user to ensure name is unique.

Files can be referenced by their name within the email using the cid URL protocol:

<img src="cid:myFileName"/>

r is not read until Send is called and the MIME type will be detected using https://golang.org/pkg/net/http/#DetectContentType

Example
// Create a new email
mail := New("mail.host.com:25", smtp.PlainAuth("", "user", "pass", "mail.host.com"))
mail.To("dom@itsallbroken.com")
mail.From("jsmith@example.com")

// Initialise an io.Reader that contains your image (typically read from
// disk, or embedded in memory).
//
// Here we use an empty buffer as a mock.
imageBuffer := &bytes.Buffer{}

// Add the image as an attachment.
//
// To reference it, use the name as the cid value.
mail.AttachInline("myimage", imageBuffer)

// Set the HTML body, which includes the inline CID reference.
mail.HTML().Set(`
		<html>
		<body>
			<img src="cid:myimage"/>
		</body>
		</html>
	`)

// Send it!
if err := mail.Send(); err != nil {
	panic(" :( ")
}
Output:

func (*MailYak) AttachInlineWithMimeType

func (m *MailYak) AttachInlineWithMimeType(name string, r io.Reader, mimeType string)

AttachInlineWithMimeType adds the contents of r to the email as an inline attachment with mimeType as the specified MIME type of the content. Inline attachments are typically used within the email body, such as a logo or header image. It is up to the user to ensure name is unique and the specified mimeType is correct.

Files can be referenced by their name within the email using the cid URL protocol:

<img src="cid:myFileName"/>

r is not read until Send is called.

func (*MailYak) AttachWithMimeType

func (m *MailYak) AttachWithMimeType(name string, r io.Reader, mimeType string)

AttachWithMimeType adds the contents of r to the email as an attachment with name as the filename and mimeType as the specified MIME type of the content. It is up to the user to ensure the mimeType is correct.

r is not read until Send is called.

func (*MailYak) Bcc

func (m *MailYak) Bcc(addrs ...string)

Bcc sets a list of blind carbon copy (BCC) addresses.

You can pass one or more addresses to this method, none of which are viewable to the recipients.

mail.Bcc("dom@itsallbroken.com", "another@itsallbroken.com")

or pass a slice of strings:

bccs := []string{
	"one@itsallbroken.com",
	"John Doe <two@itsallbroken.com>"
}

mail.Bcc(bccs...)

func (*MailYak) Cc

func (m *MailYak) Cc(addrs ...string)

Cc sets a list of carbon copy (CC) addresses.

You can pass one or more addresses to this method, which are viewable to the other recipients.

mail.Cc("dom@itsallbroken.com", "another@itsallbroken.com")

or pass a slice of strings:

ccs := []string{
	"one@itsallbroken.com",
	"John Doe <two@itsallbroken.com>"
}

mail.Cc(ccs...)

func (*MailYak) ClearAttachments

func (m *MailYak) ClearAttachments()

ClearAttachments removes all current attachments.

func (*MailYak) From

func (m *MailYak) From(addr string)

From sets the sender email address.

Users should also consider setting FromName().

func (*MailYak) FromName

func (m *MailYak) FromName(name string)

FromName sets the sender name.

If set, emails typically display as being from:

From Name <sender@example.com>

If name contains non-ASCII characters, it is Q-encoded according to RFC1342.

func (*MailYak) HTML

func (m *MailYak) HTML() *BodyPart

HTML returns a BodyPart for the HTML email body.

func (*MailYak) LocalName

func (m *MailYak) LocalName(name string)

LocalName sets the sender domain name.

If set, it is used in the EHLO/HELO command instead of the default domain (localhost, see smtp.NewClient). Some SMTP servers, such as the Gmail SMTP-relay, requires a proper domain name and will reject attempts to use localhost.

func (*MailYak) MimeBuf

func (m *MailYak) MimeBuf() (*bytes.Buffer, error)

MimeBuf returns the buffer containing all the RAW MIME data.

MimeBuf is typically used with an API service such as Amazon SES that does not use an SMTP interface.

func (*MailYak) Plain

func (m *MailYak) Plain() *BodyPart

Plain returns a BodyPart for the plain-text email body.

func (*MailYak) ReplyTo

func (m *MailYak) ReplyTo(addr string)

ReplyTo sets the Reply-To email address.

Setting a ReplyTo address is optional.

func (*MailYak) Send

func (m *MailYak) Send() error

Send attempts to send the built email via the configured SMTP server.

Attachments are read and the email timestamp is created when Send() is called, and any connection/authentication errors will be returned by Send().

func (*MailYak) SetHeader

func (m *MailYak) SetHeader(name, value string)

SetHeader sets the header entries associated with key to the single element value. It replaces any existing values associated with key.

If value contains non-ASCII characters, it is Q-encoded according to RFC1342. As always, validate any user input before adding it to a message, as this method may enable an attacker to override the standard headers and, for example, BCC themselves in a password reset email to a different user.

func (*MailYak) String

func (m *MailYak) String() string

String returns a redacted description of the email state, typically for logging or debugging purposes.

Authentication information is not included in the returned string.

func (*MailYak) Subject

func (m *MailYak) Subject(sub string)

Subject sets the email subject line.

If sub contains non-ASCII characters, it is Q-encoded according to RFC1342.

func (*MailYak) To

func (m *MailYak) To(addrs ...string)

To sets a list of recipient addresses.

You can pass one or more addresses to this method, all of which are viewable to the recipients.

mail.To("dom@itsallbroken.com", "another@itsallbroken.com")

or pass a slice of strings:

tos := []string{
	"one@itsallbroken.com",
	"John Doe <two@itsallbroken.com>"
}

mail.To(tos...)

func (*MailYak) WriteBccHeader

func (m *MailYak) WriteBccHeader(shouldWrite bool)

WriteBccHeader writes the BCC header to the MIME body when true. Defaults to false.

This is usually required when writing the MIME body to an email API such as Amazon's SES, but can cause problems when sending emails via a SMTP server.

Specifically, RFC822 says:

Some  systems  may choose to include the text of the "Bcc" field only in the
author(s)'s  copy,  while  others  may also include it in the text sent to
all those indicated in the "Bcc" list.

This ambiguity can result in some SMTP servers not stripping the BCC header and exposing the BCC addressees to recipients. For more information, see:

https://github.com/domodwyer/mailyak/issues/14

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL