blob: 734d881b3f0cc7f17dd7175d148e2f82efd2a7e1 [file] [log] [blame]
// Package gomail provides a simple interface to send emails.
//
// More info on Github: https://github.com/go-gomail/gomail
package gomail
import (
"bytes"
"io"
"io/ioutil"
"mime"
"path/filepath"
"sync"
"time"
"gopkg.in/alexcesaro/quotedprintable.v2"
)
// Message represents an email.
type Message struct {
header header
parts []part
attachments []*File
embedded []*File
charset string
encoding Encoding
hEncoder *quotedprintable.HeaderEncoder
}
type header map[string][]string
type part struct {
contentType string
body *bytes.Buffer
}
// NewMessage creates a new message. It uses UTF-8 and quoted-printable encoding
// by default.
func NewMessage(settings ...MessageSetting) *Message {
msg := &Message{
header: make(header),
charset: "UTF-8",
encoding: QuotedPrintable,
}
msg.applySettings(settings)
var e quotedprintable.Encoding
if msg.encoding == Base64 {
e = quotedprintable.B
} else {
e = quotedprintable.Q
}
msg.hEncoder = e.NewHeaderEncoder(msg.charset)
return msg
}
func (msg *Message) applySettings(settings []MessageSetting) {
for _, s := range settings {
s(msg)
}
}
// A MessageSetting can be used as an argument in NewMessage to configure an
// email.
type MessageSetting func(msg *Message)
// SetCharset is a message setting to set the charset of the email.
//
// Example:
//
// msg := gomail.NewMessage(SetCharset("ISO-8859-1"))
func SetCharset(charset string) MessageSetting {
return func(msg *Message) {
msg.charset = charset
}
}
// SetEncoding is a message setting to set the encoding of the email.
//
// Example:
//
// msg := gomail.NewMessage(SetEncoding(gomail.Base64))
func SetEncoding(enc Encoding) MessageSetting {
return func(msg *Message) {
msg.encoding = enc
}
}
// Encoding represents a MIME encoding scheme like quoted-printable or base64.
type Encoding string
const (
// QuotedPrintable represents the quoted-printable encoding as defined in
// RFC 2045.
QuotedPrintable Encoding = "quoted-printable"
// Base64 represents the base64 encoding as defined in RFC 2045.
Base64 Encoding = "base64"
// Unencoded can be used to avoid encoding the body of an email. The headers
// will still be encoded using quoted-printable encoding.
Unencoded Encoding = "8bit"
)
// SetHeader sets a value to the given header field.
func (msg *Message) SetHeader(field string, value ...string) {
for i := range value {
value[i] = encodeHeader(msg.hEncoder, value[i])
}
msg.header[field] = value
}
// SetHeaders sets the message headers.
//
// Example:
//
// msg.SetHeaders(map[string][]string{
// "From": {"alex@example.com"},
// "To": {"bob@example.com", "cora@example.com"},
// "Subject": {"Hello"},
// })
func (msg *Message) SetHeaders(h map[string][]string) {
for k, v := range h {
msg.SetHeader(k, v...)
}
}
// SetAddressHeader sets an address to the given header field.
func (msg *Message) SetAddressHeader(field, address, name string) {
msg.header[field] = []string{msg.FormatAddress(address, name)}
}
// FormatAddress formats an address and a name as a valid RFC 5322 address.
func (msg *Message) FormatAddress(address, name string) string {
buf := getBuffer()
defer putBuffer(buf)
if !quotedprintable.NeedsEncoding(name) {
quote(buf, name)
} else {
var n string
if hasSpecials(name) {
n = encodeHeader(quotedprintable.B.NewHeaderEncoder(msg.charset), name)
} else {
n = encodeHeader(msg.hEncoder, name)
}
buf.WriteString(n)
}
buf.WriteString(" <")
buf.WriteString(address)
buf.WriteByte('>')
return buf.String()
}
// SetDateHeader sets a date to the given header field.
func (msg *Message) SetDateHeader(field string, date time.Time) {
msg.header[field] = []string{msg.FormatDate(date)}
}
// FormatDate formats a date as a valid RFC 5322 date.
func (msg *Message) FormatDate(date time.Time) string {
return date.Format(time.RFC1123Z)
}
// GetHeader gets a header field.
func (msg *Message) GetHeader(field string) []string {
return msg.header[field]
}
// DelHeader deletes a header field.
func (msg *Message) DelHeader(field string) {
delete(msg.header, field)
}
// SetBody sets the body of the message.
func (msg *Message) SetBody(contentType, body string) {
msg.parts = []part{
part{
contentType: contentType,
body: bytes.NewBufferString(body),
},
}
}
// AddAlternative adds an alternative body to the message. Commonly used to
// send HTML emails that default to the plain text version for backward
// compatibility.
//
// Example:
//
// msg.SetBody("text/plain", "Hello!")
// msg.AddAlternative("text/html", "<p>Hello!</p>")
//
// More info: http://en.wikipedia.org/wiki/MIME#Alternative
func (msg *Message) AddAlternative(contentType, body string) {
msg.parts = append(msg.parts,
part{
contentType: contentType,
body: bytes.NewBufferString(body),
},
)
}
// GetBodyWriter gets a writer that writes to the body. It can be useful with
// the templates from packages text/template or html/template.
//
// Example:
//
// w := msg.GetBodyWriter("text/plain")
// t := template.Must(template.New("example").Parse("Hello {{.}}!"))
// t.Execute(w, "Bob")
func (msg *Message) GetBodyWriter(contentType string) io.Writer {
buf := new(bytes.Buffer)
msg.parts = append(msg.parts,
part{
contentType: contentType,
body: buf,
},
)
return buf
}
// A File represents a file that can be attached or embedded in an email.
type File struct {
Name string
MimeType string
Content []byte
ContentID string
}
// OpenFile opens a file on disk to create a gomail.File.
func OpenFile(filename string) (*File, error) {
content, err := readFile(filename)
if err != nil {
return nil, err
}
f := CreateFile(filepath.Base(filename), content)
return f, nil
}
// CreateFile creates a gomail.File from the given name and content.
func CreateFile(name string, content []byte) *File {
mimeType := mime.TypeByExtension(filepath.Ext(name))
if mimeType == "" {
mimeType = "application/octet-stream"
}
return &File{
Name: name,
MimeType: mimeType,
Content: content,
}
}
// Attach attaches the files to the email.
func (msg *Message) Attach(f ...*File) {
if msg.attachments == nil {
msg.attachments = f
} else {
msg.attachments = append(msg.attachments, f...)
}
}
// Embed embeds the images to the email.
//
// Example:
//
// f, err := gomail.OpenFile("/tmp/image.jpg")
// if err != nil {
// panic(err)
// }
// msg.Embed(f)
// msg.SetBody("text/html", `<img src="cid:image.jpg" alt="My image" />`)
func (msg *Message) Embed(image ...*File) {
if msg.embedded == nil {
msg.embedded = image
} else {
msg.embedded = append(msg.embedded, image...)
}
}
// Stubbed out for testing.
var readFile = ioutil.ReadFile
func quote(buf *bytes.Buffer, text string) {
buf.WriteByte('"')
for i := 0; i < len(text); i++ {
if text[i] == '\\' || text[i] == '"' {
buf.WriteByte('\\')
}
buf.WriteByte(text[i])
}
buf.WriteByte('"')
}
func hasSpecials(text string) bool {
for i := 0; i < len(text); i++ {
switch c := text[i]; c {
case '(', ')', '<', '>', '[', ']', ':', ';', '@', '\\', ',', '.', '"':
return true
}
}
return false
}
func encodeHeader(enc *quotedprintable.HeaderEncoder, value string) string {
if !quotedprintable.NeedsEncoding(value) {
return value
}
return enc.Encode(value)
}
var bufPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func getBuffer() *bytes.Buffer {
return bufPool.Get().(*bytes.Buffer)
}
func putBuffer(buf *bytes.Buffer) {
if buf.Len() > 1024 {
return
}
buf.Reset()
bufPool.Put(buf)
}