/var/lib/azumakuniyuki

Sisimaiとか技術的なことはこっちに書いてみようかという試み

SendGridへリレーする方法、MTA3種盛り。

SendGrid Advent Calendarの12日目は、主流なMTA3種類(Sendmail, Postfix, OpenSMTPD)からSMTPでSendGridへリレーする方法の話です。

前書き

ここ数年でちょいちょい「PostfixからSendGridへ移行した」系の記事を見かけるようになり、さくらインターネットでSendGridが使えるようになり、クラウド系のメール配信がより身近になってきたなぁという印象です。

遅延対策としてのリレー先

僕は前職ではメール配信(多くて100万通ぐらい/日)に使われているSendmailの守役をしていたのですが、現職では大量配信はまったく行っていなくて、原則としてサイトを構成するネットワーク内にあるSMTPサーバから配信することにしています。それでも宛先ホストによっては遅延しがちなところもあります。

そこでSendGridです。

f:id:azumakuniyuki:20161212184602p:plain:right 設定を変えて観測して、一旦SendGridのSMTPサーバを経由したほうが遅延が少ないようであれば、そのドメイン宛メールはSendGridに丸投げすることにして、なるべくこちら側のMTAに滞留するキューが少なくなるように運用しています。

SMTPでリレーする

今時のアプリケーションは、最初からSendGridなどクラウド系メール配信サービスを使う前提で開発されていればWeb APIを使った配信機能を持っていることでしょう。しかし、「たまに通知系のメールが飛ぶ」「監視系のアラートがたまに出る」ようにメールの流量が少ないケースでは、アプリケーションが動いているホストの/usr/sbin/sendmailに任せっきりで、遅延が発生してから「メール配信どうしよう」と壁に当たることもあるかと思います。

そんな時はMTAの設定を少し変更してSendGridへリレーするだけで、配信状況が劇的に改善する可能性があります。 この記事は、そんな壁に当たった時に役に立つかもしれない内容です、たぶん。

この記事でやること

いずれのMTAでも、宛先が@gmail.com@aol.comの場合に限り、SendGridへリレーする設定を行います。

  1. SendGridでSMTP認証用アカウントを作る
  2. SendmailでSendGridの25番ポートへリレーする
  3. PostfixでSendGridの587番ポートへリレーする
  4. OpenSMTPDでSendGridの2525番ポートへリレーする

1. SMTP認証用アカウントの作成

ここから本題です。認証を経てSMTPでリレーするためのリレー用アカウントをSendGridで作っておきます。SendGridにログインした左側の[Settings]→[Credentials]から[Add New Credential]って青いボタンを押し、ユーザ名とパスワードを入力して、□MAIL:のところにチェックを入れて[Create Credential]です。

f:id:azumakuniyuki:20161212175625p:plain

以下のユーザ名とパスワードを各MTAの設定で使うことにします。

  • ユーザ名 = sgac-2016-smtp-auth-1
  • パスワード = kijitora-nyan-nyan-22

ちなみに、たとえ例示用パスワードの文字列といえども未知の単語が出てくると「?」と感じる事があると思いますので、軽く言及しておきます。

kijitoraキジトラ雉虎猫の事を指しています。茶色と黒のシマシマ模様な猫で、街中で野良猫として非常によく見かけるネコです。逆にペットショップで売っているネコの中にキジトラは先ず居ないと言っていいでしょう。この模様は野生型とも言われるもので、イエネコの源流と言われるリビアヤマネコと同じ毛色です。

Two stray cats and cherry blossoms | 猫と桜

この猫を「キジトラ」と表現する人はわりとネコの事をよく知っている人で、そうでもない人は「黒っぽい猫」「シマシマの猫」「ヘビっぽい模様の猫」といった表現をしはります。白猫、黒猫、三毛猫と違ってキジトラの呼び方は猫をよく知っている人かどうかのリトマス試験紙として機能しますね。

なお、黒と灰色のシマシマ模様な猫は「サバトラ=(鯖っぽい模様の虎猫)」と言います。

2. Sendmail

f:id:azumakuniyuki:20161212175906g:plain:left

最初はSendmailです。信頼と実績、歴史と伝統のSendmailです。ここ10年かもうちょいを見れば最早MTAとしてSendmailを意図的に選択することは少なくなっていると思いますが、Postfix登場〜隆盛以前の時代はオープンソースのMTA=Sendmailでした。まだSendmailを使っているところもあると思いますし、僕もSendmailが現役で動くホストを(職業的に必要なので)幾つか確保しています。

ちなみに、このSendmailの節は2011年に書いたブログの焼き直しです。今回はMTA3種盛りなので、ササッと進めます。

Sendmailでのリレー動作を確認したのは、つい先日発表されたAWS Lightsailで作ったAmazonLinuxの一番小さいインスタンスです。

/etc/mail/sendmail.cf

authinfoという認証に使用する機能が/etc/mail/sendmail.cfに組み込まれている必要があるので、grepして何も出てこなければ、次のようにsendmail.mcに1行書いて、sendmail.cfを作り直します。

% grep authinfo /etc/mail/sendmail.cf
#####  $Id: authinfo.m4,v 1.9 2002/06/27 23:23:57 gshapiro Exp $  #####
# authinfo list database: contains info for authentication as client
Kauthinfo hash /etc/mail/authinfo
... いろいろ出る

authinfoが組み込まれていれば↑のような何かが出る、何も出なければ↓のようにFEATURE(`authinfo')dnlを書く

FEATURE(`authinfo')dnl ←この行を追加する
FEATURE(`mailertable', `hash -o /etc/mail/mailertable.db')dnl ←最初から入ってることが多いけど無い時はこれも追加で

書いたらmake sendmail.cfを実行する(sendmail.mcからsendmail.cfが生成される)

# cd /etc/mail
# make sendmail.cf

/etc/mail/authinfo

authinfoを組み込んだsendmail.cfが出来上がったら、SendGridへ認証付きでリレーする定義を/etc/mail/authinfoに書きます、次のような感じで。

Authinfo:smtp.sendgrid.net "U:sgac-2016-smtp-auth-1" "P:kijitora-nyan-nyan-22" "M:plain"

リレー時の認証情報が生で書いているのでパーミッションは安全に。

# cd /etc/mail
# /usr/sbin/makemap hash authinfo.db < authinfo
# chown root ./authinfo*
# chmod 0600 ./authinfo*

このファイルはsmtp.sendgrid.netへリレーする時に使う認証情報としてsendmailのプロセスに参照されます。

/etc/mail/mailertable

この設定をしているホストから出る全てのメールをSendGridへリレーすることもできますが、今回の記事は特定の宛先だけSendGridへリレーするので、ここは@gmail.com@aol.com宛メールはSendGridへリレーする設定をします。

特定ドメイン宛のリレーは/etc/mail/mailertableを使います。次のような書式で書くと良いです。

gmail.com          relay:[smtp.sendgrid.net]
aol.com            relay:[smtp.sendgrid.net]
# cd /etc/mail
# /usr/sbin/makemap hash mailertable.db < mailertable
# make mailertable.db ←/etc/mail/Makefileがあればこれでも良い

Sendmailの再起動と送信テスト

authinfoを組み込んだ(sendmail.cfを変更した)のでSendmailを再起動します。それから送信テストです。

# service sendmail restart
Shutting down sm-client:                                   [  OK  ]
Shutting down sendmail:                                    [  OK  ]
Starting sendmail:                                         [  OK  ]
Starting sm-client:                                        [  OK  ]

送信テストは/bin/mailコマンドで。もしmailコマンドがないRed HatLinuxならyum install mailxしてください。

% date | mail -s 'Test from Sendmail via SendGrid' 宛先ローカルパート@gmail.com
# cat /var/log/maillog
Dec 12 01:18:38 sgac2016 sendmail[2607]: uBC1Ic4D002607: from=ec2-user, size=246, class=0, nrcpts=1, msgid=<201612120118.uBC1Ic4D002607@sgac2016.nyaan.jp>, relay=root@localhost
Dec 12 01:18:38 sgac2016 sendmail[2608]: uBC1Ic0h002608: from=<ec2-user@sgac2016.nyaan.jp>, size=513, class=0, nrcpts=1, msgid=<201612120118.uBC1Ic4D002607@sgac2016.nyaan.jp>, proto=ESMTP, daemon=MTA, relay=localhost [127.0.0.1]
Dec 12 01:18:38 sgac2016 sendmail[2607]: uBC1Ic4D002607: to=宛先ローカルパート@gmail.com, ctladdr=ec2-user (500/500), delay=00:00:00, xdelay=00:00:00, mailer=relay, pri=30246, relay=[127.0.0.1] [127.0.0.1], dsn=2.0.0, stat=Sent (uBC1Ic0h002608 Message accepted for delivery)
Dec 12 01:18:38 sgac2016 sendmail[2610]: STARTTLS=client, relay=smtp.sendgrid.net., version=TLSv1/SSLv3, verify=FAIL, cipher=AES128-GCM-SHA256, bits=128/128
Dec 12 01:18:39 sgac2016 sendmail[2610]: uBC1Ic0h002608: to=<宛先ローカルパート@gmail.com>, ctladdr=<ec2-user@sgac2016.nyaan.jp> (500/500), delay=00:00:01, xdelay=00:00:01, mailer=relay, pri=120513, relay=smtp.sendgrid.net. [108.168.190.109], dsn=2.0.0, stat=Sent (Ok: queued as X5tJVpcRR9yPrMm9oYisjA)

ちゃんとSendGridのSMTPサーバに流れていますね、relay=smtp.sendgrid.net. [108.168.190.110]って部分を見ると。 そういえば、この記事書くためのテストで思い出しましたが、STARTTLSのところもちゃんと確認したいとこです。

ササッと進めるとか書きましたがSendmailだけでわりと長くなってしまいました、次へ行きます。

3. Postfix

f:id:azumakuniyuki:20161212180328p:plain:left 今やSMTPサーバを構築するならPostfix一択(というと言い過ぎなところはありますが実際そんな感じ)でしょう。僕もSendmailしかない時代はSendmailでしたが、PostfixがリリースされてすぐにPostfixに乗り換えました、快適でした。ところが次に行った会社は全てSendmailで、しかもたくさんあったので、結局今に至るまでうちのカミさんと言ってもいいぐらいSendmailと付き合ってます。

Postfixでも同様に、@gmail.com@aol.com宛はSendGridのSMTPサーバsmtp.sendgrid.netへ認証を経てリレーします。 この節の内容もSendmailと同じAmazonLinuxで動作テストをして書きました。

/etc/postfix/main.cf

Sendmailmailertable(宛先ドメインごとにリレーする経路を定義する)に相当するPostfixの機能がtransport_mapsです。この定義が/etc/postfix/main.cfに書いていなければなりません。それと、リレー時の認証で使用されるファイルの定義、これも同様に記述します。

# grep -E '(transport_maps|sasl)' /etc/postfix/main.cf
transport_maps = hash:/etc/postfix/transport ←これが書いていればOK
smtp_sasl_auth_enable = yes ←SMTP認証を使う
smtp_sasl_password_maps = hash:/etc/postfix/authinfo ←認証で使うアカウント・パスワードの設定
smtp_sasl_security_options = noanonymous
smtp_tls_security_level = encrypt

/etc/postfix/authinfo

main.cfで認証アカウントを定義するファイルは/etc/postfix/authinfoとしたので、以下のようなファイルを/etc/postfix/authinfoとして保存します。ファイル名はmain.cfで指定したものであればcredentialsでもrelay-accountsでもなんでも良いです。

[smtp.sendgrid.net]:587 sgac2016-smtp-auth-1:kijitora-nyan-nyan-22

authinfoに認証情報を書いたらpostmapコマンドでauthinfo.dbを作って、安全なパーミッションに変更しておきます。

# cd /etc/postfix
# /usr/sbin/postmap authinfo
# chown root ./authinfo*
# chmod 0600 ./authinfo* ←パーミッションは安全に

/etc/postfix/transport

続いて、transport_mapsで定義しているファイル/etc/postfix/transportに下記のようなSendGridへリレーする設定を書きます。

gmail.com          relay:[smtp.sendgrid.net]:587
aol.com            relay:[smtp.sendgrid.net]:587

中身はSendmailmailertableと同じです。なお、smtp.sendgrid.netと書く([``]に入れない)と、smtp.sendgrid.netのMXレコードを引く動作(Sendmailのmailertableも同じ)になります。実際、このホスト名にはMXレコードはないのでAレコードを引いて正しくリレーされるのですが、[smtp.sendgrid.net]と書くほうが確実です。

なお、動作テストでは25番ポート(Sendmailでテストした)でも2525番ポート(OpenSMTPDでテストした)でも正しく送信できる事を確認していますが、公式ドキュメント: Postfixでメール送信 - ドキュメント | SendGridにも記述がある通り、587番ポートを使うと良いでしょう。

Postfixの再起動と送信テスト

main.cfを変更したので、Postfixを再起動してから送信テストをします。

% sudo service postfix restart
Shutting down postfix:                                     [  OK  ]
Starting postfix:                                          [  OK  ]

% date | mail -s 'Test from Postfix via SendGrid' 宛先ローカルパート@gmail.com
% sudo cat /var/log/maillog
Dec 12 04:50:43 sgac2016 postfix/qmgr[9677]: D69EC625E9: from=<ec2-user@sgac2016.nyaan.jp>, size=716, nrcpt=1 (queue active)
Dec 12 04:50:43 sgac2016 sendmail[9680]: uBC4oh1N009680: to=宛先ローカルパート@gmail.com, ctladdr=ec2-user (500/500), delay=00:00:00, xdelay=00:00:00, mailer=relay, pri=30248, relay=[127.0.0.1] [127.0.0.1], dsn=2.0.0, stat=Sent (Ok: queued as D69EC625E9)
Dec 12 04:50:43 sgac2016 postfix/smtpd[9681]: disconnect from localhost[127.0.0.1]
Dec 12 04:50:43 sgac2016 postfix/smtp[9685]: certificate verification failed for smtp.sendgrid.net[108.168.190.109]:587: untrusted issuer /C=US/O=The Go Daddy Group, Inc./OU=Go Daddy Class 2 Certification Authority
Dec 12 04:50:43 sgac2016 postfix/smtp[9685]: D69EC625E9: to=<宛先ローカルパート@gmail.com>, relay=smtp.sendgrid.net[108.168.190.109]:587, delay=0.09, delays=0.05/0.01/0.02/0, dsn=2.0.0, status=sent (250 Ok: queued as guKZiGUJTjKvZcRsOP9OOA)

下から二つ目のログにrelay=smtp.sendgrid.net[108.168.190.109]:587と書いているので、リレー成功です。

4. OpenSMTPD

f:id:azumakuniyuki:20161212180402p:plain:left *BSDが好きな人やメールサーバに関心のある人は知ってはると思います、OpenBSDプロジェクトから誕生した比較的新しいMTAであるOpenSMTPDでリレーする方法です。

OpenBSDプロジェクトから誕生したプロダクトで最も有名なのはおそらくOpenSSHですね。昔は、SSHプロトコルv1の時代は商用製品しかなかったと記憶していますが、今やSSHプロトコルv2の時代、皆さんが使ってはるSSHはサーバもクライアントもOpenSSHであることやと思います。

OpenSMTPDはセキュアである事(OpenBSDプロジェクトですし)・設定が簡単である事・高い信頼性を特徴とするMTAで、現在の最新版は6.02です。この記事を書くために用意した環境は、FreeBSD 10.1-RELEASE-p26で動くOpenSMTPD 6.0.2p1(/opt/smtpdへソースビルドしたやつ)です。

/opt/smtpd/etc/smtpd.conf

SendmailPostfixと同じ要領で、リレー条件の定義と認証情報を書いたファイルを作ります。OpenSMTPDの設定ファイルには下記のような内容を書きます。

table authinfo file:/opt/smtpd/etc/authinfo
accept for domain "gmail.com" relay via "tls+auth://sendgrid@smtp.sendgrid.net:2525" auth <authinfo>
accept for domain "aol.com" relay via "tls+auth://sendgrid@smtp.sendgrid.net:2525" auth <authinfo>

書く場所はaliasesの定義近辺で良いでしょう。上記のtls+auth://sendgrid@の**sendgridの部分はラベルと呼ばれる部分で、後述の認証情報テーブル(authinfo)からOpenSMTPDのプロセスが検索するときのキーとなります。

/opt/smtpd/etc/authinfo

こっちはリレーするときの認証情報です。下記のようにPostfixのそれと同じような形式で書きます。

sendgrid sgac-2016-smtp-auth-1:kijitora-nyan-nyan-22

ファイルを書いたらrootとOpenSMTPDの特権分離ユーザ(_smtpd)だけが読めるパーミッションにしておきます。

# cd /opt/smtpd/etc
# chown root:_smtpd ./authinfo
# chmod 0640 ./authinfo

なお、smtpd.conftable authinfo db:...という書き方もできる(makemap(8)でハッシュを作る)のですが、OpenSMTPD 6.0.2p1ではauthinfo.dbがなぜか作れなかったので、man smtpd.confの設定例にしたがってfile:...としています。

OpenSMTPDの再起動と送信テスト

/etc/rc.d/smtpdなんて便利なものがあればそれでrestartでOKですが、特に用意していないので、昔ながらの泥臭い再起動でやります。

% sudo /opt/smtpd/sbin/smtpctl stop && /opt/smtpd/sbin/smtpd

再起動をしたら同じようにテストメールを送ってログの確認です。

% date | mail -s 'Test from OpenSMTPD via SendGrid' 宛先ローカルパート@gmail.com
% sudo cat /var/log/maillog

Dec 12 16:36:27 neko smtpd[88528]: 1e0b8dc91093c292 mta event=connecting address=tls://167.89.125.25:2525 host=o16789125x25.outbound-mail.sendgrid.net
Dec 12 16:36:27 neko smtpd[88528]: 1e0b8dc91093c292 mta event=connected
Dec 12 16:36:28 neko smtpd[88528]: 1e0b8dc91093c292 mta event=starttls ciphers=version=TLSv1.2, cipher=AES128-GCM-SHA256, bits=128
Dec 12 16:36:29 neko smtpd[88528]: smtp-out: Server certificate verification succeeded on session 1e0b8dc91093c292
Dec 12 16:36:30 neko smtpd[88528]: 1e0b8dc91093c292 mta event=delivery evpid=8c545a4e9a16e5dc from=<cat@neko.nyaan.jp> to=<宛先ローカルパート@gmail.com> rcpt=<-> source=133.242.157.118 relay=167.89.125.25 (o16789125x25.outbound-mail.sendgrid.net) delay=3s result=Ok stat=250 Ok: queued as dfzaGbhcTE2D0abtiIAEFA

長いホスト名.sendgrid.netへリレーしたって書いているので、リレー成功です、めでたし。

まとめ

SendGridの公式ブログ: Web APIかSMTPリレーか: どうやってメールを送ればいいの? | SendGridブログ では「可能であればWeb APIのご利用をお勧めいたします」と書いています。たしかにAPIのほうが細かい制御ができる・SMTPリレーより高速という利点があります。

とはいえ、サイトで動いているアプリケーションに簡単に手を入れられないケースもあるでしょう。そんな時はこの記事で紹介しているようなSMTPリレーの方が簡単です。もちろん、MTAの設定を変更する方が困難ってケースもあると思いますので、それはコストの少ない方を選ぶとか良しなにってところではありますが。

後書き

現職では大量メール送信は全然扱っていなくて、主にバウンスメールを正しく解析する方面SMTPに関わっています。

SendGridが米国で登場してすぐに「メール送信の主流になりそう」ってことでアカウントを作っていたのですが、今回12日目の記事を書くための動作確認とJSONで得られるバウンスオブジェクトを解析するコード実装で、SendGridへログインする必要がでてきました。

ところが、最後にログインして1年以上経過していたので、アカウントが無効な状態になっていました。「さて、どうしたもんやろ」と思いながらTwitterに書いたら、日本法人の中の人にツイートを拾っていただいて、助け舟をだしていただきました。

僕のアカウントは米国で作ったもので、最終的に英語での問い合わせで解決したのですが、それまでの道筋を日本語で教えて頂けてとても助かりました。

いざという時に日本語でどうにかなるというのも嬉しいところですね。