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

  • 45
    Like
  • 0
    Comment
More than 1 year has passed since last update.

いろいろ試しました。
先に結論を言ってしまうと、基本的には、文字化けないように出来ててあんまり考えなくていいです。あと、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の標準設定がこうn

'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 したときの挙動がかなり不審でいまのところ鬼門です。やり方間違ってる??