Help us understand the problem. What is going on with this article?

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

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした