TBR: PROTOTYPE(discovery): update third_party

Change-Id: Ibaef4fca7a916e88975266acc1d528769dc2f489
diff --git a/go/src/gopkg.in/alexcesaro/quotedprintable.v2/LICENSE b/go/src/gopkg.in/alexcesaro/quotedprintable.v2/LICENSE
new file mode 100644
index 0000000..5f5c12a
--- /dev/null
+++ b/go/src/gopkg.in/alexcesaro/quotedprintable.v2/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Alexandre Cesaro
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/go/src/gopkg.in/alexcesaro/quotedprintable.v2/README.google b/go/src/gopkg.in/alexcesaro/quotedprintable.v2/README.google
new file mode 100644
index 0000000..9ace3f7
--- /dev/null
+++ b/go/src/gopkg.in/alexcesaro/quotedprintable.v2/README.google
@@ -0,0 +1,10 @@
+URL: https://github.com/alexcesaro/quotedprintable/archive/9b4a113f96b3263b5223fe9c5fd1d1115811b343.zip
+Version: 44d228784e161379153583e31bcc32d9a25796e3
+License: The MIT License (MIT)
+License File: LICENSE
+
+Description:
+A Go package concerning quoted-printable encoding.
+
+Local Modifications:
+No modifications.
diff --git a/go/src/gopkg.in/alexcesaro/quotedprintable.v2/README.md b/go/src/gopkg.in/alexcesaro/quotedprintable.v2/README.md
new file mode 100644
index 0000000..afc82b0
--- /dev/null
+++ b/go/src/gopkg.in/alexcesaro/quotedprintable.v2/README.md
@@ -0,0 +1,20 @@
+# quotedprintable
+
+## Introduction
+
+Package quotedprintable implements quoted-printable and message header encoding
+as specified by RFC 2045 and RFC 2047.
+
+It is similar to the package currently being developed in the standard library: https://codereview.appspot.com/132680044/
+
+It requires Go 1.3 or newer.
+
+Some links:
+ - [Post on golang-dev](https://groups.google.com/d/topic/golang-dev/PK_ICQNJTmg/discussion)
+ - [issue on the bug traker](https://code.google.com/p/go/issues/detail?id=4943)
+ - [the former code review](https://codereview.appspot.com/101330049/)
+
+
+## Documentation
+
+https://godoc.org/gopkg.in/alexcesaro/quotedprintable.v2
diff --git a/go/src/gopkg.in/alexcesaro/quotedprintable.v2/header.go b/go/src/gopkg.in/alexcesaro/quotedprintable.v2/header.go
new file mode 100644
index 0000000..cd16f2c
--- /dev/null
+++ b/go/src/gopkg.in/alexcesaro/quotedprintable.v2/header.go
@@ -0,0 +1,407 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file defines encoding and decoding functions for encoded-words
+// as defined in RFC 2047.
+
+package quotedprintable
+
+import (
+	"bytes"
+	"encoding/base64"
+	"fmt"
+	"io"
+	"strings"
+	"sync"
+	"unicode/utf8"
+
+	"gopkg.in/alexcesaro/quotedprintable.v2/internal"
+)
+
+// Encoding represents the encoding used in encoded-words. It must be one of the
+// two encodings defined in RFC 2047 ("B" or "Q" encoding).
+type Encoding string
+
+const (
+	// Q represents the Q-encoding defined in RFC 2047.
+	Q Encoding = "Q"
+	// B represents the Base64 encoding defined in RFC 2045.
+	B Encoding = "B"
+)
+
+// A HeaderEncoder is an RFC 2047 encoded-word encoder.
+type HeaderEncoder struct {
+	charset    string
+	encoding   Encoding
+	splitWords bool
+}
+
+const maxEncodedWordLen = 75 // As defined in RFC 2047, section 2
+
+// NewHeaderEncoder returns a new HeaderEncoder to encode strings in the
+// specified charset.
+func (enc Encoding) NewHeaderEncoder(charset string) *HeaderEncoder {
+	// We automatically split encoded-words only when the charset is UTF-8
+	// because since multi-octet character must not be split across adjacent
+	// encoded-words (see RFC 2047, section 5) there is no way to split words
+	// without knowing how the charset works.
+	splitWords := strings.ToUpper(charset) == "UTF-8"
+
+	return &HeaderEncoder{charset, enc, splitWords}
+}
+
+// Encode encodes a string to be used as a MIME header value.
+func (e *HeaderEncoder) Encode(s string) string {
+	return e.encodeWord(s)
+}
+
+// NeedsEncoding returns whether the given header content needs to be encoded
+// into an encoded-words.
+func NeedsEncoding(s string) bool {
+	for i := 0; i < len(s); i++ {
+		b := s[i]
+		if (b > '~' || b < ' ') && b != '\t' {
+			return true
+		}
+	}
+
+	return false
+}
+
+type bufPool struct {
+	sync.Pool
+}
+
+func (b *bufPool) GetBuffer() *bytes.Buffer {
+	return bufFree.Get().(*bytes.Buffer)
+}
+
+func (b *bufPool) PutBuffer(buf *bytes.Buffer) {
+	if buf.Len() > 1024 {
+		return
+	}
+	buf.Reset()
+	b.Put(buf)
+}
+
+var bufFree = &bufPool{
+	sync.Pool{
+		New: func() interface{} {
+			return new(bytes.Buffer)
+		},
+	},
+}
+
+// encodeWord encodes a string into an encoded-word.
+func (e *HeaderEncoder) encodeWord(s string) string {
+	buf := bufFree.GetBuffer()
+	defer bufFree.PutBuffer(buf)
+
+	e.openWord(buf)
+
+	switch {
+	case e.encoding == B:
+		e.encodeWordB(buf, s)
+	default:
+		e.encodeWordQ(buf, s)
+	}
+
+	e.closeWord(buf)
+	return buf.String()
+}
+
+func (e *HeaderEncoder) encodeWordB(buf *bytes.Buffer, s string) {
+	maxLen := maxEncodedWordLen - buf.Len() - 2
+	if !e.splitWords || base64.StdEncoding.EncodedLen(len(s)) <= maxLen {
+		buf.WriteString(base64.StdEncoding.EncodeToString([]byte(s)))
+		return
+	}
+
+	v := []byte(s)
+	var n, last, runeSize int
+	for i := 0; i < len(s); i += runeSize {
+		runeSize = getRuneSize(s, i)
+
+		if base64.StdEncoding.EncodedLen(n+runeSize) <= maxLen {
+			n += runeSize
+		} else {
+			buf.WriteString(base64.StdEncoding.EncodeToString(v[last:i]))
+			e.splitWord(buf)
+			last = i
+			n = runeSize
+		}
+	}
+	buf.WriteString(base64.StdEncoding.EncodeToString(v[last:]))
+}
+
+func (e *HeaderEncoder) encodeWordQ(buf *bytes.Buffer, s string) {
+	if !e.splitWords {
+		for i := 0; i < len(s); i++ {
+			writeQ(buf, s[i])
+		}
+		return
+	}
+
+	var runeSize int
+	n := buf.Len()
+	for i := 0; i < len(s); i += runeSize {
+		b := s[i]
+		var encLen int
+		if b >= ' ' && b <= '~' && b != '=' && b != '?' && b != '_' {
+			encLen, runeSize = 1, 1
+		} else {
+			runeSize = getRuneSize(s, i)
+			encLen = 3 * runeSize
+		}
+
+		// We remove 2 to let spaces for closing chars "?="
+		if n+encLen > maxEncodedWordLen-2 {
+			n = e.splitWord(buf)
+		}
+		writeQString(buf, s[i:i+runeSize])
+		n += encLen
+	}
+}
+
+func (e *HeaderEncoder) openWord(buf *bytes.Buffer) int {
+	n := buf.Len()
+	buf.WriteString("=?")
+	buf.WriteString(e.charset)
+	buf.WriteByte('?')
+	buf.WriteString(string(e.encoding))
+	buf.WriteByte('?')
+
+	return buf.Len() - n
+}
+
+func (e *HeaderEncoder) closeWord(buf *bytes.Buffer) {
+	buf.WriteString("?=")
+}
+
+func (e *HeaderEncoder) splitWord(buf *bytes.Buffer) int {
+	e.closeWord(buf)
+	buf.WriteString("\r\n ")
+	return e.openWord(buf)
+}
+
+func getRuneSize(s string, i int) int {
+	runeSize := 1
+	for i+runeSize < len(s) && !utf8.RuneStart(s[i+runeSize]) {
+		runeSize++
+	}
+
+	return runeSize
+}
+
+func writeQString(buf *bytes.Buffer, s string) {
+	for i := 0; i < len(s); i++ {
+		writeQ(buf, s[i])
+	}
+}
+
+func writeQ(buf *bytes.Buffer, b byte) {
+	switch {
+	case b == ' ':
+		buf.WriteByte('_')
+	case b >= '!' && b <= '~' && b != '=' && b != '?' && b != '_':
+		buf.WriteByte(b)
+	default:
+		enc := make([]byte, 3)
+		internal.EncodeByte(enc, b)
+		buf.Write(enc)
+	}
+}
+
+// DecodeHeader decodes a MIME header by decoding all encoded-words of the
+// header. The returned text is encoded in the returned charset. Text is not
+// necessarily encoded in UTF-8. Returned charset is always upper case. This
+// function does not support decoding headers with multiple encoded-words
+// using different charsets.
+func DecodeHeader(header string) (text, charset string, err error) {
+	buf := bufFree.GetBuffer()
+	defer bufFree.PutBuffer(buf)
+
+	for {
+		i := strings.IndexByte(header, '=')
+		if i == -1 {
+			break
+		}
+		if i > 0 {
+			buf.WriteString(header[:i])
+			header = header[i:]
+		}
+
+		word := getEncodedWord(header)
+		if word == "" {
+			buf.WriteByte('=')
+			header = header[1:]
+			continue
+		}
+
+		for {
+			dec, wordCharset, err := decodeWord(word)
+			if err != nil {
+				buf.WriteString(word)
+				header = header[len(word):]
+				break
+			}
+			if charset == "" {
+				charset = wordCharset
+			} else if charset != wordCharset {
+				return "", "", fmt.Errorf("quotedprintable: multiple charsets in header are not supported: %q and %q used", charset, wordCharset)
+			}
+			buf.Write(dec)
+			header = header[len(word):]
+
+			// White-space and newline characters separating two encoded-words
+			// must be deleted.
+			var j int
+			for j = 0; j < len(header); j++ {
+				b := header[j]
+				if b != ' ' && b != '\t' && b != '\n' && b != '\r' {
+					break
+				}
+			}
+			if j == 0 {
+				// If there are no white-space characters following the current
+				// encoded-word there is nothing special to do.
+				break
+			}
+			word = getEncodedWord(header[j:])
+			if word == "" {
+				break
+			}
+			header = header[j:]
+		}
+	}
+	buf.WriteString(header)
+
+	return buf.String(), charset, nil
+}
+
+func getEncodedWord(s string) string {
+	if len(s) < 2 {
+		return ""
+	}
+	if s[0] != '=' {
+		return ""
+	}
+	if s[1] != '?' {
+		return ""
+	}
+
+	n := 2
+	for {
+		if n >= len(s) {
+			return ""
+		}
+
+		b := s[n]
+		if (b < '0' || b > '9') &&
+			(b < 'A' || b > 'Z') &&
+			(b < 'a' || b > 'z') &&
+			b != '-' {
+			break
+		}
+
+		n++
+	}
+	if s[n] != '?' {
+		return ""
+	}
+
+	if n+2 >= len(s) {
+		return ""
+	}
+	b := s[n+1]
+	if b != 'Q' && b != 'B' && b != 'q' && b != 'b' {
+		return ""
+	}
+	if s[n+2] != '?' {
+		return ""
+	}
+
+	n = n + 3
+	for {
+		if n >= len(s) {
+			return ""
+		}
+
+		if s[n] < ' ' || s[n] > '~' {
+			return ""
+		}
+		if s[n] == '?' {
+			n++
+			break
+		}
+		n++
+	}
+	if n >= len(s) || s[n] != '=' {
+		return ""
+	}
+
+	return s[0 : n+1]
+}
+
+func decodeWord(s string) (text []byte, charset string, err error) {
+	fields := strings.Split(s, "?")
+	if len(fields) != 5 || fields[0] != "=" || fields[4] != "=" || len(fields[2]) != 1 {
+		return []byte(s), "", nil
+	}
+
+	charset, enc, src := fields[1], fields[2], fields[3]
+
+	var dec []byte
+	switch Encoding(strings.ToUpper(enc)) {
+	case B:
+		if dec, err = base64.StdEncoding.DecodeString(src); err != nil {
+			return dec, charset, err
+		}
+	case Q:
+		if dec, err = qDecode(src); err != nil {
+			return dec, charset, err
+		}
+	default:
+		return []byte(""), charset, fmt.Errorf("quotedprintable: RFC 2047 encoding not supported: %q", enc)
+	}
+
+	return dec, strings.ToUpper(charset), nil
+}
+
+// qDecode decodes a Q encoded string.
+func qDecode(s string) ([]byte, error) {
+	length := len(s)
+	for i := 0; i < len(s); i++ {
+		if s[i] == '=' {
+			length -= 2
+			i += 2
+		}
+	}
+	dec := make([]byte, length)
+
+	n := 0
+	for i := 0; i < len(s); i++ {
+		switch c := s[i]; {
+		case c == '_':
+			dec[n] = ' '
+		case c == '=':
+			if i+2 >= len(s) {
+				return []byte(""), io.ErrUnexpectedEOF
+			}
+			buf, err := internal.ReadHexByte([]byte(s[i+1:]))
+			if err != nil {
+				return []byte(""), err
+			}
+			dec[n] = buf
+			i += 2
+		case (c >= ' ' && c <= '~') || c == '\n' || c == '\r' || c == '\t':
+			dec[n] = c
+		default:
+			return []byte(""), fmt.Errorf("quotedprintable: invalid unescaped byte %#02x in Q encoded string at byte %d", c, i)
+		}
+		n++
+	}
+
+	return dec, nil
+}
diff --git a/go/src/gopkg.in/alexcesaro/quotedprintable.v2/header_test.go b/go/src/gopkg.in/alexcesaro/quotedprintable.v2/header_test.go
new file mode 100644
index 0000000..8612ccb
--- /dev/null
+++ b/go/src/gopkg.in/alexcesaro/quotedprintable.v2/header_test.go
@@ -0,0 +1,115 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package quotedprintable
+
+import (
+	"fmt"
+	"strings"
+	"testing"
+)
+
+func ExampleEncodeHeader() {
+	enc := Q.NewHeaderEncoder("UTF-8")
+	fmt.Println(enc.Encode("Coffee"))
+	fmt.Println(enc.Encode("Café"))
+	// Output:
+	// =?UTF-8?Q?Coffee?=
+	// =?UTF-8?Q?Caf=C3=A9?=
+}
+
+func ExampleNewHeaderEncoder() {
+	e := B.NewHeaderEncoder("UTF-8")
+	fmt.Printf(e.Encode("Caf\xc3"))
+	// Output: =?UTF-8?B?Q2Fmww==?=
+}
+
+func ExampleDecodeHeader() {
+	// text is not encoded in UTF-8 but in ISO-8859-1
+	text, charset, err := DecodeHeader("=?ISO-8859-1?Q?Caf=C3?=")
+	if err != nil {
+		panic(err)
+	}
+	fmt.Printf("Text: %q, charset: %q", text, charset)
+	// Output: Text: "Caf\xc3", charset: "ISO-8859-1"
+}
+
+func TestEncodeHeader(t *testing.T) {
+	utf8, iso88591 := "UTF-8", "iso-8859-1"
+	tests := []struct {
+		charset  string
+		encoding Encoding
+		src, exp string
+	}{
+		{utf8, Q, "François-Jérôme", "=?UTF-8?Q?Fran=C3=A7ois-J=C3=A9r=C3=B4me?="},
+		{utf8, B, "André", "=?UTF-8?B?QW5kcsOp?="},
+		{iso88591, Q, "Rapha\xebl Dupont", "=?iso-8859-1?Q?Rapha=EBl_Dupont?="},
+		{utf8, Q, "A", "=?UTF-8?Q?A?="},
+		{utf8, Q, "An 'encoded-word' may not be more than 75 characters long, including 'charset', 'encoding', 'encoded-text', and delimiters. ©", "=?UTF-8?Q?An_'encoded-word'_may_not_be_more_than_75_characters_long,_incl?=\r\n =?UTF-8?Q?uding_'charset',_'encoding',_'encoded-text',_and_delimiters._?=\r\n =?UTF-8?Q?=C2=A9?="},
+		{utf8, Q, strings.Repeat("0", 62) + "é", "=?UTF-8?Q?" + strings.Repeat("0", 62) + "?=\r\n =?UTF-8?Q?=C3=A9?="},
+		{utf8, B, strings.Repeat("é", 23), "=?UTF-8?B?w6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6nDqcOpw6k=?=\r\n =?UTF-8?B?w6k=?="},
+	}
+
+	for _, test := range tests {
+		e := test.encoding.NewHeaderEncoder(test.charset)
+		if s := e.Encode(test.src); s != test.exp {
+			t.Errorf("Encode(%q) = %q, want %q", test.src, s, test.exp)
+		}
+	}
+}
+
+func TestDecodeHeader(t *testing.T) {
+	tests := []struct {
+		src, exp, charset string
+		isError           bool
+	}{
+		{"=?UTF-8?Q?Fran=C3=A7ois-J=C3=A9r=C3=B4me?=", "François-Jérôme", "UTF-8", false},
+		{"=?UTF-8?q?ascii?=", "ascii", "UTF-8", false},
+		{"=?utf-8?B?QW5kcsOp?=", "André", "UTF-8", false},
+		{"=?ISO-8859-1?Q?Rapha=EBl_Dupont?=", "Rapha\xebl Dupont", "ISO-8859-1", false},
+		{"Jean", "Jean", "", false},
+		{"=?UTF-8?A?Test?=", "=?UTF-8?A?Test?=", "", false},
+		{"=?UTF-8?Q?A=B?=", "=?UTF-8?Q?A=B?=", "", false},
+		{"=?UTF-8?Q?=A?=", "=?UTF-8?Q?=A?=", "", false},
+		{"=?UTF-8?A?A?=", "=?UTF-8?A?A?=", "", false},
+		// Tests from RFC 2047
+		{"=?ISO-8859-1?Q?a?=", "a", "ISO-8859-1", false},
+		{"=?ISO-8859-1?Q?a?= b", "a b", "ISO-8859-1", false},
+		{"=?ISO-8859-1?Q?a?= =?ISO-8859-1?Q?b?=", "ab", "ISO-8859-1", false},
+		{"=?ISO-8859-1?Q?a?=  =?ISO-8859-1?Q?b?=", "ab", "ISO-8859-1", false},
+		{"=?ISO-8859-1?Q?a?= \r\n\t =?ISO-8859-1?Q?b?=", "ab", "ISO-8859-1", false},
+		{"=?ISO-8859-1?Q?a_b?=", "a b", "ISO-8859-1", false},
+		{"=?ISO-8859-1?Q?a?= =?ISO-8859-2?Q?_b?=", "", "", true},
+	}
+
+	for _, test := range tests {
+		s, charset, err := DecodeHeader(test.src)
+		if test.isError && err == nil {
+			t.Errorf("DecodeHeader(%q) should return an error", test.src)
+		}
+		if !test.isError && err != nil {
+			t.Errorf("DecodeHeader(%q): %v", test.src, err)
+		}
+		if s != test.exp || charset != test.charset {
+			t.Errorf("DecodeHeader(%q) = %q (charset=%q), want %q (charset=%q)", test.src, s, charset, test.exp, test.charset)
+		}
+	}
+}
+
+var testHeader = "¡Hola, señor!"
+
+func BenchmarkQEncode(b *testing.B) {
+	enc := Q.NewHeaderEncoder("UTF-8")
+	for i := 0; i < b.N; i++ {
+		enc.Encode(testHeader)
+	}
+}
+
+func BenchmarkBEncode(b *testing.B) {
+	encoder := &HeaderEncoder{charset: "UTF-8", encoding: B, splitWords: true}
+
+	for i := 0; i < b.N; i++ {
+		encoder.Encode(testHeader)
+	}
+}
diff --git a/go/src/gopkg.in/alexcesaro/quotedprintable.v2/internal/quotedprintable.go b/go/src/gopkg.in/alexcesaro/quotedprintable.v2/internal/quotedprintable.go
new file mode 100644
index 0000000..8b08bd0
--- /dev/null
+++ b/go/src/gopkg.in/alexcesaro/quotedprintable.v2/internal/quotedprintable.go
@@ -0,0 +1,43 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package internal contains quoted-printable internals shared by mime and
+// mime/quotedprintable.
+package internal
+
+import "fmt"
+
+// EncodeByte encodes a byte using the quoted-printable encoding.
+func EncodeByte(dst []byte, b byte) {
+	dst[0] = '='
+	dst[1] = upperhex[b>>4]
+	dst[2] = upperhex[b&0x0f]
+}
+
+const upperhex = "0123456789ABCDEF"
+
+func fromHex(b byte) (byte, error) {
+	switch {
+	case b >= '0' && b <= '9':
+		return b - '0', nil
+	case b >= 'A' && b <= 'F':
+		return b - 'A' + 10, nil
+	// Accept badly encoded bytes
+	case b >= 'a' && b <= 'f':
+		return b - 'a' + 10, nil
+	}
+	return 0, fmt.Errorf("quotedprintable: invalid quoted-printable hex byte %#02x", b)
+}
+
+// ReadHexByte returns the byte represented by an hexadecimal byte slice of length 2.
+func ReadHexByte(v []byte) (b byte, err error) {
+	var hb, lb byte
+	if hb, err = fromHex(v[0]); err != nil {
+		return 0, err
+	}
+	if lb, err = fromHex(v[1]); err != nil {
+		return 0, err
+	}
+	return hb<<4 | lb, nil
+}
diff --git a/go/src/gopkg.in/alexcesaro/quotedprintable.v2/quotedprintable.go b/go/src/gopkg.in/alexcesaro/quotedprintable.v2/quotedprintable.go
new file mode 100644
index 0000000..2a48283
--- /dev/null
+++ b/go/src/gopkg.in/alexcesaro/quotedprintable.v2/quotedprintable.go
@@ -0,0 +1,223 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file defines quoted-printable decoders and encoders, as specified in RFC
+// 2045.
+// Deviations:
+// 1. in addition to "=\r\n", "=\n" is also treated as soft line break.
+// 2. it will pass through a '\r' or '\n' not preceded by '=', consistent
+//    with other broken QP encoders and decoders.
+
+// Package quotedprintable implements quoted-printable and message header
+// encoding as specified by RFC 2045 and RFC 2047.
+package quotedprintable
+
+import (
+	"bufio"
+	"bytes"
+	"fmt"
+	"io"
+
+	"gopkg.in/alexcesaro/quotedprintable.v2/internal"
+)
+
+// Encode encodes src into at most MaxEncodedLen(len(src)) bytes to dst,
+// returning the actual number of bytes written to dst.
+func Encode(dst, src []byte) int {
+	n := 0
+	for i, c := range src {
+		switch {
+		case c != '=' && (c >= '!' && c <= '~' || c == '\n' || c == '\r'):
+			dst[n] = c
+			n++
+		case c == ' ' || c == '\t':
+			if isLastChar(i, src) {
+				internal.EncodeByte(dst[n:], c)
+				n += 3
+			} else {
+				dst[n] = c
+				n++
+			}
+		default:
+			internal.EncodeByte(dst[n:], c)
+			n += 3
+		}
+	}
+
+	return n
+}
+
+// isLastChar returns true if byte i is the last character of the line.
+func isLastChar(i int, src []byte) bool {
+	return i == len(src)-1 ||
+		(i < len(src)-1 && src[i+1] == '\n') ||
+		(i < len(src)-2 && src[i+1] == '\r' && src[i+2] == '\n')
+}
+
+// EncodeToString returns the quoted-printable encoding of src.
+func EncodeToString(src []byte) string {
+	dbuf := make([]byte, MaxEncodedLen(len(src)))
+	n := Encode(dbuf, src)
+	return string(dbuf[:n])
+}
+
+// MaxEncodedLen returns the maximum length of an encoding of n source bytes.
+func MaxEncodedLen(n int) int { return 3 * n }
+
+// NewEncoder returns a new quoted-printable stream encoder. Data written to the
+// returned writer will be encoded and then written to w.
+func NewEncoder(w io.Writer) io.Writer {
+	return &encoder{w}
+}
+
+type encoder struct {
+	w io.Writer
+}
+
+func (e *encoder) Write(p []byte) (int, error) {
+	dbuf := make([]byte, MaxEncodedLen(len(p)))
+	n := Encode(dbuf, p)
+	n, err := e.w.Write(dbuf[:n])
+	if err != nil {
+		nn := 0
+		for i := 0; i < n; i++ {
+			if dbuf[i] == '=' {
+				if i+2 >= n {
+					break
+				}
+				i += 2
+			}
+			nn++
+		}
+		return nn, err
+	}
+
+	return len(p), nil
+}
+
+// Decode decodes src into at most MaxDecodedLen(len(src)) bytes to dst,
+// returning the actual number of bytes written to dst.
+func Decode(dst, src []byte) (n int, err error) {
+	var eol, trimLen, eolLen int
+	for i := 0; i < len(src); i++ {
+		if i == eol {
+			eol = bytes.IndexByte(src[i:], '\n') + i + 1
+			if eol == i {
+				eol = len(src)
+				eolLen = 0
+			} else if eol-2 >= i && src[eol-2] == '\r' {
+				eolLen = 2
+			} else {
+				eolLen = 1
+			}
+
+			// Count the number of bytes to trim
+			trimLen = 0
+			for {
+				if trimLen == eol-eolLen-i {
+					break
+				}
+
+				switch src[eol-eolLen-trimLen-1] {
+				case '\n', '\r', ' ', '\t':
+					trimLen++
+					continue
+				case '=':
+					if trimLen > 0 {
+						trimLen += eolLen + 1
+						eolLen = 0
+						err = fmt.Errorf("quotedprintable: invalid bytes after =: %q", src[eol-trimLen+1:eol])
+					} else {
+						trimLen = eolLen + 1
+						eolLen = 0
+					}
+				}
+				break
+			}
+		}
+
+		// Skip trimmable bytes
+		if trimLen > 0 && i == eol-trimLen-eolLen {
+			if err != nil {
+				return n, err
+			}
+
+			i += trimLen - 1
+			continue
+		}
+
+		switch c := src[i]; {
+		case c == '=':
+			if i+2 >= len(src) {
+				return n, io.ErrUnexpectedEOF
+			}
+			b, convErr := internal.ReadHexByte(src[i+1:])
+			if convErr != nil {
+				return n, convErr
+			}
+			dst[n] = b
+			n++
+			i += 2
+		case (c >= ' ' && c <= '~') || c == '\n' || c == '\r' || c == '\t':
+			dst[n] = c
+			n++
+		default:
+			return n, fmt.Errorf("quotedprintable: invalid unescaped byte 0x%02x in quoted-printable body", c)
+		}
+	}
+
+	return n, nil
+}
+
+// MaxDecodedLen returns the maximum length of a decoding of n source bytes.
+func MaxDecodedLen(n int) int { return n }
+
+// DecodeString returns the bytes represented by the quoted-printable string s.
+func DecodeString(s string) ([]byte, error) {
+	dbuf := make([]byte, MaxDecodedLen(len(s)))
+	n, err := Decode(dbuf, []byte(s))
+	return dbuf[:n], err
+}
+
+// NewDecoder returns a new quoted-printable stream decoder.
+func NewDecoder(r io.Reader) io.Reader {
+	return &qpReader{br: bufio.NewReader(r)}
+}
+
+type qpReader struct {
+	br   *bufio.Reader
+	line []byte
+	eof  bool
+	err  error
+}
+
+func (q *qpReader) Read(p []byte) (int, error) {
+	n := 0
+	for n < len(p) {
+		if len(q.line) == 0 {
+			if q.err != nil {
+				return n, q.err
+			} else if q.eof {
+				return n, io.EOF
+			}
+
+			q.line, q.err = q.br.ReadSlice('\n')
+			if q.err == io.EOF {
+				q.eof = true
+			} else if q.err != nil {
+				return n, q.err
+			}
+
+			var nn int
+			nn, q.err = Decode(q.line, q.line)
+			q.line = q.line[:nn]
+		}
+
+		nn := copy(p[n:], q.line)
+		n += nn
+		q.line = q.line[nn:]
+	}
+
+	return n, nil
+}
diff --git a/go/src/gopkg.in/alexcesaro/quotedprintable.v2/quotedprintable_test.go b/go/src/gopkg.in/alexcesaro/quotedprintable.v2/quotedprintable_test.go
new file mode 100644
index 0000000..bb5a710
--- /dev/null
+++ b/go/src/gopkg.in/alexcesaro/quotedprintable.v2/quotedprintable_test.go
@@ -0,0 +1,313 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package quotedprintable
+
+import (
+	"bufio"
+	"bytes"
+	"errors"
+	"flag"
+	"fmt"
+	"io"
+	"os"
+	"os/exec"
+	"regexp"
+	"sort"
+	"strings"
+	"testing"
+	"time"
+)
+
+func ExampleEncodeToString() {
+	data := []byte("¡Hola, señor!")
+	str := EncodeToString(data)
+	fmt.Println(str)
+	// Output:
+	// =C2=A1Hola, se=C3=B1or!
+}
+
+func ExampleDecodeString() {
+	str := "=C2=A1Hola, se=C3=B1or!"
+	data, err := DecodeString(str)
+	if err != nil {
+		fmt.Println("error:", err)
+		return
+	}
+	fmt.Printf("%s\n", data)
+	// Output:
+	// ¡Hola, señor!
+}
+
+func ExampleNewEncoder() {
+	input := []byte("Café")
+	encoder := NewEncoder(os.Stdout)
+	encoder.Write(input)
+	// Output:
+	// Caf=C3=A9
+}
+
+func TestQuotedPrintable(t *testing.T) {
+	tests := []struct {
+		in, want string
+		err      interface{}
+	}{
+		{in: "", want: ""},
+		{in: "foo bar", want: "foo bar"},
+		{in: "foo bar=3D", want: "foo bar="},
+		{in: "foo bar=3d", want: "foo bar="}, // lax.
+		{in: "foo bar=\n", want: "foo bar"},
+		{in: "foo bar\n", want: "foo bar\n"}, // somewhat lax.
+		{in: "foo bar=0", want: "foo bar", err: io.ErrUnexpectedEOF},
+		{in: "foo bar=0D=0A", want: "foo bar\r\n"},
+		{in: " A B        \r\n C ", want: " A B\r\n C"},
+		{in: " A B =\r\n C ", want: " A B  C"},
+		{in: " A B =\n C ", want: " A B  C"}, // lax. treating LF as CRLF
+		{in: "foo=\nbar", want: "foobar"},
+		{in: "foo\x00bar", want: "foo", err: "quotedprintable: invalid unescaped byte 0x00 in quoted-printable body"},
+		{in: "foo bar\xff", want: "foo bar", err: "quotedprintable: invalid unescaped byte 0xff in quoted-printable body"},
+
+		// Equal sign.
+		{in: "=3D30\n", want: "=30\n"},
+		{in: "=00=FF0=\n", want: "\x00\xff0"},
+
+		// Trailing whitespace
+		{in: "foo  \n", want: "foo\n"},
+		{in: "foo  \n\nfoo =\n\nfoo=20\n\n", want: "foo\n\nfoo \nfoo \n\n"},
+
+		// Tests that we allow bare \n and \r through, despite it being strictly
+		// not permitted per RFC 2045, Section 6.7 Page 22 bullet (4).
+		{in: "foo\nbar", want: "foo\nbar"},
+		{in: "foo\rbar", want: "foo\rbar"},
+		{in: "foo\r\nbar", want: "foo\r\nbar"},
+
+		// Different types of soft line-breaks.
+		{in: "foo=\r\nbar", want: "foobar"},
+		{in: "foo=\nbar", want: "foobar"},
+		{in: "foo=\rbar", want: "foo", err: "quotedprintable: invalid quoted-printable hex byte 0x0d"},
+		{in: "foo=\r\r\r \nbar", want: "foo", err: `quotedprintable: invalid bytes after =: "\r\r\r \n"`},
+
+		// Example from RFC 2045:
+		{in: "Now's the time =\n" + "for all folk to come=\n" + " to the aid of their country.",
+			want: "Now's the time for all folk to come to the aid of their country."},
+	}
+	for _, tt := range tests {
+		var buf bytes.Buffer
+		_, err := io.Copy(&buf, NewDecoder(strings.NewReader(tt.in)))
+		if got := buf.String(); got != tt.want {
+			t.Errorf("for %q, got %q; want %q", tt.in, got, tt.want)
+		}
+		switch verr := tt.err.(type) {
+		case nil:
+			if err != nil {
+				t.Errorf("for %q, got unexpected error: %v", tt.in, err)
+			}
+		case string:
+			if got := fmt.Sprint(err); got != verr {
+				t.Errorf("for %q, got error %q; want %q", tt.in, got, verr)
+			}
+		case error:
+			if err != verr {
+				t.Errorf("for %q, got error %q; want %q", tt.in, err, verr)
+			}
+		}
+	}
+
+}
+
+func everySequence(base, alpha string, length int, fn func(string)) {
+	if len(base) == length {
+		fn(base)
+		return
+	}
+	for i := 0; i < len(alpha); i++ {
+		everySequence(base+alpha[i:i+1], alpha, length, fn)
+	}
+}
+
+var useQprint = flag.Bool("qprint", false, "Compare against the 'qprint' program.")
+
+var badSoftRx = regexp.MustCompile(`=([^\r\n]+?\n)|([^\r\n]+$)|(\r$)|(\r[^\n]+\n)|( \r\n)`)
+
+func TestQPExhaustive(t *testing.T) {
+	if *useQprint {
+		_, err := exec.LookPath("qprint")
+		if err != nil {
+			t.Fatalf("Error looking for qprint: %v", err)
+		}
+	}
+
+	var buf bytes.Buffer
+	res := make(map[string]int)
+	everySequence("", "0A \r\n=", 6, func(s string) {
+		if strings.HasSuffix(s, "=") || strings.Contains(s, "==") {
+			return
+		}
+		buf.Reset()
+		_, err := io.Copy(&buf, NewDecoder(strings.NewReader(s)))
+		if err != nil {
+			errStr := err.Error()
+			if strings.Contains(errStr, "invalid bytes after =:") {
+				errStr = "invalid bytes after ="
+			}
+			res[errStr]++
+			if strings.Contains(errStr, "invalid quoted-printable hex byte ") {
+				if strings.HasSuffix(errStr, "0x20") && (strings.Contains(s, "=0 ") || strings.Contains(s, "=A ") || strings.Contains(s, "= ")) {
+					return
+				}
+				if strings.HasSuffix(errStr, "0x3d") && (strings.Contains(s, "=0=") || strings.Contains(s, "=A=")) {
+					return
+				}
+				if strings.HasSuffix(errStr, "0x0a") || strings.HasSuffix(errStr, "0x0d") {
+					// bunch of cases; since whitespace at the end of a line before \n is removed.
+					return
+				}
+			}
+			if strings.Contains(errStr, "unexpected EOF") {
+				return
+			}
+			if errStr == "invalid bytes after =" && badSoftRx.MatchString(s) {
+				return
+			}
+			t.Errorf("decode(%q) = %v", s, err)
+			return
+		}
+		if *useQprint {
+			cmd := exec.Command("qprint", "-d")
+			cmd.Stdin = strings.NewReader(s)
+			stderr, err := cmd.StderrPipe()
+			if err != nil {
+				panic(err)
+			}
+			qpres := make(chan interface{}, 2)
+			go func() {
+				br := bufio.NewReader(stderr)
+				s, _ := br.ReadString('\n')
+				if s != "" {
+					qpres <- errors.New(s)
+					if cmd.Process != nil {
+						// It can get stuck on invalid input, like:
+						// echo -n "0000= " | qprint -d
+						cmd.Process.Kill()
+					}
+				}
+			}()
+			go func() {
+				want, err := cmd.Output()
+				if err == nil {
+					qpres <- want
+				}
+			}()
+			select {
+			case got := <-qpres:
+				if want, ok := got.([]byte); ok {
+					if string(want) != buf.String() {
+						t.Errorf("go decode(%q) = %q; qprint = %q", s, want, buf.String())
+					}
+				} else {
+					t.Logf("qprint -d(%q) = %v", s, got)
+				}
+			case <-time.After(5 * time.Second):
+				t.Logf("qprint timeout on %q", s)
+			}
+		}
+		res["OK"]++
+	})
+	var outcomes []string
+	for k, v := range res {
+		outcomes = append(outcomes, fmt.Sprintf("%v: %d", k, v))
+	}
+	sort.Strings(outcomes)
+	got := strings.Join(outcomes, "\n")
+	want := `OK: 21576
+invalid bytes after =: 3397
+quotedprintable: invalid quoted-printable hex byte 0x0a: 1190
+quotedprintable: invalid quoted-printable hex byte 0x0d: 3325
+quotedprintable: invalid quoted-printable hex byte 0x20: 3325
+quotedprintable: invalid quoted-printable hex byte 0x3d: 810
+unexpected EOF: 1502`
+	if got != want {
+		t.Errorf("Got:\n%s\nWant:\n%s", got, want)
+	}
+}
+
+func TestEncodeToString(t *testing.T) {
+	tests := []struct {
+		in, want string
+	}{
+		{in: "", want: ""},
+		{in: "foo bar", want: "foo bar"},
+		{in: "foo bar=", want: "foo bar=3D"},
+		{in: "foo bar\n", want: "foo bar\n"},
+		{in: "foo bar\r\n", want: "foo bar\r\n"},
+		{in: "foo bar ", want: "foo bar=20"},
+		{in: "foo bar  ", want: "foo bar =20"},
+		{in: "foo bar \n", want: "foo bar=20\n"},
+		{in: "foo bar  \n", want: "foo bar =20\n"},
+		{in: "foo bar  \n ", want: "foo bar =20\n=20"},
+		{in: "résumé", want: "r=C3=A9sum=C3=A9"},
+		{in: "\t !\"#$%&'()*+,-./ :;<>?@[\\]^_`{|}~", want: "\t !\"#$%&'()*+,-./ :;<>?@[\\]^_`{|}~"},
+	}
+
+	for _, tt := range tests {
+		got := EncodeToString([]byte(tt.in))
+		if got != tt.want {
+			t.Errorf("EncodeToString(%q), got %q; want %q", tt.in, got, tt.want)
+		}
+	}
+}
+
+type brokenWriter struct {
+	errorByte int
+	*bytes.Buffer
+}
+
+func (w *brokenWriter) Write(p []byte) (int, error) {
+	for i, b := range p {
+		if i == w.errorByte {
+			return i, errors.New("Broken writer error")
+		}
+		w.WriteByte(b)
+	}
+
+	return len(p), nil
+}
+
+func newBrokenWriter(l int) *brokenWriter {
+	return &brokenWriter{l, new(bytes.Buffer)}
+}
+
+func TestEncoder(t *testing.T) {
+	tests := []struct {
+		in, want     string
+		errorByte, n int
+		isError      bool
+	}{
+		{in: "a", want: "", errorByte: 0, n: 0, isError: true},
+		{in: "a", want: "a", errorByte: 1, n: 1, isError: false},
+		{in: "=", want: "=", errorByte: 1, n: 0, isError: true},
+		{in: "=", want: "=3", errorByte: 2, n: 0, isError: true},
+		{in: "=", want: "=3D", errorByte: 3, n: 1, isError: false},
+		{in: "==", want: "=3D", errorByte: 3, n: 1, isError: true},
+		{in: "==", want: "=3D=", errorByte: 4, n: 1, isError: true},
+		{in: "==", want: "=3D=3", errorByte: 5, n: 1, isError: true},
+		{in: " \r\n", want: "=20\r", errorByte: 4, n: 2, isError: true},
+	}
+
+	for _, tt := range tests {
+		w := newBrokenWriter(tt.errorByte)
+		n, err := NewEncoder(w).Write([]byte(tt.in))
+		if tt.isError && (err == nil) {
+			t.Errorf("NewEncoder.Write(%q) with error at byte %d should return an error", tt.in, tt.errorByte)
+		} else if !tt.isError && (err != nil) {
+			t.Errorf("NewEncoder.Write(%q) with error at byte %d should not return an error", tt.in, tt.errorByte)
+		}
+		if w.String() != tt.want {
+			t.Errorf("NewEncoder.Write(%q) with error at byte %d, got %q; want %q", tt.in, tt.errorByte, w.String(), tt.want)
+		}
+		if n != tt.n {
+			t.Errorf("NewEncoder.Write(%q) with error at byte %d, got n=%d; want n=%d", tt.in, tt.errorByte, n, tt.n)
+		}
+	}
+}
diff --git a/go/src/gopkg.in/gomail.v1/LICENSE b/go/src/gopkg.in/gomail.v1/LICENSE
new file mode 100644
index 0000000..5f5c12a
--- /dev/null
+++ b/go/src/gopkg.in/gomail.v1/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Alexandre Cesaro
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/go/src/gopkg.in/gomail.v1/README.google b/go/src/gopkg.in/gomail.v1/README.google
new file mode 100644
index 0000000..46fcc63
--- /dev/null
+++ b/go/src/gopkg.in/gomail.v1/README.google
@@ -0,0 +1,10 @@
+URL: https://github.com/go-gomail/gomail/archive/11b919ab4933936a28fb6aeda5c6523091266f37.zip
+Version: 11b919ab4933936a28fb6aeda5c6523091266f37
+License: The MIT License (MIT)
+License File: LICENSE
+
+Description:
+The best way to send emails in Go.
+
+Local Modifications:
+No modifications.
diff --git a/go/src/gopkg.in/gomail.v1/README.md b/go/src/gopkg.in/gomail.v1/README.md
new file mode 100644
index 0000000..70f7acf
--- /dev/null
+++ b/go/src/gopkg.in/gomail.v1/README.md
@@ -0,0 +1,97 @@
+# Gomail
+
+## Introduction
+
+Gomail is a very simple and powerful package to send emails.
+
+It requires Go 1.3 or newer.
+
+
+## Features
+
+ * Dead-simple API
+ * Highly flexible
+ * Backward compatibility promise
+ * Supports HTML and text templates
+ * Attachments
+ * Embedded images
+ * SSL/TLS support
+ * Automatic encoding of special characters
+ * Well-documented
+ * High test coverage
+
+
+## Documentation
+
+https://godoc.org/gopkg.in/gomail.v1
+
+
+## Download
+
+    go get gopkg.in/gomail.v1
+
+
+## Example
+
+```go
+package main
+
+import (
+	"gopkg.in/gomail.v1"
+)
+
+func main() {
+	msg := gomail.NewMessage()
+	msg.SetHeader("From", "alex@example.com")
+	msg.SetHeader("To", "bob@example.com", "cora@example.com")
+	msg.SetAddressHeader("Cc", "dan@example.com", "Dan")
+	msg.SetHeader("Subject", "Hello!")
+	msg.SetBody("text/html", "Hello <b>Bob</b> and <i>Cora</i>!")
+
+	f, err := gomail.OpenFile("/home/Alex/lolcat.jpg")
+	if err != nil {
+		panic(err)
+	}
+	msg.Attach(f)
+
+	// Send the email to Bob, Cora and Dan
+	mailer := gomail.NewMailer("smtp.example.com", "user", "123456", 587)
+	if err := mailer.Send(msg); err != nil {
+		panic(err)
+	}
+}
+```
+
+
+## FAQ
+
+### x509: certificate signed by unknown authority
+
+If you get this error it means the certificate used by the SMTP server is not
+considered valid by the client running Gomail. As a quick workaround you can
+bypass the verification of the server's certificate chain and host name by using
+`SetTLSConfig`:
+
+    mailer := gomail.NewMailer("smtp.example.com", "user", "123456", 587, gomail.SetTLSConfig(&tls.Config{InsecureSkipVerify: true}))
+
+Note, however, that this is insecure and should not be used in production.
+
+### 504 5.7.4 Unrecognized authentication type
+
+If you get this error, you should try using the LOGIN authentication mechanism:
+
+    addr := "smtp.example.com:587"
+    auth := gomail.LoginAuth("username", "password", "smtp.example.com")
+    mailer := gomail.NewCustomMailer(addr, auth)
+
+See [issue #16](https://github.com/go-gomail/gomail/issues/16).
+
+
+## Contact
+
+You are more than welcome to open issues and send pull requests if you find a
+bug or need a new feature.
+
+You can also ask questions on the [Gomail
+thread](https://groups.google.com/d/topic/golang-nuts/ywPpNlmSt6U/discussion)
+in the Go mailing-list or via Twitter [@alexandrecesaro](https://twitter.com/alexandrecesaro).
diff --git a/go/src/gopkg.in/gomail.v1/export.go b/go/src/gopkg.in/gomail.v1/export.go
new file mode 100644
index 0000000..1f56cd3
--- /dev/null
+++ b/go/src/gopkg.in/gomail.v1/export.go
@@ -0,0 +1,262 @@
+package gomail
+
+import (
+	"bytes"
+	"encoding/base64"
+	"io"
+	"mime/multipart"
+	"net/mail"
+	"time"
+
+	"gopkg.in/alexcesaro/quotedprintable.v2"
+)
+
+// Export converts the message into a net/mail.Message.
+func (msg *Message) Export() *mail.Message {
+	w := newMessageWriter(msg)
+
+	if msg.hasMixedPart() {
+		w.openMultipart("mixed")
+	}
+
+	if msg.hasRelatedPart() {
+		w.openMultipart("related")
+	}
+
+	if msg.hasAlternativePart() {
+		w.openMultipart("alternative")
+	}
+	for _, part := range msg.parts {
+		h := make(map[string][]string)
+		h["Content-Type"] = []string{part.contentType + "; charset=" + msg.charset}
+		h["Content-Transfer-Encoding"] = []string{string(msg.encoding)}
+
+		w.write(h, part.body.Bytes(), msg.encoding)
+	}
+	if msg.hasAlternativePart() {
+		w.closeMultipart()
+	}
+
+	w.addFiles(msg.embedded, false)
+	if msg.hasRelatedPart() {
+		w.closeMultipart()
+	}
+
+	w.addFiles(msg.attachments, true)
+	if msg.hasMixedPart() {
+		w.closeMultipart()
+	}
+
+	return w.export()
+}
+
+func (msg *Message) hasMixedPart() bool {
+	return (len(msg.parts) > 0 && len(msg.attachments) > 0) || len(msg.attachments) > 1
+}
+
+func (msg *Message) hasRelatedPart() bool {
+	return (len(msg.parts) > 0 && len(msg.embedded) > 0) || len(msg.embedded) > 1
+}
+
+func (msg *Message) hasAlternativePart() bool {
+	return len(msg.parts) > 1
+}
+
+// messageWriter helps converting the message into a net/mail.Message
+type messageWriter struct {
+	header     map[string][]string
+	buf        *bytes.Buffer
+	writers    [3]*multipart.Writer
+	partWriter io.Writer
+	depth      uint8
+}
+
+func newMessageWriter(msg *Message) *messageWriter {
+	// We copy the header so Export does not modify the message
+	header := make(map[string][]string, len(msg.header)+2)
+	for k, v := range msg.header {
+		header[k] = v
+	}
+
+	if _, ok := header["Mime-Version"]; !ok {
+		header["Mime-Version"] = []string{"1.0"}
+	}
+	if _, ok := header["Date"]; !ok {
+		header["Date"] = []string{msg.FormatDate(now())}
+	}
+
+	return &messageWriter{header: header, buf: new(bytes.Buffer)}
+}
+
+// Stubbed out for testing.
+var now = time.Now
+
+func (w *messageWriter) openMultipart(mimeType string) {
+	w.writers[w.depth] = multipart.NewWriter(w.buf)
+	contentType := "multipart/" + mimeType + "; boundary=" + w.writers[w.depth].Boundary()
+
+	if w.depth == 0 {
+		w.header["Content-Type"] = []string{contentType}
+	} else {
+		h := make(map[string][]string)
+		h["Content-Type"] = []string{contentType}
+		w.createPart(h)
+	}
+	w.depth++
+}
+
+func (w *messageWriter) createPart(h map[string][]string) {
+	// No need to check the error since the underlying writer is a bytes.Buffer
+	w.partWriter, _ = w.writers[w.depth-1].CreatePart(h)
+}
+
+func (w *messageWriter) closeMultipart() {
+	if w.depth > 0 {
+		w.writers[w.depth-1].Close()
+		w.depth--
+	}
+}
+
+func (w *messageWriter) addFiles(files []*File, isAttachment bool) {
+	for _, f := range files {
+		h := make(map[string][]string)
+		h["Content-Type"] = []string{f.MimeType + "; name=\"" + f.Name + "\""}
+		h["Content-Transfer-Encoding"] = []string{string(Base64)}
+		if isAttachment {
+			h["Content-Disposition"] = []string{"attachment; filename=\"" + f.Name + "\""}
+		} else {
+			h["Content-Disposition"] = []string{"inline; filename=\"" + f.Name + "\""}
+			if f.ContentID != "" {
+				h["Content-ID"] = []string{"<" + f.ContentID + ">"}
+			} else {
+				h["Content-ID"] = []string{"<" + f.Name + ">"}
+			}
+		}
+
+		w.write(h, f.Content, Base64)
+	}
+}
+
+func (w *messageWriter) write(h map[string][]string, body []byte, enc Encoding) {
+	w.writeHeader(h)
+	w.writeBody(body, enc)
+}
+
+func (w *messageWriter) writeHeader(h map[string][]string) {
+	if w.depth == 0 {
+		for field, value := range h {
+			w.header[field] = value
+		}
+	} else {
+		w.createPart(h)
+	}
+}
+
+func (w *messageWriter) writeBody(body []byte, enc Encoding) {
+	var subWriter io.Writer
+	if w.depth == 0 {
+		subWriter = w.buf
+	} else {
+		subWriter = w.partWriter
+	}
+
+	// The errors returned by writers are not checked since these writers cannot
+	// return errors.
+	if enc == Base64 {
+		writer := base64.NewEncoder(base64.StdEncoding, newBase64LineWriter(subWriter))
+		writer.Write(body)
+		writer.Close()
+	} else if enc == Unencoded {
+		subWriter.Write(body)
+	} else {
+		writer := quotedprintable.NewEncoder(newQpLineWriter(subWriter))
+		writer.Write(body)
+	}
+}
+
+func (w *messageWriter) export() *mail.Message {
+	return &mail.Message{Header: w.header, Body: w.buf}
+}
+
+// As required by RFC 2045, 6.7. (page 21) for quoted-printable, and
+// RFC 2045, 6.8. (page 25) for base64.
+const maxLineLen = 76
+
+// base64LineWriter limits text encoded in base64 to 76 characters per line
+type base64LineWriter struct {
+	w       io.Writer
+	lineLen int
+}
+
+func newBase64LineWriter(w io.Writer) *base64LineWriter {
+	return &base64LineWriter{w: w}
+}
+
+func (w *base64LineWriter) Write(p []byte) (int, error) {
+	n := 0
+	for len(p)+w.lineLen > maxLineLen {
+		w.w.Write(p[:maxLineLen-w.lineLen])
+		w.w.Write([]byte("\r\n"))
+		p = p[maxLineLen-w.lineLen:]
+		n += maxLineLen - w.lineLen
+		w.lineLen = 0
+	}
+
+	w.w.Write(p)
+	w.lineLen += len(p)
+
+	return n + len(p), nil
+}
+
+// qpLineWriter limits text encoded in quoted-printable to 76 characters per
+// line
+type qpLineWriter struct {
+	w       io.Writer
+	lineLen int
+}
+
+func newQpLineWriter(w io.Writer) *qpLineWriter {
+	return &qpLineWriter{w: w}
+}
+
+func (w *qpLineWriter) Write(p []byte) (int, error) {
+	n := 0
+	for len(p) > 0 {
+		// If the text is not over the limit, write everything
+		if len(p) < maxLineLen-w.lineLen {
+			w.w.Write(p)
+			w.lineLen += len(p)
+			return n + len(p), nil
+		}
+
+		i := bytes.IndexAny(p[:maxLineLen-w.lineLen+2], "\n")
+		// If there is a newline before the limit, write the end of the line
+		if i != -1 && (i != maxLineLen-w.lineLen+1 || p[i-1] == '\r') {
+			w.w.Write(p[:i+1])
+			p = p[i+1:]
+			n += i + 1
+			w.lineLen = 0
+			continue
+		}
+
+		// Quoted-printable text must not be cut between an equal sign and the
+		// two following characters
+		var toWrite int
+		if maxLineLen-w.lineLen-2 >= 0 && p[maxLineLen-w.lineLen-2] == '=' {
+			toWrite = maxLineLen - w.lineLen - 2
+		} else if p[maxLineLen-w.lineLen-1] == '=' {
+			toWrite = maxLineLen - w.lineLen - 1
+		} else {
+			toWrite = maxLineLen - w.lineLen
+		}
+
+		// Insert the newline where it is needed
+		w.w.Write(p[:toWrite])
+		w.w.Write([]byte("=\r\n"))
+		p = p[toWrite:]
+		n += toWrite
+		w.lineLen = 0
+	}
+
+	return n, nil
+}
diff --git a/go/src/gopkg.in/gomail.v1/gomail.go b/go/src/gopkg.in/gomail.v1/gomail.go
new file mode 100644
index 0000000..734d881
--- /dev/null
+++ b/go/src/gopkg.in/gomail.v1/gomail.go
@@ -0,0 +1,334 @@
+// 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)
+}
diff --git a/go/src/gopkg.in/gomail.v1/gomail_test.go b/go/src/gopkg.in/gomail.v1/gomail_test.go
new file mode 100644
index 0000000..087b7c9
--- /dev/null
+++ b/go/src/gopkg.in/gomail.v1/gomail_test.go
@@ -0,0 +1,639 @@
+package gomail
+
+import (
+	"encoding/base64"
+	"net/smtp"
+	"path/filepath"
+	"regexp"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+)
+
+type message struct {
+	from    string
+	to      []string
+	content string
+}
+
+func TestMessage(t *testing.T) {
+	msg := NewMessage()
+	msg.SetAddressHeader("From", "from@example.com", "Señor From")
+	msg.SetHeader("To", msg.FormatAddress("to@example.com", "Señor To"), "tobis@example.com")
+	msg.SetAddressHeader("Cc", "cc@example.com", "A, B")
+	msg.SetAddressHeader("X-To", "ccbis@example.com", "à, b")
+	msg.SetDateHeader("X-Date", stubNow())
+	msg.SetHeader("X-Date-2", msg.FormatDate(stubNow()))
+	msg.SetHeader("Subject", "¡Hola, señor!")
+	msg.SetHeaders(map[string][]string{
+		"X-Headers": {"Test", "Café"},
+	})
+	msg.SetBody("text/plain", "¡Hola, señor!")
+
+	want := message{
+		from: "from@example.com",
+		to: []string{
+			"to@example.com",
+			"tobis@example.com",
+			"cc@example.com",
+		},
+		content: "From: =?UTF-8?Q?Se=C3=B1or_From?= <from@example.com>\r\n" +
+			"To: =?UTF-8?Q?Se=C3=B1or_To?= <to@example.com>, tobis@example.com\r\n" +
+			"Cc: \"A, B\" <cc@example.com>\r\n" +
+			"X-To: =?UTF-8?B?w6AsIGI=?= <ccbis@example.com>\r\n" +
+			"X-Date: Wed, 25 Jun 2014 17:46:00 +0000\r\n" +
+			"X-Date-2: Wed, 25 Jun 2014 17:46:00 +0000\r\n" +
+			"X-Headers: Test, =?UTF-8?Q?Caf=C3=A9?=\r\n" +
+			"Subject: =?UTF-8?Q?=C2=A1Hola,_se=C3=B1or!?=\r\n" +
+			"Content-Type: text/plain; charset=UTF-8\r\n" +
+			"Content-Transfer-Encoding: quoted-printable\r\n" +
+			"\r\n" +
+			"=C2=A1Hola, se=C3=B1or!",
+	}
+
+	testMessage(t, msg, 0, want)
+}
+
+func TestBodyWriter(t *testing.T) {
+	msg := NewMessage()
+	msg.SetHeader("From", "from@example.com")
+	msg.SetHeader("To", "to@example.com")
+	w := msg.GetBodyWriter("text/plain")
+	w.Write([]byte("Test message"))
+
+	want := message{
+		from: "from@example.com",
+		to:   []string{"to@example.com"},
+		content: "From: from@example.com\r\n" +
+			"To: to@example.com\r\n" +
+			"Content-Type: text/plain; charset=UTF-8\r\n" +
+			"Content-Transfer-Encoding: quoted-printable\r\n" +
+			"\r\n" +
+			"Test message",
+	}
+
+	testMessage(t, msg, 0, want)
+}
+
+func TestCustomMessage(t *testing.T) {
+	msg := NewMessage(SetCharset("ISO-8859-1"), SetEncoding(Base64))
+	msg.SetHeaders(map[string][]string{
+		"From":    {"from@example.com"},
+		"To":      {"to@example.com"},
+		"Subject": {"Café"},
+	})
+	msg.SetBody("text/html", "¡Hola, señor!")
+
+	want := message{
+		from: "from@example.com",
+		to:   []string{"to@example.com"},
+		content: "From: from@example.com\r\n" +
+			"To: to@example.com\r\n" +
+			"Subject: =?ISO-8859-1?B?Q2Fmw6k=?=\r\n" +
+			"Content-Type: text/html; charset=ISO-8859-1\r\n" +
+			"Content-Transfer-Encoding: base64\r\n" +
+			"\r\n" +
+			"wqFIb2xhLCBzZcOxb3Ih",
+	}
+
+	testMessage(t, msg, 0, want)
+}
+
+func TestUnencodedMessage(t *testing.T) {
+	msg := NewMessage(SetEncoding(Unencoded))
+	msg.SetHeaders(map[string][]string{
+		"From":    {"from@example.com"},
+		"To":      {"to@example.com"},
+		"Subject": {"Café"},
+	})
+	msg.SetBody("text/html", "¡Hola, señor!")
+
+	want := message{
+		from: "from@example.com",
+		to:   []string{"to@example.com"},
+		content: "From: from@example.com\r\n" +
+			"To: to@example.com\r\n" +
+			"Subject: =?UTF-8?Q?Caf=C3=A9?=\r\n" +
+			"Content-Type: text/html; charset=UTF-8\r\n" +
+			"Content-Transfer-Encoding: 8bit\r\n" +
+			"\r\n" +
+			"¡Hola, señor!",
+	}
+
+	testMessage(t, msg, 0, want)
+}
+
+func TestRecipients(t *testing.T) {
+	msg := NewMessage()
+	msg.SetHeaders(map[string][]string{
+		"From":    {"from@example.com"},
+		"To":      {"to@example.com"},
+		"Cc":      {"cc@example.com"},
+		"Bcc":     {"bcc1@example.com", "bcc2@example.com"},
+		"Subject": {"Hello!"},
+	})
+	msg.SetBody("text/plain", "Test message")
+
+	want := message{
+		from: "from@example.com",
+		to:   []string{"to@example.com", "cc@example.com"},
+		content: "From: from@example.com\r\n" +
+			"To: to@example.com\r\n" +
+			"Cc: cc@example.com\r\n" +
+			"Subject: Hello!\r\n" +
+			"Content-Type: text/plain; charset=UTF-8\r\n" +
+			"Content-Transfer-Encoding: quoted-printable\r\n" +
+			"\r\n" +
+			"Test message",
+	}
+	wantBcc1 := message{
+		from: "from@example.com",
+		to:   []string{"bcc1@example.com"},
+		content: "From: from@example.com\r\n" +
+			"To: to@example.com\r\n" +
+			"Cc: cc@example.com\r\n" +
+			"Bcc: bcc1@example.com\r\n" +
+			"Subject: Hello!\r\n" +
+			"Content-Type: text/plain; charset=UTF-8\r\n" +
+			"Content-Transfer-Encoding: quoted-printable\r\n" +
+			"\r\n" +
+			"Test message",
+	}
+	wantBcc2 := message{
+		from: "from@example.com",
+		to:   []string{"bcc2@example.com"},
+		content: "From: from@example.com\r\n" +
+			"To: to@example.com\r\n" +
+			"Cc: cc@example.com\r\n" +
+			"Bcc: bcc2@example.com\r\n" +
+			"Subject: Hello!\r\n" +
+			"Content-Type: text/plain; charset=UTF-8\r\n" +
+			"Content-Transfer-Encoding: quoted-printable\r\n" +
+			"\r\n" +
+			"Test message",
+	}
+
+	testMessage(t, msg, 0, want, wantBcc1, wantBcc2)
+}
+
+func TestAlternative(t *testing.T) {
+	msg := NewMessage()
+	msg.SetHeader("From", "from@example.com")
+	msg.SetHeader("To", "to@example.com")
+	msg.SetBody("text/plain", "¡Hola, señor!")
+	msg.AddAlternative("text/html", "¡<b>Hola</b>, <i>señor</i>!</h1>")
+
+	want := message{
+		from: "from@example.com",
+		to:   []string{"to@example.com"},
+		content: "From: from@example.com\r\n" +
+			"To: to@example.com\r\n" +
+			"Content-Type: multipart/alternative; boundary=_BOUNDARY_1_\r\n" +
+			"\r\n" +
+			"--_BOUNDARY_1_\r\n" +
+			"Content-Type: text/plain; charset=UTF-8\r\n" +
+			"Content-Transfer-Encoding: quoted-printable\r\n" +
+			"\r\n" +
+			"=C2=A1Hola, se=C3=B1or!\r\n" +
+			"--_BOUNDARY_1_\r\n" +
+			"Content-Type: text/html; charset=UTF-8\r\n" +
+			"Content-Transfer-Encoding: quoted-printable\r\n" +
+			"\r\n" +
+			"=C2=A1<b>Hola</b>, <i>se=C3=B1or</i>!</h1>\r\n" +
+			"--_BOUNDARY_1_--\r\n",
+	}
+
+	testMessage(t, msg, 1, want)
+}
+
+func TestAttachmentOnly(t *testing.T) {
+	readFile = func(filename string) ([]byte, error) {
+		return []byte("Content of " + filepath.Base(filename)), nil
+	}
+
+	msg := NewMessage()
+	msg.SetHeader("From", "from@example.com")
+	msg.SetHeader("To", "to@example.com")
+	f, err := OpenFile("/tmp/test.pdf")
+	if err != nil {
+		panic(err)
+	}
+	msg.Attach(f)
+
+	want := message{
+		from: "from@example.com",
+		to:   []string{"to@example.com"},
+		content: "From: from@example.com\r\n" +
+			"To: to@example.com\r\n" +
+			"Content-Type: application/pdf; name=\"test.pdf\"\r\n" +
+			"Content-Disposition: attachment; filename=\"test.pdf\"\r\n" +
+			"Content-Transfer-Encoding: base64\r\n" +
+			"\r\n" +
+			base64.StdEncoding.EncodeToString([]byte("Content of test.pdf")),
+	}
+
+	testMessage(t, msg, 0, want)
+}
+
+func TestAttachment(t *testing.T) {
+	msg := NewMessage()
+	msg.SetHeader("From", "from@example.com")
+	msg.SetHeader("To", "to@example.com")
+	msg.SetBody("text/plain", "Test")
+	msg.Attach(CreateFile("test.pdf", []byte("Content")))
+
+	want := message{
+		from: "from@example.com",
+		to:   []string{"to@example.com"},
+		content: "From: from@example.com\r\n" +
+			"To: to@example.com\r\n" +
+			"Content-Type: multipart/mixed; boundary=_BOUNDARY_1_\r\n" +
+			"\r\n" +
+			"--_BOUNDARY_1_\r\n" +
+			"Content-Type: text/plain; charset=UTF-8\r\n" +
+			"Content-Transfer-Encoding: quoted-printable\r\n" +
+			"\r\n" +
+			"Test\r\n" +
+			"--_BOUNDARY_1_\r\n" +
+			"Content-Type: application/pdf; name=\"test.pdf\"\r\n" +
+			"Content-Disposition: attachment; filename=\"test.pdf\"\r\n" +
+			"Content-Transfer-Encoding: base64\r\n" +
+			"\r\n" +
+			base64.StdEncoding.EncodeToString([]byte("Content")) + "\r\n" +
+			"--_BOUNDARY_1_--\r\n",
+	}
+
+	testMessage(t, msg, 1, want)
+}
+
+func TestAttachmentsOnly(t *testing.T) {
+	msg := NewMessage()
+	msg.SetHeader("From", "from@example.com")
+	msg.SetHeader("To", "to@example.com")
+	msg.Attach(CreateFile("test.pdf", []byte("Content 1")))
+	msg.Attach(CreateFile("test.zip", []byte("Content 2")))
+
+	want := message{
+		from: "from@example.com",
+		to:   []string{"to@example.com"},
+		content: "From: from@example.com\r\n" +
+			"To: to@example.com\r\n" +
+			"Content-Type: multipart/mixed; boundary=_BOUNDARY_1_\r\n" +
+			"\r\n" +
+			"--_BOUNDARY_1_\r\n" +
+			"Content-Type: application/pdf; name=\"test.pdf\"\r\n" +
+			"Content-Disposition: attachment; filename=\"test.pdf\"\r\n" +
+			"Content-Transfer-Encoding: base64\r\n" +
+			"\r\n" +
+			base64.StdEncoding.EncodeToString([]byte("Content 1")) + "\r\n" +
+			"--_BOUNDARY_1_\r\n" +
+			"Content-Type: application/zip; name=\"test.zip\"\r\n" +
+			"Content-Disposition: attachment; filename=\"test.zip\"\r\n" +
+			"Content-Transfer-Encoding: base64\r\n" +
+			"\r\n" +
+			base64.StdEncoding.EncodeToString([]byte("Content 2")) + "\r\n" +
+			"--_BOUNDARY_1_--\r\n",
+	}
+
+	testMessage(t, msg, 1, want)
+}
+
+func TestAttachments(t *testing.T) {
+	msg := NewMessage()
+	msg.SetHeader("From", "from@example.com")
+	msg.SetHeader("To", "to@example.com")
+	msg.SetBody("text/plain", "Test")
+	msg.Attach(CreateFile("test.pdf", []byte("Content 1")))
+	msg.Attach(CreateFile("test.zip", []byte("Content 2")))
+
+	want := message{
+		from: "from@example.com",
+		to:   []string{"to@example.com"},
+		content: "From: from@example.com\r\n" +
+			"To: to@example.com\r\n" +
+			"Content-Type: multipart/mixed; boundary=_BOUNDARY_1_\r\n" +
+			"\r\n" +
+			"--_BOUNDARY_1_\r\n" +
+			"Content-Type: text/plain; charset=UTF-8\r\n" +
+			"Content-Transfer-Encoding: quoted-printable\r\n" +
+			"\r\n" +
+			"Test\r\n" +
+			"--_BOUNDARY_1_\r\n" +
+			"Content-Type: application/pdf; name=\"test.pdf\"\r\n" +
+			"Content-Disposition: attachment; filename=\"test.pdf\"\r\n" +
+			"Content-Transfer-Encoding: base64\r\n" +
+			"\r\n" +
+			base64.StdEncoding.EncodeToString([]byte("Content 1")) + "\r\n" +
+			"--_BOUNDARY_1_\r\n" +
+			"Content-Type: application/zip; name=\"test.zip\"\r\n" +
+			"Content-Disposition: attachment; filename=\"test.zip\"\r\n" +
+			"Content-Transfer-Encoding: base64\r\n" +
+			"\r\n" +
+			base64.StdEncoding.EncodeToString([]byte("Content 2")) + "\r\n" +
+			"--_BOUNDARY_1_--\r\n",
+	}
+
+	testMessage(t, msg, 1, want)
+}
+
+func TestEmbedded(t *testing.T) {
+	msg := NewMessage()
+	msg.SetHeader("From", "from@example.com")
+	msg.SetHeader("To", "to@example.com")
+	f := CreateFile("image1.jpg", []byte("Content 1"))
+	f.ContentID = "test-content-id"
+	msg.Embed(f)
+	msg.Embed(CreateFile("image2.jpg", []byte("Content 2")))
+	msg.SetBody("text/plain", "Test")
+
+	want := message{
+		from: "from@example.com",
+		to:   []string{"to@example.com"},
+		content: "From: from@example.com\r\n" +
+			"To: to@example.com\r\n" +
+			"Content-Type: multipart/related; boundary=_BOUNDARY_1_\r\n" +
+			"\r\n" +
+			"--_BOUNDARY_1_\r\n" +
+			"Content-Type: text/plain; charset=UTF-8\r\n" +
+			"Content-Transfer-Encoding: quoted-printable\r\n" +
+			"\r\n" +
+			"Test\r\n" +
+			"--_BOUNDARY_1_\r\n" +
+			"Content-Type: image/jpeg; name=\"image1.jpg\"\r\n" +
+			"Content-Disposition: inline; filename=\"image1.jpg\"\r\n" +
+			"Content-ID: <test-content-id>\r\n" +
+			"Content-Transfer-Encoding: base64\r\n" +
+			"\r\n" +
+			base64.StdEncoding.EncodeToString([]byte("Content 1")) + "\r\n" +
+			"--_BOUNDARY_1_\r\n" +
+			"Content-Type: image/jpeg; name=\"image2.jpg\"\r\n" +
+			"Content-Disposition: inline; filename=\"image2.jpg\"\r\n" +
+			"Content-ID: <image2.jpg>\r\n" +
+			"Content-Transfer-Encoding: base64\r\n" +
+			"\r\n" +
+			base64.StdEncoding.EncodeToString([]byte("Content 2")) + "\r\n" +
+			"--_BOUNDARY_1_--\r\n",
+	}
+
+	testMessage(t, msg, 1, want)
+}
+
+func TestFullMessage(t *testing.T) {
+	msg := NewMessage()
+	msg.SetHeader("From", "from@example.com")
+	msg.SetHeader("To", "to@example.com")
+	msg.SetBody("text/plain", "¡Hola, señor!")
+	msg.AddAlternative("text/html", "¡<b>Hola</b>, <i>señor</i>!</h1>")
+	msg.Attach(CreateFile("test.pdf", []byte("Content 1")))
+	msg.Embed(CreateFile("image.jpg", []byte("Content 2")))
+
+	want := message{
+		from: "from@example.com",
+		to:   []string{"to@example.com"},
+		content: "From: from@example.com\r\n" +
+			"To: to@example.com\r\n" +
+			"Content-Type: multipart/mixed; boundary=_BOUNDARY_1_\r\n" +
+			"\r\n" +
+			"--_BOUNDARY_1_\r\n" +
+			"Content-Type: multipart/related; boundary=_BOUNDARY_2_\r\n" +
+			"\r\n" +
+			"--_BOUNDARY_2_\r\n" +
+			"Content-Type: multipart/alternative; boundary=_BOUNDARY_3_\r\n" +
+			"\r\n" +
+			"--_BOUNDARY_3_\r\n" +
+			"Content-Type: text/plain; charset=UTF-8\r\n" +
+			"Content-Transfer-Encoding: quoted-printable\r\n" +
+			"\r\n" +
+			"=C2=A1Hola, se=C3=B1or!\r\n" +
+			"--_BOUNDARY_3_\r\n" +
+			"Content-Type: text/html; charset=UTF-8\r\n" +
+			"Content-Transfer-Encoding: quoted-printable\r\n" +
+			"\r\n" +
+			"=C2=A1<b>Hola</b>, <i>se=C3=B1or</i>!</h1>\r\n" +
+			"--_BOUNDARY_3_--\r\n" +
+			"\r\n" +
+			"--_BOUNDARY_2_\r\n" +
+			"Content-Type: image/jpeg; name=\"image.jpg\"\r\n" +
+			"Content-Disposition: inline; filename=\"image.jpg\"\r\n" +
+			"Content-ID: <image.jpg>\r\n" +
+			"Content-Transfer-Encoding: base64\r\n" +
+			"\r\n" +
+			base64.StdEncoding.EncodeToString([]byte("Content 2")) + "\r\n" +
+			"--_BOUNDARY_2_--\r\n" +
+			"\r\n" +
+			"--_BOUNDARY_1_\r\n" +
+			"Content-Type: application/pdf; name=\"test.pdf\"\r\n" +
+			"Content-Disposition: attachment; filename=\"test.pdf\"\r\n" +
+			"Content-Transfer-Encoding: base64\r\n" +
+			"\r\n" +
+			base64.StdEncoding.EncodeToString([]byte("Content 1")) + "\r\n" +
+			"--_BOUNDARY_1_--\r\n",
+	}
+
+	testMessage(t, msg, 3, want)
+}
+
+func TestQpLineLength(t *testing.T) {
+	msg := NewMessage()
+	msg.SetHeader("From", "from@example.com")
+	msg.SetHeader("To", "to@example.com")
+	msg.SetBody("text/plain",
+		strings.Repeat("0", 77)+"\r\n"+
+			strings.Repeat("0", 76)+"à\r\n"+
+			strings.Repeat("0", 75)+"à\r\n"+
+			strings.Repeat("0", 74)+"à\r\n"+
+			strings.Repeat("0", 73)+"à\r\n"+
+			strings.Repeat("0", 76)+"\r\n"+
+			strings.Repeat("0", 77)+"\n")
+
+	want := message{
+		from: "from@example.com",
+		to:   []string{"to@example.com"},
+		content: "From: from@example.com\r\n" +
+			"To: to@example.com\r\n" +
+			"Content-Type: text/plain; charset=UTF-8\r\n" +
+			"Content-Transfer-Encoding: quoted-printable\r\n" +
+			"\r\n" +
+			strings.Repeat("0", 76) + "=\r\n0\r\n" +
+			strings.Repeat("0", 76) + "=\r\n=C3=A0\r\n" +
+			strings.Repeat("0", 75) + "=\r\n=C3=A0\r\n" +
+			strings.Repeat("0", 74) + "=\r\n=C3=A0\r\n" +
+			strings.Repeat("0", 73) + "=C3=\r\n=A0\r\n" +
+			strings.Repeat("0", 76) + "\r\n" +
+			strings.Repeat("0", 76) + "=\r\n0\n",
+	}
+
+	testMessage(t, msg, 0, want)
+}
+
+func TestBase64LineLength(t *testing.T) {
+	msg := NewMessage(SetCharset("UTF-8"), SetEncoding(Base64))
+	msg.SetHeader("From", "from@example.com")
+	msg.SetHeader("To", "to@example.com")
+	msg.SetBody("text/plain", strings.Repeat("0", 58))
+
+	want := message{
+		from: "from@example.com",
+		to:   []string{"to@example.com"},
+		content: "From: from@example.com\r\n" +
+			"To: to@example.com\r\n" +
+			"Content-Type: text/plain; charset=UTF-8\r\n" +
+			"Content-Transfer-Encoding: base64\r\n" +
+			"\r\n" +
+			strings.Repeat("MDAw", 19) + "\r\nMA==",
+	}
+
+	testMessage(t, msg, 0, want)
+}
+
+func testMessage(t *testing.T, msg *Message, bCount int, emails ...message) {
+	now = stubNow
+	mailer := NewMailer("host", "username", "password", 587, SetSendMail(stubSendMail(t, bCount, emails...)))
+
+	err := mailer.Send(msg)
+	if err != nil {
+		t.Error(err)
+	}
+}
+
+func stubNow() time.Time {
+	return time.Date(2014, 06, 25, 17, 46, 0, 0, time.UTC)
+}
+
+func stubSendMail(t *testing.T, bCount int, emails ...message) SendMailFunc {
+	i := 0
+	return func(addr string, a smtp.Auth, from string, to []string, msg []byte) error {
+		if i > len(emails) {
+			t.Fatalf("Only %d mails should be sent", len(emails))
+		}
+		want := emails[i]
+
+		if addr != "host:587" {
+			t.Fatalf("Invalid address, got %q, want host:587", addr)
+		}
+
+		if from != want.from {
+			t.Fatalf("Invalid from, got %q, want %q", from, want.from)
+		}
+
+		if len(to) != len(want.to) {
+			t.Fatalf("Invalid recipient count, \ngot %d: %q\nwant %d: %q",
+				len(to), to,
+				len(want.to), want.to,
+			)
+		}
+		for i := range want.to {
+			if to[i] != want.to[i] {
+				t.Fatalf("Invalid recipient, got %q, want %q",
+					to[i], want.to[i],
+				)
+			}
+		}
+
+		got := string(msg)
+		wantMsg := string("Mime-Version: 1.0\r\n" +
+			"Date: Wed, 25 Jun 2014 17:46:00 +0000\r\n" +
+			want.content)
+		if bCount > 0 {
+			boundaries := getBoundaries(t, bCount, got)
+			for i, b := range boundaries {
+				wantMsg = strings.Replace(wantMsg, "_BOUNDARY_"+strconv.Itoa(i+1)+"_", b, -1)
+			}
+		}
+		i++
+
+		compareBodies(t, got, wantMsg)
+
+		return nil
+	}
+}
+
+func compareBodies(t *testing.T, got, want string) {
+	// We cannot do a simple comparison since the ordering of headers' fields
+	// is random.
+	gotLines := strings.Split(got, "\r\n")
+	wantLines := strings.Split(want, "\r\n")
+
+	// We only test for too many lines, missing lines are tested after
+	if len(gotLines) > len(wantLines) {
+		t.Fatalf("Message has too many lines, \ngot %d:\n%s\nwant %d:\n%s", len(gotLines), got, len(wantLines), want)
+	}
+
+	isInHeader := true
+	headerStart := 0
+	for i, line := range wantLines {
+		if line == gotLines[i] {
+			if line == "" {
+				isInHeader = false
+			} else if !isInHeader && len(line) > 2 && line[:2] == "--" {
+				isInHeader = true
+				headerStart = i + 1
+			}
+			continue
+		}
+
+		if !isInHeader {
+			missingLine(t, line, got, want)
+		}
+
+		isMissing := true
+		for j := headerStart; j < len(gotLines); j++ {
+			if gotLines[j] == "" {
+				break
+			}
+			if gotLines[j] == line {
+				isMissing = false
+				break
+			}
+		}
+		if isMissing {
+			missingLine(t, line, got, want)
+		}
+	}
+}
+
+func missingLine(t *testing.T, line, got, want string) {
+	t.Fatalf("Missing line %q\ngot:\n%s\nwant:\n%s", line, got, want)
+}
+
+func getBoundaries(t *testing.T, count int, msg string) []string {
+	if matches := boundaryRegExp.FindAllStringSubmatch(msg, count); matches != nil {
+		boundaries := make([]string, count)
+		for i, match := range matches {
+			boundaries[i] = match[1]
+		}
+		return boundaries
+	}
+
+	t.Fatal("Boundary not found in body")
+	return []string{""}
+}
+
+var boundaryRegExp = regexp.MustCompile("boundary=(\\w+)")
+
+func BenchmarkFull(b *testing.B) {
+	emptyFunc := func(addr string, a smtp.Auth, from string, to []string, msg []byte) error {
+		return nil
+	}
+
+	for n := 0; n < b.N; n++ {
+		msg := NewMessage()
+		msg.SetAddressHeader("From", "from@example.com", "Señor From")
+		msg.SetHeaders(map[string][]string{
+			"To":      {"to@example.com"},
+			"Cc":      {"cc@example.com"},
+			"Bcc":     {"bcc1@example.com", "bcc2@example.com"},
+			"Subject": {"¡Hola, señor!"},
+		})
+		msg.SetBody("text/plain", "¡Hola, señor!")
+		msg.AddAlternative("text/html", "<p>¡Hola, señor!</p>")
+		msg.Attach(CreateFile("benchmark.txt", []byte("Benchmark")))
+		msg.Embed(CreateFile("benchmark.jpg", []byte("Benchmark")))
+
+		mailer := NewMailer("host", "username", "password", 587, SetSendMail(emptyFunc))
+		if err := mailer.Send(msg); err != nil {
+			panic(err)
+		}
+	}
+}
diff --git a/go/src/gopkg.in/gomail.v1/login.go b/go/src/gopkg.in/gomail.v1/login.go
new file mode 100644
index 0000000..ee4b3b4
--- /dev/null
+++ b/go/src/gopkg.in/gomail.v1/login.go
@@ -0,0 +1,54 @@
+package gomail
+
+import (
+	"errors"
+	"fmt"
+	"net/smtp"
+	"strings"
+)
+
+type loginAuth struct {
+	username string
+	password string
+	host     string
+}
+
+// LoginAuth returns an Auth that implements the LOGIN authentication mechanism.
+func LoginAuth(username, password, host string) smtp.Auth {
+	return &loginAuth{username, password, host}
+}
+
+func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
+	if !server.TLS {
+		advertised := false
+		for _, mechanism := range server.Auth {
+			if mechanism == "LOGIN" {
+				advertised = true
+				break
+			}
+		}
+		if !advertised {
+			return "", nil, errors.New("gomail: unencrypted connection")
+		}
+	}
+	if server.Name != a.host {
+		return "", nil, errors.New("gomail: wrong host name")
+	}
+	return "LOGIN", nil, nil
+}
+
+func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
+	if !more {
+		return nil, nil
+	}
+
+	command := strings.ToLower(strings.TrimSuffix(string(fromServer), ":"))
+	switch command {
+	case "username":
+		return []byte(fmt.Sprintf("%s", a.username)), nil
+	case "password":
+		return []byte(fmt.Sprintf("%s", a.password)), nil
+	default:
+		return nil, fmt.Errorf("gomail: unexpected server challenge: %s", command)
+	}
+}
diff --git a/go/src/gopkg.in/gomail.v1/login_test.go b/go/src/gopkg.in/gomail.v1/login_test.go
new file mode 100644
index 0000000..64e1762
--- /dev/null
+++ b/go/src/gopkg.in/gomail.v1/login_test.go
@@ -0,0 +1,66 @@
+package gomail
+
+import (
+	"net/smtp"
+	"testing"
+)
+
+type output struct {
+	proto string
+	data  []string
+	err   error
+}
+
+const (
+	testUser = "user"
+	testPwd  = "pwd"
+)
+
+func TestPlainAuth(t *testing.T) {
+	tests := []struct {
+		serverProtos     []string
+		serverChallenges []string
+		proto            string
+		data             []string
+	}{
+		{
+			serverProtos:     []string{"LOGIN"},
+			serverChallenges: []string{"Username:", "Password:"},
+			proto:            "LOGIN",
+			data:             []string{"", testUser, testPwd},
+		},
+	}
+
+	for _, test := range tests {
+		auth := LoginAuth(testUser, testPwd, testHost)
+		server := &smtp.ServerInfo{
+			Name: testHost,
+			TLS:  true,
+			Auth: test.serverProtos,
+		}
+		proto, toServer, err := auth.Start(server)
+		if err != nil {
+			t.Fatalf("Start error: %v", err)
+		}
+		if proto != test.proto {
+			t.Errorf("Invalid protocol, got %q, want %q", proto, test.proto)
+		}
+
+		i := 0
+		got := string(toServer)
+		if got != test.data[i] {
+			t.Errorf("Invalid response, got %q, want %q", got, test.data[i])
+		}
+		for _, challenge := range test.serverChallenges {
+			toServer, err = auth.Next([]byte(challenge), true)
+			if err != nil {
+				t.Fatalf("Auth error: %v", err)
+			}
+			i++
+			got = string(toServer)
+			if got != test.data[i] {
+				t.Errorf("Invalid response, got %q, want %q", got, test.data[i])
+			}
+		}
+	}
+}
diff --git a/go/src/gopkg.in/gomail.v1/mailer.go b/go/src/gopkg.in/gomail.v1/mailer.go
new file mode 100644
index 0000000..99ab619
--- /dev/null
+++ b/go/src/gopkg.in/gomail.v1/mailer.go
@@ -0,0 +1,205 @@
+package gomail
+
+import (
+	"crypto/tls"
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"net"
+	"net/mail"
+	"net/smtp"
+	"strings"
+)
+
+// A Mailer represents an SMTP server.
+type Mailer struct {
+	addr   string
+	host   string
+	config *tls.Config
+	auth   smtp.Auth
+	send   SendMailFunc
+}
+
+// A MailerSetting can be used in a mailer constructor to configure it.
+type MailerSetting func(m *Mailer)
+
+// SetSendMail allows to set the email-sending function of a mailer.
+//
+// Example:
+//
+//	myFunc := func(addr string, a smtp.Auth, from string, to []string, msg []byte) error {
+//		// Implement your email-sending function similar to smtp.SendMail
+//	}
+//	mailer := gomail.NewMailer("host", "user", "pwd", 465, SetSendMail(myFunc))
+func SetSendMail(s SendMailFunc) MailerSetting {
+	return func(m *Mailer) {
+		m.send = s
+	}
+}
+
+// SetTLSConfig allows to set the TLS configuration used to connect the SMTP
+// server.
+func SetTLSConfig(c *tls.Config) MailerSetting {
+	return func(m *Mailer) {
+		m.config = c
+	}
+}
+
+// A SendMailFunc is a function to send emails with the same signature than
+// smtp.SendMail.
+type SendMailFunc func(addr string, a smtp.Auth, from string, to []string, msg []byte) error
+
+// NewMailer returns a mailer. The given parameters are used to connect to the
+// SMTP server via a PLAIN authentication mechanism.
+func NewMailer(host string, username string, password string, port int, settings ...MailerSetting) *Mailer {
+	return NewCustomMailer(
+		fmt.Sprintf("%s:%d", host, port),
+		smtp.PlainAuth("", username, password, host),
+		settings...,
+	)
+}
+
+// NewCustomMailer creates a mailer with the given authentication mechanism.
+//
+// Example:
+//
+//	gomail.NewCustomMailer("host:587", smtp.CRAMMD5Auth("username", "secret"))
+func NewCustomMailer(addr string, auth smtp.Auth, settings ...MailerSetting) *Mailer {
+	// Error is not handled here to preserve backward compatibility
+	host, port, _ := net.SplitHostPort(addr)
+
+	m := &Mailer{
+		addr: addr,
+		host: host,
+		auth: auth,
+	}
+
+	for _, s := range settings {
+		s(m)
+	}
+
+	if m.config == nil {
+		m.config = &tls.Config{ServerName: host}
+	}
+	if m.send == nil {
+		m.send = m.getSendMailFunc(port == "465")
+	}
+
+	return m
+}
+
+// Send sends the emails to all the recipients of the message.
+func (m *Mailer) Send(msg *Message) error {
+	message := msg.Export()
+
+	from, err := getFrom(message)
+	if err != nil {
+		return err
+	}
+	recipients, bcc, err := getRecipients(message)
+	if err != nil {
+		return err
+	}
+
+	h := flattenHeader(message, "")
+	body, err := ioutil.ReadAll(message.Body)
+	if err != nil {
+		return err
+	}
+
+	mail := append(h, body...)
+	if err := m.send(m.addr, m.auth, from, recipients, mail); err != nil {
+		return err
+	}
+
+	for _, to := range bcc {
+		h = flattenHeader(message, to)
+		mail = append(h, body...)
+		if err := m.send(m.addr, m.auth, from, []string{to}, mail); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func flattenHeader(msg *mail.Message, bcc string) []byte {
+	buf := getBuffer()
+	defer putBuffer(buf)
+
+	for field, value := range msg.Header {
+		if field != "Bcc" {
+			buf.WriteString(field)
+			buf.WriteString(": ")
+			buf.WriteString(strings.Join(value, ", "))
+			buf.WriteString("\r\n")
+		} else if bcc != "" {
+			for _, to := range value {
+				if strings.Contains(to, bcc) {
+					buf.WriteString(field)
+					buf.WriteString(": ")
+					buf.WriteString(to)
+					buf.WriteString("\r\n")
+				}
+			}
+		}
+	}
+	buf.WriteString("\r\n")
+
+	return buf.Bytes()
+}
+
+func getFrom(msg *mail.Message) (string, error) {
+	from := msg.Header.Get("Sender")
+	if from == "" {
+		from = msg.Header.Get("From")
+		if from == "" {
+			return "", errors.New("mailer: invalid message, \"From\" field is absent")
+		}
+	}
+
+	return parseAddress(from)
+}
+
+func getRecipients(msg *mail.Message) (recipients, bcc []string, err error) {
+	for _, field := range []string{"Bcc", "To", "Cc"} {
+		if addresses, ok := msg.Header[field]; ok {
+			for _, addr := range addresses {
+				switch field {
+				case "Bcc":
+					bcc, err = addAdress(bcc, addr)
+				default:
+					recipients, err = addAdress(recipients, addr)
+				}
+				if err != nil {
+					return recipients, bcc, err
+				}
+			}
+		}
+	}
+
+	return recipients, bcc, nil
+}
+
+func addAdress(list []string, addr string) ([]string, error) {
+	addr, err := parseAddress(addr)
+	if err != nil {
+		return list, err
+	}
+	for _, a := range list {
+		if addr == a {
+			return list, nil
+		}
+	}
+
+	return append(list, addr), nil
+}
+
+func parseAddress(field string) (string, error) {
+	a, err := mail.ParseAddress(field)
+	if a == nil {
+		return "", err
+	}
+
+	return a.Address, err
+}
diff --git a/go/src/gopkg.in/gomail.v1/send.go b/go/src/gopkg.in/gomail.v1/send.go
new file mode 100644
index 0000000..77aa6a2
--- /dev/null
+++ b/go/src/gopkg.in/gomail.v1/send.go
@@ -0,0 +1,102 @@
+package gomail
+
+import (
+	"crypto/tls"
+	"io"
+	"net"
+	"net/smtp"
+)
+
+func (m *Mailer) getSendMailFunc(ssl bool) SendMailFunc {
+	return func(addr string, a smtp.Auth, from string, to []string, msg []byte) error {
+		var c smtpClient
+		var err error
+		if ssl {
+			c, err = sslDial(addr, m.host, m.config)
+		} else {
+			c, err = starttlsDial(addr, m.config)
+		}
+		if err != nil {
+			return err
+		}
+		defer c.Close()
+
+		if a != nil {
+			if ok, _ := c.Extension("AUTH"); ok {
+				if err = c.Auth(a); err != nil {
+					return err
+				}
+			}
+		}
+
+		if err = c.Mail(from); err != nil {
+			return err
+		}
+
+		for _, addr := range to {
+			if err = c.Rcpt(addr); err != nil {
+				return err
+			}
+		}
+
+		w, err := c.Data()
+		if err != nil {
+			return err
+		}
+		_, err = w.Write(msg)
+		if err != nil {
+			return err
+		}
+		err = w.Close()
+		if err != nil {
+			return err
+		}
+
+		return c.Quit()
+	}
+}
+
+func sslDial(addr, host string, config *tls.Config) (smtpClient, error) {
+	conn, err := initTLS("tcp", addr, config)
+	if err != nil {
+		return nil, err
+	}
+
+	return newClient(conn, host)
+}
+
+func starttlsDial(addr string, config *tls.Config) (smtpClient, error) {
+	c, err := initSMTP(addr)
+	if err != nil {
+		return c, err
+	}
+
+	if ok, _ := c.Extension("STARTTLS"); ok {
+		return c, c.StartTLS(config)
+	}
+
+	return c, nil
+}
+
+var initSMTP = func(addr string) (smtpClient, error) {
+	return smtp.Dial(addr)
+}
+
+var initTLS = func(network, addr string, config *tls.Config) (*tls.Conn, error) {
+	return tls.Dial(network, addr, config)
+}
+
+var newClient = func(conn net.Conn, host string) (smtpClient, error) {
+	return smtp.NewClient(conn, host)
+}
+
+type smtpClient interface {
+	Extension(string) (bool, string)
+	StartTLS(*tls.Config) error
+	Auth(smtp.Auth) error
+	Mail(string) error
+	Rcpt(string) error
+	Data() (io.WriteCloser, error)
+	Quit() error
+	Close() error
+}
diff --git a/go/src/gopkg.in/gomail.v1/send_test.go b/go/src/gopkg.in/gomail.v1/send_test.go
new file mode 100644
index 0000000..7f074bc
--- /dev/null
+++ b/go/src/gopkg.in/gomail.v1/send_test.go
@@ -0,0 +1,245 @@
+package gomail
+
+import (
+	"crypto/tls"
+	"io"
+	"net"
+	"net/smtp"
+	"testing"
+)
+
+var (
+	testAddr    = "smtp.example.com:587"
+	testSSLAddr = "smtp.example.com:465"
+	testTLSConn = &tls.Conn{}
+	testConfig  = &tls.Config{InsecureSkipVerify: true}
+	testHost    = "smtp.example.com"
+	testAuth    = smtp.PlainAuth("", "user", "pwd", "smtp.example.com")
+	testFrom    = "from@example.com"
+	testTo      = []string{"to1@example.com", "to2@example.com"}
+	testBody    = "Test message"
+)
+
+const wantMsg = "To: to1@example.com, to2@example.com\r\n" +
+	"From: from@example.com\r\n" +
+	"Mime-Version: 1.0\r\n" +
+	"Date: Wed, 25 Jun 2014 17:46:00 +0000\r\n" +
+	"Content-Type: text/plain; charset=UTF-8\r\n" +
+	"Content-Transfer-Encoding: quoted-printable\r\n" +
+	"\r\n" +
+	"Test message"
+
+func TestDefaultSendMail(t *testing.T) {
+	testSendMail(t, testAddr, nil, []string{
+		"Extension STARTTLS",
+		"StartTLS",
+		"Extension AUTH",
+		"Auth",
+		"Mail " + testFrom,
+		"Rcpt " + testTo[0],
+		"Rcpt " + testTo[1],
+		"Data",
+		"Write message",
+		"Close writer",
+		"Quit",
+		"Close",
+	})
+}
+
+func TestSSLSendMail(t *testing.T) {
+	testSendMail(t, testSSLAddr, nil, []string{
+		"Extension AUTH",
+		"Auth",
+		"Mail " + testFrom,
+		"Rcpt " + testTo[0],
+		"Rcpt " + testTo[1],
+		"Data",
+		"Write message",
+		"Close writer",
+		"Quit",
+		"Close",
+	})
+}
+
+func TestTLSConfigSendMail(t *testing.T) {
+	testSendMail(t, testAddr, testConfig, []string{
+		"Extension STARTTLS",
+		"StartTLS",
+		"Extension AUTH",
+		"Auth",
+		"Mail " + testFrom,
+		"Rcpt " + testTo[0],
+		"Rcpt " + testTo[1],
+		"Data",
+		"Write message",
+		"Close writer",
+		"Quit",
+		"Close",
+	})
+}
+
+func TestTLSConfigSSLSendMail(t *testing.T) {
+	testSendMail(t, testSSLAddr, testConfig, []string{
+		"Extension AUTH",
+		"Auth",
+		"Mail " + testFrom,
+		"Rcpt " + testTo[0],
+		"Rcpt " + testTo[1],
+		"Data",
+		"Write message",
+		"Close writer",
+		"Quit",
+		"Close",
+	})
+}
+
+type mockClient struct {
+	t      *testing.T
+	i      int
+	want   []string
+	addr   string
+	auth   smtp.Auth
+	config *tls.Config
+}
+
+func (c *mockClient) Extension(ext string) (bool, string) {
+	c.do("Extension " + ext)
+	return true, ""
+}
+
+func (c *mockClient) StartTLS(config *tls.Config) error {
+	assertConfig(c.t, config, c.config)
+	c.do("StartTLS")
+	return nil
+}
+
+func (c *mockClient) Auth(a smtp.Auth) error {
+	assertAuth(c.t, a, c.auth)
+	c.do("Auth")
+	return nil
+}
+
+func (c *mockClient) Mail(from string) error {
+	c.do("Mail " + from)
+	return nil
+}
+
+func (c *mockClient) Rcpt(to string) error {
+	c.do("Rcpt " + to)
+	return nil
+}
+
+func (c *mockClient) Data() (io.WriteCloser, error) {
+	c.do("Data")
+	return &mockWriter{c: c, want: wantMsg}, nil
+}
+
+func (c *mockClient) Quit() error {
+	c.do("Quit")
+	return nil
+}
+
+func (c *mockClient) Close() error {
+	c.do("Close")
+	return nil
+}
+
+func (c *mockClient) do(cmd string) {
+	if c.i >= len(c.want) {
+		c.t.Fatalf("Invalid command %q", cmd)
+	}
+
+	if cmd != c.want[c.i] {
+		c.t.Fatalf("Invalid command, got %q, want %q", cmd, c.want[c.i])
+	}
+	c.i++
+}
+
+type mockWriter struct {
+	want string
+	c    *mockClient
+}
+
+func (w *mockWriter) Write(p []byte) (int, error) {
+	w.c.do("Write message")
+	compareBodies(w.c.t, string(p), w.want)
+	return len(p), nil
+}
+
+func (w *mockWriter) Close() error {
+	w.c.do("Close writer")
+	return nil
+}
+
+func testSendMail(t *testing.T, addr string, config *tls.Config, want []string) {
+	testClient := &mockClient{
+		t:      t,
+		want:   want,
+		addr:   addr,
+		auth:   testAuth,
+		config: config,
+	}
+
+	initSMTP = func(addr string) (smtpClient, error) {
+		assertAddr(t, addr, testClient.addr)
+		return testClient, nil
+	}
+
+	initTLS = func(network, addr string, config *tls.Config) (*tls.Conn, error) {
+		if network != "tcp" {
+			t.Errorf("Invalid network, got %q, want tcp", network)
+		}
+		assertAddr(t, addr, testClient.addr)
+		assertConfig(t, config, testClient.config)
+		return testTLSConn, nil
+	}
+
+	newClient = func(conn net.Conn, host string) (smtpClient, error) {
+		if conn != testTLSConn {
+			t.Error("Invalid TLS connection used")
+		}
+		if host != testHost {
+			t.Errorf("Invalid host, got %q, want %q", host, testHost)
+		}
+		return testClient, nil
+	}
+
+	msg := NewMessage()
+	msg.SetHeader("From", testFrom)
+	msg.SetHeader("To", testTo...)
+	msg.SetBody("text/plain", testBody)
+
+	var settings []MailerSetting
+	if config != nil {
+		settings = []MailerSetting{SetTLSConfig(config)}
+	}
+
+	mailer := NewCustomMailer(addr, testAuth, settings...)
+	if err := mailer.Send(msg); err != nil {
+		t.Error(err)
+	}
+}
+
+func assertAuth(t *testing.T, got, want smtp.Auth) {
+	if got != want {
+		t.Errorf("Invalid auth, got %#v, want %#v", got, want)
+	}
+}
+
+func assertAddr(t *testing.T, got, want string) {
+	if got != want {
+		t.Errorf("Invalid addr, got %q, want %q", got, want)
+	}
+}
+
+func assertConfig(t *testing.T, got, want *tls.Config) {
+	if want == nil {
+		want = &tls.Config{ServerName: testHost}
+	}
+	if got.ServerName != want.ServerName {
+		t.Errorf("Invalid field ServerName in config, got %q, want %q", got.ServerName, want.ServerName)
+	}
+	if got.InsecureSkipVerify != want.InsecureSkipVerify {
+		t.Errorf("Invalid field InsecureSkipVerify in config, got %v, want %v", got.InsecureSkipVerify, want.InsecureSkipVerify)
+	}
+}