Last active
May 22, 2021 23:31
-
-
Save arouene/93f685a21b3af0e3d091bb4d8636b1ec to your computer and use it in GitHub Desktop.
Send emails using SMTP with TLS support in Go using standard libraries only
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
// Connecting to a SMTP server to send an email in Go | |
// - Support TLS and optional password | |
// - A CA file can be provided or it will use the CA bundle of your OS | |
// | |
// Usage: | |
// s := smtp.NewSMTP("localhost", "[email protected]", "[email protected]") | |
// s.SetPort(465) | |
// s.SetTLS(true) | |
// s.SetUser("login/user") | |
// s.SetPassword("password") | |
// s.SetCAFile("/path/to/cafile") | |
// s.Send("Subject", "Body) | |
// | |
// Additionnal recipents can be added with | |
// | |
// s.AddRecv("[email protected]") | |
// | |
package smtp | |
import ( | |
"bytes" | |
"crypto/tls" | |
"crypto/x509" | |
"fmt" | |
"io/ioutil" | |
"log" | |
"net" | |
"net/mail" | |
"net/smtp" | |
"os" | |
"strconv" | |
"strings" | |
"time" | |
) | |
type SMTP struct { | |
from *mail.Address | |
to []*mail.Address | |
smtpHost string | |
smtpPort string | |
smtpTLS bool | |
smtpUser string | |
smtpPassword string | |
caFile string | |
} | |
//func NewSMTP(from string, to []string, smtpHost string) SMTP { | |
func NewSMTP(host, from string, rcpt []string) *SMTP { | |
s := SMTP{ | |
smtpPort: "25", | |
smtpTLS: true, | |
} | |
s.SetHost(host) | |
s.SetFrom(from) | |
for _, m := range rcpt { | |
s.AddRecv(m) | |
} | |
return &s | |
} | |
func (s *SMTP) SetFrom(address string) *SMTP { | |
addr, err := mail.ParseAddress(address) | |
if err != nil { | |
log.Fatalf("%s is not a valid email address", address) | |
} | |
s.from = addr | |
return s | |
} | |
// AddRecv allow to specify multiple recipents | |
// | |
// s.AddRecv("[email protected]") | |
// s.AddRecv("[email protected]") | |
// | |
func (s *SMTP) AddRecv(address string) *SMTP { | |
addr, err := mail.ParseAddress(address) | |
if err != nil { | |
log.Fatalf("%s is not a valid email address", address) | |
} | |
s.to = append(s.to, addr) | |
return s | |
} | |
// SetHost set the smtp host "smtp.example.org" | |
func (s *SMTP) SetHost(h string) *SMTP { | |
s.smtpHost = h | |
return s | |
} | |
// SetPort set the smtp port : 25, 465, 857, ... | |
// 25 is the default port if not specified | |
func (s *SMTP) SetPort(port int64) *SMTP { | |
if port < 1 || port > 65535 { | |
log.Fatalf("Port must be between 1 and 65535, got %d\n", port) | |
} | |
s.smtpPort = strconv.FormatInt(port, 10) | |
return s | |
} | |
// SetTLS set the connection mode, true for TLS or false without. | |
// smtpTLS is true by default, set false to disable | |
func (s *SMTP) SetTLS(b bool) *SMTP { | |
s.smtpTLS = b | |
return s | |
} | |
func (s *SMTP) SetUser(user string) *SMTP { | |
s.smtpUser = user | |
return s | |
} | |
func (s *SMTP) SetPassword(password string) *SMTP { | |
s.smtpPassword = password | |
return s | |
} | |
func (s *SMTP) SetCAFile(file string) *SMTP { | |
if _, err := os.Stat(file); os.IsNotExist(err) { | |
log.Fatalf("CAFile %s does not exists\n", file) | |
} | |
s.caFile = file | |
return s | |
} | |
func (s *SMTP) Send(subj, body string) error { | |
// Panic if mandatory parameters are not set | |
if s.from.Address == "" || len(s.to) <= 0 || s.smtpHost == "" { | |
log.Panic("SMTP needs a smtpHost, a from and a recv parameter") | |
} | |
// Setup headers | |
headers := make(map[string]string) | |
headers["From"] = s.from.String() | |
headers["To"] = AddressesToString(s.to) | |
headers["Subject"] = subj | |
// Setup message | |
var message bytes.Buffer | |
for k, v := range headers { | |
message.WriteString(fmt.Sprintf("%s: %s\r\n", k, v)) | |
} | |
message.WriteString("\r\n" + body) | |
var err error | |
var conn net.Conn | |
var smtpConn *smtp.Client | |
var addr = s.smtpHost + ":" + s.smtpPort | |
var timeout = time.Second * 10 | |
// Establish a connection with the smtp server | |
if conn, err = net.DialTimeout("tcp", addr, timeout); err != nil { | |
return err | |
} | |
// Support of TLS if enabled | |
if s.smtpTLS { | |
var rootCAs *x509.CertPool | |
// Custom CA file if set | |
if s.caFile != "" { | |
rootCAs = x509.NewCertPool() | |
pemBytes, err := ioutil.ReadFile(s.caFile) | |
if err != nil { | |
return err | |
} | |
ok := rootCAs.AppendCertsFromPEM(pemBytes) | |
if !ok { | |
return err | |
} | |
} | |
// TLS config | |
tlsconfig := &tls.Config{ | |
InsecureSkipVerify: false, | |
ServerName: s.smtpHost, | |
RootCAs: rootCAs, | |
} | |
tlsConn := tls.Client(conn, tlsconfig) | |
if err = tlsConn.Handshake(); err != nil { | |
return err | |
} | |
if smtpConn, err = smtp.NewClient(tlsConn, s.smtpHost); err != nil { | |
return err | |
} | |
} else { | |
if smtpConn, err = smtp.NewClient(conn, s.smtpHost); err != nil { | |
return err | |
} | |
} | |
defer smtpConn.Close() | |
// Auth | |
if s.smtpUser != "" && s.smtpPassword != "" { | |
auth := smtp.PlainAuth("", s.smtpUser, s.smtpPassword, s.smtpHost) | |
if err = smtpConn.Auth(auth); err != nil { | |
return err | |
} | |
} | |
// From | |
if err = smtpConn.Mail(s.from.Address); err != nil { | |
return err | |
} | |
// To | |
for _, addr := range s.to { | |
if err = smtpConn.Rcpt(addr.Address); err != nil { | |
log.Printf("Error while setting the RCPT: %s\n", err.Error()) | |
} | |
} | |
// Data | |
w, err := smtpConn.Data() | |
if err != nil { | |
return err | |
} | |
// Effectively send the message | |
_, err = w.Write(message.Bytes()) | |
if err != nil { | |
return err | |
} else { | |
log.Println("Mail sent successfully") | |
} | |
err = w.Close() | |
if err != nil { | |
log.Println(err.Error()) | |
} | |
smtpConn.Quit() | |
return nil | |
} | |
func AddressesToString(addresses []*mail.Address) string { | |
var l []string | |
for _, addr := range addresses { | |
l = append(l, addr.String()) | |
} | |
return strings.Join(l, ", ") | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment