Pythonで日本語メールを送る方法をいろいろ試した


いろいろ試しました。
先に結論を言ってしまうと、基本的には、文字化けないように出来ててあんまり考えなくていいです。あと、Python3でadd_charsetで文字セットいじったときの挙動がよく分かりません。

本題です。


##基本のコード

```sendmail.py
# -*- coding: utf-8 -*-
import smtplib
from email.mime.text import MIMEText
from email.header import Header
from email import charset

con = smtplib.SMTP('localhost')
con.set_debuglevel(True)

cset = 'utf-8'  # <---------------(文字セットの設定だよ)

message = MIMEText(u'日本語のメールだよ★', 'plain', cset)
message['Subject'] = Header(u'メール送信テスト', cset)
message['From'] = 'from@example.com'
message['To'] = 'to@example.com'

con.sendmail('from@example.com', ['to@example.com'], 
message.as_string()) 

con.close()
```

ではやってみます。

##Python2.7.2 + None
いきなり変化球だけど、文字セットの設定をしなかった場合を試す。
基本のコードで、```cset = None``` ってしてみた。

```
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-9: ordinal not in range(128)
```
さすがに怒られる。文字セットは何かしら登録しないと、us-ascii として処理されるので、どこかでコケます。



##Python2.7.2 + utf-8 (with BASE64)
基本のコードで```cset = utf-8``` ってしてみる。これは無事受信できました。
生データはこんなかんじ。

```
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Subject: =?utf-8?b?44Oh44O844Or6YCB5L+h44OG44K544OI?=
From: from@example.com
To: to@example.com
Reply-To: somebody@example.com

5pel5pys6Kqe44Gu44Oh44O844Or44Gg44KI4piF
```
BodyのエンコードがBase64になる。
これは、Pythonの標準が以下のようになってるから。

```/email/charset.py
CHARSETS = {
...
'utf-8':       (SHORTEST,  BASE64, 'utf-8'),
#タプルは、ヘッダーのエンコード, ボディのエンコード、出力のエンコードを示してるんだってさcharset.pyに書いてあった
…
}
```
多分、これで困ることってもう殆ど無いんだけど、昔は、auの端末でNGでした確か。でももうこれでいいと思います。おしまい。


##Python2.7.2 + utf-8 with QP

Base64が嫌だ！ って場合は、CHARSETを上書きする。
これを基本のコードの最初のほうに挿入。

```sendmain.pyのどっかに書く.py
charset.add_charset('utf-8', charset.SHORTEST, charset.QP, 'utf-8')
# uft-8 の設定として、 ヘッダはSHORTEST、ボディにQP（quoted-printable)使うよ、出力のエンコードはutf-8だよ
cset = utf-8
```
これを実行するとこうなる。

```
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
Subject: =?utf-8?b?44Oh44O844Or6YCB5L+h44OG44K544OI?=
From: from@example.com
To: to@example.com
Reply-To: somebody@example.com

=E6=97=A5=E6=9C=AC=E8=AA=9E=E3=81=AE=E3=83=A1=E3=83=BC=E3=83=AB=E3=81=A0=E3=
=82=88=E2=98=85
```
BASE64じゃない何かになってます。
受信も問題ないです。

##Python2.7.2 + utf-8 with 8bit
ボディのエンコードに何も指定しないとどうなるか。

```sendmain.pyのどっかに書く.py
charset.add_charset('utf-8', charset.SHORTEST, None, 'utf-8')
cset = utf-8
```
出力はこう。そのまま出る。


```
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
Subject: =?utf-8?b?44Oh44O844Or6YCB5L+h44OG44K544OI?=
From: from@example.com
To: to@example.com
Reply-To: somebody@example.com

日本語のメールだよ★
```
Content-Transfer-Encodingは7bitか8bitになる。
これは、/email/encoders.py の
encode_7or8bit() という関数がうまいこと決定している。
8bitにしたいなーと思う場合は、これ。これが結構最近多いのかも。




##Python2.7.2 + shift_jis


```
IME-Version: 1.0
Content-Type: text/plain; charset="iso-2022-jp"
Content-Transfer-Encoding: 7bit
Subject: =?iso-2022-jp?b?GyRCJWEhPCVrQXc/LiVGJTklSBsoQg==?=
From: from@example.com
To: to@example.com
Reply-To: somebody@example.com

F|K\8l$N%a!<%k$@$h!z
```
文字セットを'shift_jis'とすると、出力はみんな大好きiso-2022-jpになりました。
これは、Pythonの標準設定がこうｎ

```
'shift_jis':   (BASE64,    None,    'iso-2022-jp'),
```
BodyのエンコーディングがNoneになってる。Content-Transfer-Encodingは勝手に 7bit ってなってる。



##Python3.3.0 + None
次、Python3 で試す。最初に、文字セットを指定しない場合。Python2だとUnicodeEncodeErrorが出たやつ。

```
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Subject: =?utf-8?b?44Oh44O844Or6YCB5L+h44OG44K544OI?=
From: from@example.com
To: to@example.com
Reply-To: somebody@example.com

5pel5pys6Kqe44Gu44Oh44O844Or44Gg44KI4piF
```

なんと送れる。受信も問題ない。
ちょっと中身読んだ感じ、us-ascii で試して、UnicodeEncodeErrorが出たらutf-8で試す、っていう感じっぽい。
ということで、Python3.3 だと、文字セットを全く意識しなくてもメール飛ばせるのですごい。

##Python3.3.0 + utf-8 (with BASE64)

ということで、```cset = utf-8``` とやっても、上と同じ筈。

```
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Subject: =?utf-8?b?44Oh44O844Or6YCB5L+h44OG44K544OI?=
From: from@example.com
To: to@example.com
Reply-To: somebody@example.com

5pel5pys6Kqe44Gu44Oh44O844Or44Gg44KI4piF
```
同じ！ 次！

##Python3.3.0 + utf-8 with QP
BODYにQP使ってみた〜い！

ってことで、Python2のときと同じく下をどっかに書く。

```sendmain.pyのどっかに書く.py
charset.add_charset('utf-8', charset.SHORTEST, charset.QP, 'utf-8')
cset = utf-8
```

メール送信！


```
   self.set_payload(_text, _charset)
  File "/Users/yasunori/.pythonbrew/pythons/Python-3.3.0/Frameworks/Python.framework/Versions/3.3/lib/python3.3/email/message.py", line 280, in set_payload
    self.set_charset(charset)
  File "/Users/yasunori/.pythonbrew/pythons/Python-3.3.0/Frameworks/Python.framework/Versions/3.3/lib/python3.3/email/message.py", line 317, in set_charset
    self._payload = charset.body_encode(self._payload)
  File "/Users/yasunori/.pythonbrew/pythons/Python-3.3.0/Frameworks/Python.framework/Versions/3.3/lib/python3.3/email/charset.py", line 395, in body_encode
    return email.quoprimime.body_encode(string)
  File "/Users/yasunori/.pythonbrew/pythons/Python-3.3.0/Frameworks/Python.framework/Versions/3.3/lib/python3.3/email/quoprimime.py", line 240, in body_encode
    if body_check(ord(c)):
  File "/Users/yasunori/.pythonbrew/pythons/Python-3.3.0/Frameworks/Python.framework/Versions/3.3/lib/python3.3/email/quoprimime.py", line 81, in body_check
    return chr(octet) != _QUOPRI_BODY_MAP[octet]
KeyError: 26085
```

怒られたー！ 怖い怖い怖い怖い！！！！
配列にそんなキー無いって言われてる。見た感じ、_QUOPRI_BODY_MAP は英数字程度の筈なのに、26085番目の文字を参照しようとしてる。
なんでだろう…… ちょっと見た感じではよく分からないので保留。

##Python3.3.0 + utf-8 with 8bit
8bitでそのまま送りたいのねん。

```sendmain.pyのどっかに書く.py
charset.add_charset('utf-8', charset.SHORTEST, None, 'utf-8')
```

こいつを追加して送信。

```
File "/Users/yasunori/.pythonbrew/pythons/Python-3.3.0/Frameworks/Python.framework/Versions/3.3/lib/python3.3/smtplib.py", line 744, in sendmail
    msg = _fix_eols(msg).encode('ascii')
UnicodeEncodeError: 'ascii' codec can't encode characters in position 231-240: ordinal not in range(128)
```

怒られたー！ 怖い怖い怖い怖い！！！！
何でこれ怒られるんでしょう。
実は、QPのエラーと違って、```message.as_string()``` までは通っていて、メール文面まではちゃんと出来ている。

```
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
Subject: =?cp932?b?g4GBW4OLkZeQTYNlg1iDZw==?=
From: from@example.com
To: to@example.com
Reply-To: somebody@example.com

日本語のメールだよ★
```
いいじゃん。
いざ送信！ ってときに怒られてるんだよなー。
さっきのエラー箇所を見ると、smtplibの中で、asciiべた書きでencodeしようとしてるので確かに駄目っぽいんだけど…… これってどうすればいいんだろう。誰か教えてください……




##Python3.3.0 + shift_jis

安定のsjis。

```
MIME-Version: 1.0
Content-Type: text/plain; charset="iso-2022-jp"
Content-Transfer-Encoding: 7bit
Subject: =?iso-2022-jp?b?GyRCJWEhPCVrQXc/LiVGJTklSBsoQg==?=
From: from@example.com
To: to@example.com
Reply-To: somebody@example.com

F|K\8l$N%a!<%k$@$h!z
```

##おしまい

大体想像通りの動きをしてくれますが、Python3系で、add_charset したときの挙動がかなり不審でいまのところ鬼門です。やり方間違ってる？？