Skip to content

Instantly share code, notes, and snippets.

@Loupax
Last active September 20, 2019 08:24
Show Gist options
  • Save Loupax/7bf90b6fe9c1c0dd72a543aa6ac58398 to your computer and use it in GitHub Desktop.
Save Loupax/7bf90b6fe9c1c0dd72a543aa6ac58398 to your computer and use it in GitHub Desktop.
package mailer
import (
"bytes"
"crypto/tls"
"encoding/base64"
"fmt"
"io"
"net/http"
"net/mail"
"net/smtp"
"strings"
)
type MailerConfig struct {
Hostname string
User string
Password string
Port int
}
type Mailer struct {
config MailerConfig
auth smtp.Auth
}
type MessageBody struct {
Content string
ContentType string
}
type Message struct {
Subject string
HTMLContent string
Attachments map[string]io.Reader
}
func NewMailer(config config.Mailer) Mailer {
return Mailer{
config: config,
auth: smtp.PlainAuth("", config.User, config.Password, config.Hostname),
}
}
func (m Mailer) Send(from mail.Address, to []mail.Address, cc []mail.Address, bcc []mail.Address, message Message) error {
client, dialErr := smtp.Dial(fmt.Sprintf("%s:%d", m.config.Hostname, m.config.Port))
if dialErr != nil {
return dialErr
}
defer client.Close()
tlsErr := client.StartTLS(&tls.Config{ServerName: fmt.Sprintf("%s:%d", m.config.Hostname, m.config.Port), InsecureSkipVerify: true})
if tlsErr != nil {
return tlsErr
}
if m.config.User != "" {
if err := client.Auth(m.auth); err != nil {
return err
}
}
if err := client.Mail(from.Address); err != nil {
return err
}
var recepients []mail.Address
recepients = append(recepients, to...)
recepients = append(recepients, cc...)
recepients = append(recepients, bcc...)
for _, rcpt := range recepients {
if err := client.Rcpt(rcpt.Address); err != nil {
return err
}
}
writer, wErr := client.Data()
if wErr != nil {
return wErr
}
lines := []string{
fmt.Sprintf(
"From: %s\r\n"+
"To: %s\r\n",
from.String(),
strings.Join(addrToStringSlice(to), ";"),
),
}
if len(cc) > 0 {
lines = append(lines, fmt.Sprintf("Cc: %s\r\n", strings.Join(addrToStringSlice(cc), ";")))
}
lines = append(lines, fmt.Sprintf("Subject: %s\r\nMIME-Version: 1.0\r\n", message.Subject))
boundary := "**=omgkaitrialol_5d83432910261_35f93fdd8157706c5341c5"
// Append Content
lines = append(
lines,
fmt.Sprintf(
"Content-Type: multipart/mixed; boundary=\"%s\"\r\n"+
"\r\n--%s\r\n"+
"Content-Type: %s; charset=\"utf-8\"\r\n"+
"Content-Transfer-Encoding: 7bit\r\n"+
"\r\n%s\r\n",
boundary,
boundary,
"text/html",
message.HTMLContent,
),
)
for _, line := range lines {
_, err := writer.Write([]byte(line))
if err != nil {
return err
}
}
lines = nil
// Append attachments
for filename, attachment := range message.Attachments {
err := sendAttachment(filename, boundary, attachment, writer)
if err != nil {
return err
}
}
if err := client.Quit(); err != nil {
return err
}
return nil
}
func sendAttachment(filename string, boundary string, attachment io.Reader, writer io.Writer) error {
var attachmentCopy bytes.Buffer
mimeDetectionReader := io.TeeReader(attachment, &attachmentCopy)
mimePart := make([]byte, 512)
io.ReadFull(mimeDetectionReader, mimePart)
_, err := writer.Write([]byte(fmt.Sprintf(
"\r\n--%s\r\n"+
"Content-Type: %s; charset=\"utf-8\"\r\n"+
"Content-Transfer-Encoding: base64\r\n"+
"Content-Disposition: attachment; filename=\"%s\"\r\n\r\n",
boundary,
http.DetectContentType(mimePart),
filename,
)))
if err != nil {
return err
}
encoder := base64.NewEncoder(base64.StdEncoding, writer)
defer encoder.Close()
_, cpErr := io.Copy(encoder, &attachmentCopy)
if cpErr != nil {
return cpErr
}
return nil
}
func addrToStringSlice(addresses []mail.Address) []string {
out := make([]string, len(addresses))
for i, addr := range addresses {
out[i] = addr.String()
}
return out
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment