GolangでSMTPを使ってメールを送る処理を書く

最近Golangでメール送信処理を書くことがあったのだけど、あまり事情を知らなかったのでまとめた。

golang+SMTPでメールを送る

Goには標準ライブラリでnet/smtpというのがある。

smtp package - net/smtp - Go Packages

これは名前の通りGoからSMTPでメールを送信するためのライブラリなのだけど、例えばヘッダとボディの間には空行を一行自分で挟まないといけないとか、素朴すぎて結構辛い。 さすがに2023年にもなってさすがにそういうことはやりたくないので、もう少しいいやつないかなと探して、今回は以下のライブラリを使った。

github.com

これはそこそこ高機能だと思う。少なくとも自分でヘッダ部とボディの間に空行を入れる、みたいなことをしなくてもいい。middlewareを差し込めるようになっていて、middlewareによって挙動を少し変える、みたいな最近ぽいこともできるのもよい(そういう場面がどれくらいあるかは置いといて)。あと最近もメンテされているという点もよい。

SMTPでメール送るコードはこういう感じ。

package main

import (
    "log"
    "mime"
    "os"
    "strconv"

    "github.com/wneessen/go-mail"
)

func main() {
    msg := mail.NewMsg()
    host := os.Getenv("SMTP_HOST")
    if host == "" {
        log.Fatal("SMTP_HOST required")
    }

    port, err := strconv.Atoi(os.Getenv("SMTP_PORT"))
    if err != nil {
        log.Fatal(err)
    }
    if err := msg.From("hoge@example.test"); err != nil {
        log.Fatal(err)
    }
    if err := msg.To("fuga@example.test"); err != nil {
        log.Fatal(err)
    }

    msg.Subject(mime.BEncoding.Encode("UTF-8", "こんにちはこんにちは"))
    msg.SetBodyString(mail.TypeTextPlain, "ようこそこんにちは")

    c, err := mail.NewClient(host, mail.WithPort(port))
    if err != nil {
        log.Fatal(err)
    }
    if err := c.DialAndSend(msg); err != nil {
        log.Fatal(err)
    }
}

こういう感じで動く。Subjectはmime.BEncodingしたりする必要がある。あとSMTPのホストとかポートも環境変数で渡せるようにしておくほうが使いやすいけど、この辺はそれぞれの事情による。

送信されるメールを確認する

メールを送信するコードを書いたり、送信用のメールのテンプレートを追加したような時に、見た目を確認するために実際に自分なりクローズドな何かにメールを送る、というのをやったことがある人は結構いると思う。こういう作業でミスしないように慎重に送信テストするぞ・・・とか言ってストレスMAXになったりしたことありませんか。僕はある。ローカルの環境にメールサーバーを立てておいて、そこに送って確認したらまあいいのだけど、そんな面倒なことはしたくない・・・という人におすすめなのがMailhogというテスト用のSMTPサーバーを立てる方法。

github.com

これを自前でビルドするとかではなくて、DockerHubに既にイメージが存在するのでそれを使う。

registry.hub.docker.com

これがいいのはWebUIがついている点で、これで立てられたテスト用のSMTPサーバーにメールを送ると、送られたメールをそのWebUIで確認できる。 開発時にはdocker composeでアプリケーションと同時に立てると、アプリケーションのメール送信処理をテストする仕組みを簡単につくることができる。 docker-compose.ymlの該当部分はこういう感じになる。

version: '3.8'
services:
  app:
    build:
      context: .
    command: [  ] # 任意のコマンド
    env:
      SMTP_HOST: 'mail'
      SMTP_PORT: '1025'
  mail:
    image: mailhog/mailhog:latest
    ports:
      - "8025:8025"
      - "1025:1025"

mailhogはデフォルトだとSMTPサーバーは1025番で、WebUIは8025番で受けているので、必要であればポートフォワードするとよい。 前述のメール送信のコードにも書いたけど、アプリケーションにはSMTP_HOSTSMTP_PORT環境変数で渡すようにしておけば、ローカル開発ではmailコンテナの1025番にメールを送信して、本番環境では任意のホスト/ポートを設定する、というようにできる。 あとはメールを送信する仕組みを実際に動かして、WebUIで確認したらよい。個人的には、本当に送信されることがないよう、RFC2606で予約済みのTLDである.testみたいなドメインにテストメールを送るようにしている(これが正しい使い方かは微妙なラインかもしれない)

https://tex2e.github.io/rfc-translater/html/rfc2606.html

まとめ

近年だとメールを送ることあまりないだろうし、あってもAWS SNSみたいなマネージドサービスを使ってSDK経由でメール送信する、みたいなことの方が多いと思うけど、もしもSMTP経由でメール送信することになったら参考にしてほしい。あとMailhogは便利。