Last active
January 26, 2025 01:24
-
-
Save douglasmakey/90753ecf37ac10c25873825097f46300 to your computer and use it in GitHub Desktop.
Golang - send an email with attachments.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bytes" | |
"encoding/base64" | |
"fmt" | |
"io/ioutil" | |
"mime/multipart" | |
"net/smtp" | |
"os" | |
"path/filepath" | |
) | |
var ( | |
host = os.Getenv("EMAIL_HOST") | |
username = os.Getenv("EMAiL_USERNAME") | |
password = os.Getenv("EMAIL_PASSWORD") | |
portNumber = os.Getenv("EMAIL_PORT") | |
) | |
type Sender struct { | |
auth smtp.Auth | |
} | |
type Message struct { | |
To []string | |
CC []string | |
BCC []string | |
Subject string | |
Body string | |
Attachments map[string][]byte | |
} | |
func New() *Sender { | |
auth := smtp.PlainAuth("", username, password, host) | |
return &Sender{auth} | |
} | |
func (s *Sender) Send(m *Message) error { | |
return smtp.SendMail(fmt.Sprintf("%s:%s", host, portNumber), s.auth, username, m.To, m.ToBytes()) | |
} | |
func NewMessage(s, b string) *Message { | |
return &Message{Subject: s, Body: b, Attachments: make(map[string][]byte)} | |
} | |
func (m *Message) AttachFile(src string) error { | |
b, err := ioutil.ReadFile(src) | |
if err != nil { | |
return err | |
} | |
_, fileName := filepath.Split(src) | |
m.Attachments[fileName] = b | |
return nil | |
} | |
func (m *Message) ToBytes() []byte { | |
buf := bytes.NewBuffer(nil) | |
withAttachments := len(m.Attachments) > 0 | |
buf.WriteString(fmt.Sprintf("Subject: %s\n", m.Subject)) | |
buf.WriteString(fmt.Sprintf("To: %s\n", strings.Join(m.To, ","))) | |
if len(m.CC) > 0 { | |
buf.WriteString(fmt.Sprintf("Cc: %s\n", strings.Join(m.CC, ","))) | |
} | |
if len(m.BCC) > 0 { | |
buf.WriteString(fmt.Sprintf("Bcc: %s\n", strings.Join(m.BCC, ","))) | |
} | |
buf.WriteString("MIME-Version: 1.0\n") | |
writer := multipart.NewWriter(buf) | |
boundary := writer.Boundary() | |
if withAttachments { | |
buf.WriteString(fmt.Sprintf("Content-Type: multipart/mixed; boundary=%s\n", boundary)) | |
buf.WriteString(fmt.Sprintf("--%s\n", boundary)) | |
} else { | |
buf.WriteString("Content-Type: text/plain; charset=utf-8\n") | |
} | |
buf.WriteString(m.Body) | |
if withAttachments { | |
for k, v := range m.Attachments { | |
buf.WriteString(fmt.Sprintf("\n\n--%s\n", boundary)) | |
buf.WriteString(fmt.Sprintf("Content-Type: %s\n", http.DetectContentType(v))) | |
buf.WriteString("Content-Transfer-Encoding: base64\n") | |
buf.WriteString(fmt.Sprintf("Content-Disposition: attachment; filename=%s\n", k)) | |
b := make([]byte, base64.StdEncoding.EncodedLen(len(v))) | |
base64.StdEncoding.Encode(b, v) | |
buf.Write(b) | |
buf.WriteString(fmt.Sprintf("\n--%s", boundary)) | |
} | |
buf.WriteString("--") | |
} | |
return buf.Bytes() | |
} | |
func main() { | |
sender := New() | |
m := NewMessage("Test", "Body message.") | |
m.To = []string{"[email protected]"} | |
m.CC = []string{"[email protected]", "[email protected]"} | |
m.BCC = []string{"[email protected]"} | |
m.AttachFile("/path/to/file") | |
fmt.Println(sender.Send(m)) | |
} |
Just wanna say thank you so much for this
If anybody is passing by, you can use
mime.TypeByExtension(filepath.Ext(filename))
instead of http.DetectContentType(byteSlice)
.
You'll likely have better results as the http function supports less MIME types.
Be aware, though, that the first function (from package mime
) uses the file extension, which means that it can be tricked easily.
On the other hand, it seems that some file types have the same beginning bytes which will confuse the http
function (.docx
, .xls
, etc apparently are mistaken for .zip
, iirc).
Double check the last info but that should give you a good starting point to fix some of the issues people seemed to encounter in the comments above.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
io/ioutils
has deprecated after go 1.16