sendmail コマンドによるメール発信 - Postfix Advent Calendar 2014 - ダメ出し Blog

2014-12-13(Sat) [postfix][shell][sh] [更新履歴]

Postfix Advent Calendar 2014 の 13日目の記事です。 現在、12月17日です。毎度遅れてすみません。

Postfix の sendmail コマンドを MUA として用い、メールを発信する方法について紹介します。 Sendmail や qmail など、ほかの MTA 付属の sendmail でも概ね同じです。 シェル芸だけで正しくメールを発信できるようになると、色々と便利ですよ。

以下、コマンドラインなどの文字エンコーディングは UTF-8 と仮定します。

設定

Postfix の設定は通常通りで構いません。 メールを発信するだけの MTA を「null クライアント」などと称しますが、 発信のほかに用途がないのであれば、そのような設定をするのがいいと思います。

null クライアントの設定例は Postfix 付属の文書ファイル STANDARD_CONFIGURATION_README に記載されています。/etc/postfix/main.cf は次のようになります。

myhostname = hostname.example.com
myorigin = $mydomain
relayhost = $mydomain
inet_interfaces = loopback-only
mydestination =

master.cf はデフォルトのままで構いません。

基本的な使い方

基本的には、コマンドライン引数に宛先アドレスを一つ以上指定し、 標準入力にメールメッセージを与えるだけです。 簡単ですね。

$ echo Meowning! |sendmail hotaru@example.ne.jp

上のコマンドラインを hostname.example.com 上のユーザー apache で実行した場合、だいたい次のような感じのメールが届きます。

Return-Path: <apache@example.com>
To: undisclosed-recipients:;
From: apache@example.com (Apache)
Date: Mon, 15 Dec 2014 22:48:41 +0900 (JST)
Message-Id: <20141215134841.D567A1D80AA@hostname.example.com>
Received: …
Received: …

Meowning!

このように、MUA としての sendmail は渡されたメールに対して必要最低限のことしかしてくれません。 せいぜい、From:Date:Message-Id フィールドを補完したり、 Bcc: フィールドを削除してくれるくらいです。 エンコーディングなどは関知せず、そのまま送ります。 このため、ユーザーが適切なコマンドラインオプション、引数(宛先)、メールを与えてやる必要があります。

コマンドラインオプションと引数

様々なオプションが用意されていますが、通常用いるのは以下のものだけです。

sendmail [-i] [-f 送信者アドレス] [-t] [宛先アドレス ...]

推奨するコマンドライン

推奨するコマンドラインは次の通りです。 ほとんどの場合はこれでよいと思います。

sendmail -i -f 送信者アドレス 宛先アドレス [...]

-i は、 メールメッセージ内にドットだけの行が含まれることはあまりないかもしれませんが、 指定しておいたほうがよいでしょう。

-f 送信者アドレス は、バウンスしたときに実行ユーザー宛に差し戻して嬉しい場面は考えにくいので、 別途バウンスを受けるためのメールアドレスを用意し、それを指定しましょう。

宛先アドレスは、-t オプションを指定してヘッダーフィールドから取得させるのではなく、 コマンドライン引数で与えることをお薦めします。 To: などの値にはメールアドレス以外にコメント等が含まれることがあり、 フォーマットは非常に複雑です。 何らかの問題により取得に失敗するかもしれません。 コマンドライン引数に指定したほうが無難です。

推奨するヘッダーフィールド

sendmail に渡すメールのヘッダー部には、以下を記述することを推奨します。 要は「ふつうのメールに見える」ようにすればいいだけです。

現実的な使用例

以上を踏まえると、次のようになります。

$ {
  echo "From: renge@example.com (MIYAUCHI Renge)"
  echo "To: hotaru@example.ne.jp"
  echo "Subject: Meowning!"
  echo
  echo "Good Meowning!"
} |sendmail -i -f renge@example.com bcc@example.com hotaru@example.ne.jp

この例ではメールメッセージをコマンドで生成していますが、 もちろん、ファイルなどで用意するのでも構いません。

日本語をヘッダーに含める

ヘッダーフィールドに日本語を含めるには、 ヘッダー向けの MIME エンコードを施してやる必要になります。 (SMTPUTF8 なる拡張がありますが、現在はまったく普及していないので無視)

文字エンコーディングは旧来の ISO-2022-JP でもいいですし、 現代なら UTF-8 でも問題ないでしょう。ただし、すべてのヘッダーフィールドで 統一したほうが無難です。文字エンコーディングが混在すると誤動作する MUA があります。

nkf による MIME エンコードの例:

$ echo にゃんぱすー |nkf --mime --ic=UTF-8
=?ISO-2022-JP?B?GyRCJEskYyRzJFEkOSE8GyhC?=
$ echo にゃんぱすー |nkf --mime --ic=UTF-8 --oc=UTF-8
=?UTF-8?B?44Gr44KD44KT44Gx44GZ44O8?=

Perl による MIME エンコードの例:

$ echo にゃんぱすー |perl -MEncode -pe 'chomp($_); $_ = encode("MIME-Header-ISO_2022_JP", decode("UTF-8",$_))'; echo
=?ISO-2022-JP?B?GyRCJEskYyRzJFEkOSE8GyhC?=
$ echo にゃんぱすー |perl -MEncode -pe 'chomp($_); $_ = encode("MIME-Header", decode("UTF-8",$_))'; echo
=?UTF-8?B?44Gr44KD44KT44Gx44GZ44O8?=

Perl の場合、改行コードも含めて処理させた場合の結果が不安定なので、 例のように改行コードは取り除いて (chomp($_)) から処理することをお勧めします。

https://twitter.com/satoh_fumiyasu/status/1202423887453020161

echo あ |perl -MEncode -pe ‘$_ = encode(“MIME-Header-ISO_2022JP”, decode(“UTF-8”,$))’ だと改行も含まれるのに、 echo あ |perl -MEncode -pe ‘$_ = encode(“MIME-Header”, decode(“UTF-8”,$_))’ だと含まれないの、何でなの…と思ったら、後者は改行も含めてエンコードされてしまうのか…。

https://twitter.com/satoh_fumiyasu/status/1202426064875618304

後者、Perl 5.8.4 だと改行は含めずにヘッダー MIME エンコードして改行が捨てられるんだけど。

ISO-2022-JP か UTF-8 かに依っても結果が異なるだけでなく、Perl バージョンに依っても異なるんですががが。

改行コードを含めずに渡すのが無難ってことかね。

日本語を本文に含める

文字エンコーディングを ISO-2022-JP にするなら、そのまま突っ込めばいいです。 ただし、Content-Type: ヘッダーフィールドで文字エンコーディングを明示しましょう。 このとき MIME-Version: ヘッダーフィールドも必須です。

…他のヘッダーフィールド…
Content-Type: text/plain; charset=ISO-2022-JP
MIME-Version: 1.0

…ISO-2022-JP エンコードされた本文…

文字エンコーディングを UTF-8 にする場合は、Base64 や Quoted-Printable 形式でエンコードする形式が一般的です。 Base64 であれば GNU coreutils の base64 コマンドや nkf の --base64 オプションが利用できます。

$ echo 本文テキスト |base64
$ echo 本文テキスト |nkf --base64 --ic=UTF-8 --oc=UTF-8

UTF-8 + Base64 (または Quoted-Printable) の場合は Content-Transfer-Encoding: フィールドで明示する必要があります。

…他のヘッダーフィールド…
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: base64
MIME-Version: 1.0

…UTF-8 + Base64 エンコードされた本文…

現代の MTA と MUA であれば、Base64 などエンコードなしで、UTF-8 のまま本文に突っ込んでも問題ありません。

…他のヘッダーフィールド…
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
MIME-Version: 1.0

…UTF-8 エンコードされた本文…

日本語を含む使用例

以上を踏まえると、次のようになります。

ISO-2022-JP の場合:

$ {
  echo "From: renge@example.com (MIYAUCHI Renge)"
  echo "To: hotaru@example.ne.jp"
  echo "Subject: `echo にゃんぱすー |nkf --mime --ic=UTF-8`"
  echo "Content-Type: text/plain; charset=ISO-2022-JP"
  echo "MIME-Version: 1.0"
  echo
  {
    echo "にゃんぱすー!" 
    echo "-- "
    echo "れんげ"
  } |nkf --ic=UTF-8 --oc=ISO-2022-JP -x -m0
} |sendmail -i -f renge@example.com bcc@example.com hotaru@example.ne.jp

UTF-8 の場合:

$ {
  echo "From: renge@example.com (MIYAUCHI Renge)"
  echo "To: hotaru@example.ne.jp"
  echo "Subject: `echo にゃんぱすー |nkf --mime --ic=UTF-8 --oc=UTF-8`"
  echo "Content-Type: text/plain; charset=UTF-8"
  echo "Content-Transfer-Encoding: 8bit"
  echo "MIME-Version: 1.0"
  echo
  echo "にゃんぱすー!" 
  echo "-- "
  echo "れんげ"
} |sendmail -i -f renge@example.com bcc@example.com hotaru@example.ne.jp

番外: nkf の推奨オプション

nkf を利用するとき指定したほうがよい、あるいは指定を考慮すべきオプションを紹介しておきます。 デフォルトで文字エンコーディング変換以外の変換処理がされるため、これを抑制します。

指定しなくても問題ない場合もありますが、明示しておいたほうが無難です。


ところで、12月25日はクリスマスな上に、 OSS 界隈で地味に活躍されているふみやすさんの誕生日ですね!
http://www.amazon.co.jp/registry/wishlist/27M7TV8CEEF6G?sort=priority

逆に、あなたの書いた OSS や Blog や Advent Calendar が気に入ったら何か送りたく なってしまうかもしれないので、プロフィールや Web サイトに あなたの Amazon 欲しいものリストの URL を貼っておいてくださいね!


私が勤める OSSTech っていう某弊社で社員募集しているようです。 人材紹介会社を介さなければ、入社後に 20万円のボーナス! 「ふみやすっていう人に紹介された」と言ってもらえると私にもボーナス!! → https://www.osstech.co.jp/recruit/


よろしければ、これまで参加した/参加予定のほかの Advent Calendar もどうぞ。