iTranslated by AI
Simple Email Sending in Go
Just a small tip again today.
You can set up simple email sending using the standard net/smtp package in Go. It's really straightforward, so let's start with the code right away.
package main
import (
"fmt"
"net/smtp"
"os"
"strings"
)
var (
hostname = "mail.example.com"
port = 587
username = "user@example.com"
password = "password"
)
func main() {
from := "gopher@example.net"
recipients := []string{"foo@example.com", "bar@example.com"}
subject := "hello"
body := "Hello World!\nHello Gopher!"
auth := smtp.CRAMMD5Auth(username, password)
msg := []byte(strings.ReplaceAll(fmt.Sprintf("To: %s\nSubject: %s\n\n%s", strings.Join(recipients, ","), subject, body), "\n", "\r\n"))
if err := smtp.SendMail(fmt.Sprintf("%s:%d", hostname, port), auth, from, recipients, msg); err != nil {
fmt.Fprintln(os.Stderr, err)
}
}
See? Easy, right? If you want to use plain text authentication for the SMTP authentication part, you can just replace the smtp.CRAMMD5Auth() function with
auth := smtp.PlainAuth("", username, password, hostname)
and you're good to go. See? Simple as that.
However, the net/smtp package is strictly specialized for email transmission (protocol). Consequently:
- If the content includes character sets other than IRV (formerly US-ASCII), you must manually add the
Content-Typefield and specify thecharset. - If you use character sets other than IRV in fields such as
From,To,Cc,Bcc, andSubject, you must encode them according to RFC 2047. - If you send multipart emails, you need to manually add the
Content-Typefield, specify aboundary, and assemble the body using the mime/multipart package or similar (multipart control is required for sending HTML emails or file attachments).
As you can see, things quickly become tedious when you try to do something even slightly sophisticated.
In other words, if you are sending simple messages in IRV, using only the net/smtp package is perfectly fine. For instance, when building a batch process in Go, you can incorporate the code above to send a quick email if the process doesn't terminate normally for some reason. On Linux, tools like msmtp exist, but if you bundle everything, including authentication info, into a single binary with Go, it becomes quite easy to handle[1].
By the way, the initial code can also be written like this using the same net/smtp package:
package main
import (
"fmt"
"io"
"net/smtp"
"os"
"strings"
)
var (
hostname = "mail.example.com"
port = 587
username = "user@example.com"
password = "password"
)
func main() {
from := "gopher@example.net"
recipients := []string{"foo@example.com", "bar@example.com"}
subject := "hello"
body := "Hello World!\nHello Gopher!"
client, err := smtp.Dial(fmt.Sprintf("%s:%d", hostname, port))
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
defer client.Close()
if err := client.Auth(smtp.CRAMMD5Auth(username, password)); err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
if err := client.Mail(from); err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
for _, addr := range recipients {
if err := client.Rcpt(addr); err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
}
if err := func() error {
w, err := client.Data()
if err != nil {
return err
}
defer w.Close()
msg := strings.ReplaceAll(fmt.Sprintf("To: %s\nSubject: %s\n\n%s", strings.Join(recipients, ","), subject, body), "\n", "\r\n")
if _, err := io.WriteString(w, msg); err != nil {
return err
}
return nil
}(); err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
if err := client.Quit(); err != nil {
fmt.Fprintln(os.Stderr, err)
}
}
In this code, we end the process all at once by calling Quit(), but it seems that you can also control the protocol in more detail.
References
-
Note that embedding authentication credentials in your code means the code itself must be kept secure. Email services are frequently used as springboards for phishing. Usually, credentials should be separated from the code and placed in a secure area with restricted access along with other sensitive information. ↩︎
Discussion