iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
💻

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.

sample1.go
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-Type field and specify the charset.
  • If you use character sets other than IRV in fields such as From, To, Cc, Bcc, and Subject, you must encode them according to RFC 2047.
  • If you send multipart emails, you need to manually add the Content-Type field, specify a boundary, 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:

sample2.go
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

https://github.com/go-mail/mail
https://github.com/ungerik/go-mail
https://twinbird-htn.hatenablog.com/entry/2017/08/02/233000

脚注
  1. 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. ↩︎

GitHubで編集を提案

Discussion