Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Djangoでのメール送信 - 設定からテストまで/djangocongress-jp-201...
Search
thinkAmi
May 18, 2019
Programming
1
12k
Djangoでのメール送信 - 設定からテストまで/djangocongress-jp-2019-talk
DjangoCongress JP 2019の発表資料です
thinkAmi
May 18, 2019
Tweet
Share
More Decks by thinkAmi
See All by thinkAmi
知ろう!使おう!HDF5ファイル!/pycon-jp-2019-talk
thinkami
4
9.7k
Django・WSGIミドルウェア入門/django-congress-jp-2018-talk
thinkami
4
5k
自分のための機械学習をしてみた話
thinkami
0
600
Xamarinで作るAndroid Wearアプリ
thinkami
1
2.6k
FluentMigratorでDBマイグレーション
thinkami
0
2.1k
デプロイボタンを使ってみた
thinkami
0
960
Vagrant + Berkshelf でお手軽写経環境構築
thinkami
1
1.3k
Twilio入門
thinkami
0
1.6k
おひとりさま環境でのChef-solo使用例
thinkami
2
1.4k
Other Decks in Programming
See All in Programming
[JAWS-UG横浜 #76] イケてるアップデートを宇宙いち早く紹介するよ!
maroon1st
0
450
Refactor your code - refactor yourself
xosofox
1
260
暇に任せてProxmoxコンソール 作ってみました
karugamo
1
720
14 Years of iOS: Lessons and Key Points
seyfoyun
1
770
htmxって知っていますか?次世代のHTML
hiro_ghap1
0
330
Go の GC の不得意な部分を克服したい
taiyow
2
770
Security_for_introducing_eBPF
kentatada
0
110
複雑な仕様に立ち向かうアーキテクチャ
myohei
0
170
Zoneless Testing
rainerhahnekamp
0
120
As an Engineers, let's build the CRM system via LINE Official Account 2.0
clonn
1
670
Scalaから始めるOpenFeature入門 / Scalaわいわい勉強会 #4
arthur1
1
300
Beyond ORM
77web
2
210
Featured
See All Featured
Raft: Consensus for Rubyists
vanstee
137
6.7k
Build your cross-platform service in a week with App Engine
jlugia
229
18k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
127
18k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
8
1.2k
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
29
2.3k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
26
1.5k
Side Projects
sachag
452
42k
How to Think Like a Performance Engineer
csswizardry
22
1.2k
Why You Should Never Use an ORM
jnunemaker
PRO
54
9.1k
Docker and Python
trallard
42
3.1k
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
6
510
Unsuck your backbone
ammeep
669
57k
Transcript
Django でのメール送信 ~ 設定からテストまで ~ DjangoCongress JP 2019 talk 2019/5/18
@thinkAmi
お前誰よ / Who are you ? @thinkAmi Python & Django
Blog :メモ的な思考的な (http://thinkami.hatenablog.com/) ( 株) 日本システム技研 PyCon JP 2016-18 Silver Sponsor ギークラボ長野 Python Boot Camp in 長野 みんなのPython 勉強会 in 長野
質問
Django でメール送信の経験がある方
簡単! Django でメール送信
設定ファイル # settings.py EMAIL_HOST = 'smtp.example.com' # 送信メールサーバ EMAIL_PORT =
'587' # 送信メールポート EMAIL_HOST_USER = 'test_user' # 送信ユーザ EMAIL_HOST_PASSWORD = 'Passw0rd' # 送信パスワード
send_mail() 関数の実行 send_mail(subject='my subject', # 件名 message='My Body', # 本文
from_email='
[email protected]
', # 送信者 recipient_list=['
[email protected]
']) # 受信者(To) のリスト
受信メール
メールの送信 send_mail(subject='my subject', # 件名 message='My Body', # 本文 from_email='
[email protected]
',
# 送信者 recipient_list=['
[email protected]
']) # 受信者(To) のリスト
話すこと Django のメール送信の流れ EmailMessage EmailBackend メール送信とunittest ショートカット関数 エラー通知メール エラー通知の仕組み 内容のマスク
話さないこと 仕組み以外の話 メールのRFC メール配信サービスの設定 SendGrid Amazon SES
注意 このトークを聴いても、Django アプリは作れません スライドは公開 スライド中のソースコードは、一部省略 GitHub に全体を公開済 https://github.com/thinkAmi/DjangoCongress_JP_2019_talk
環境 Python 3.7.3 Django 2.2.1 LTS
目次 Django のメール送信の流れ unittest ショートカット関数 エラー通知メール
Django のメールの仕組み send_mail() 関数を元に理解する
send_mail() 関数は何をしているか # django.core.mail.__init__.py def send_mail(subject, message, from_email, recipient_list, fail_silently=False,
auth_user=None, auth_password=None, connection=None, html_message=None): # EmailBackend オブジェクトを取得 connection = connection or get_connection() # EmailMessage 系オブジェクトを生成 mail = EmailMultiAlternatives( subject, message, from_email, recipient_list, connection=connection) # EmailMessage 系オブジェクトで送信 return mail.send()
send_mail() 関数 メール送信のショートカット関数 実際 EmailMessage EmailBackend
EmailMessage について
EmailMessage の役割 メール属性を持つ EmailBackend を使ってメールを送信
EmailMessage のメール属性 from_email, to, cc, bcc, reply_to subject body attachments
extra_headers connection EmailBackend
EmailMessage とsend_mail() の違い できること どちらでも EmailMessage のみ
どちらでもできる例 メールアドレスに表示名を付けたい HTML メールを送信したい メールテンプレートを使いたい
メールアドレスの表示名とは from_user <
[email protected]
>
メールアドレスに表示名を付ける # EmailMessage の場合 email = EmailMessage( from_email='from_user <
[email protected]
>', ...)
# send_mail() の場合 send_mail(from_email='from user <
[email protected]
>')
受信メール ( ヘッダ) From: from_user <
[email protected]
>
HTML メールを送信したい # EmailMessage( のサブクラス) の場合 email = EmailMultiAlternatives( body='My
Body', ...) email.attach_alternative('<strong>HTML メール</strong> です', 'text/html') email.send() # send_mail() の場合 send_mail(html_message='<strong>HTML メール</strong> です')
受信メール テキスト表示 HTML 表示
メールでテンプレートを使いたい render_to_string() 関数と組み合わせる
テンプレートエンジンを変更 TEMPLATES = [ { # View は、Django エンジン 'BACKEND':
'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')], }, { # メールは、Jinja2 エンジン 'BACKEND': 'django.template.backends.jinja2.Jinja2', 'DIRS': [os.path.join(BASE_DIR, 'template_jinja2')], }, ]
テキストメールのテンプレート # base.txt メールのベースです {% block mail_content %} {% endblock
%} {% include './signature.txt' %} # content.txt {% extends './base.txt' %} {% block mail_content %} {{ message }} {% endblock %} # signature.txt from thinkAmi
テンプレートを利用して送信 template_body = render_to_string('mail/content.txt', context={'message': ' 埋め込みます'}) # EmailMessage の場合
email = EmailMessage(body=template_body, ...) # send_mail() の場合 send_mail(message=template_body, ...)
受信メール
EmailMessage のみできる例 添付ファイル
添付ファイル EmailMessage のメソッドを使用 関数 引数 添付ファイル名 __init__() MIMEBase 系, tuple
( *) 指定可 attach() 〃 〃 attach_file() ファイルシステム上のパス ファイルシステムと同一 * tuple(filename, content, mimetype)
メソッドの使い方 # 静的ディレクトリにあるファイルを添付する static_file_dir = pathlib.Path(settings.STATICFILES_DIRS[0]) image_file = static_file_dir.joinpath('images', 'shinanogold.png')
# __init__ の場合 with image_file.open(mode='rb') as f: EmailMessage(attachments=[('my.png', f.read(), 'image/png')], ...).send() # attach_file の場合 msg = EmailMessage(...) msg.attach_file(image_file) msg.send()
__init__ での結果
attach_file での結果
EmailBackend について
EmailBackend とは メールを送信する手段 拡張や自作も可能
EmailBackend の種類 Django 標準 django.core.mail.backends メール内容をどこに出力するかで使い分け SMTP コンソール ファイル インメモリ
ダミー
EmailBackend の指定 settings の EMAIL_BACKEND デフォルト SMTP
EmailBackend に必要な設定 EmailBackend により異なる # SMTP 時のsettings EMAIL_HOST = 'smtp.example.com'
# 送信メールサーバ EMAIL_PORT = '587' # 送信メールポート EMAIL_HOST_USER = 'test_user' # 送信ユーザ EMAIL_HOST_PASSWORD = 'Passw0rd' # 送信パスワード
EmailBackend の拡張 例 console.EmailBackend でも、件名を日本語で見たい Subject: =?utf-8?b?5Lu25ZCN44Gn44GZ?=
console.EmailBackend を拡張 write_message() メソッドをオーバーライド # myapp.email_backends.py class ReadableSubjectEmailBackend(console.EmailBackend): def write_message(self,
message): from email.header import decode_header subject = message.message().get('Subject') decoded_tuple = decode_header(subject) # => [('Django', None)] # MIME ヘッダエンコーディングなし # => [(b'\xe3\x82\xb8\xe3\x83\xa3\xe3\x83\xb3\xe3\x82\xb4', 'utf-8')] if decoded_tuple[0][1] is not None: readable_subject = decoded_tuple[0][0].decode( decoded_tuple[0][1]) self.stream.write(f'\nSubject ( 日本語表示): {readable_subject}\n') super().write_message(message)
settings EMAIL_BACKEND = 'myapp.email_backends.ReadableSubjectEmailBackend'
結果 Subject ( 日本語表示): ジャンゴ Content-Type: text/plain; charset="utf-8" Subject: =?utf-8?b?44K444Oj44Oz44K0?=
EmailBackend を自作 send_messages() メソッドを実装したクラスを用意 django.core.mail.backends.base.BaseEmailBackend を継承すると楽
例 メール送信ではなく、Slack に投稿したい SlackBackend
SlackBackend の実装 メールのBody をSlack へ投稿する from slackclient import SlackClient class
SlackBackend(BaseEmailBackend): def send_messages(self, email_messages): payload = email_messages[0].message().get_payload() client = SlackClient(settings.SLACK_OAUTH_ACCESS_TOKEN) client.api_call( 'chat.postMessage', channel=settings.SLACK_CHANNEL, text=payload, )
settings EMAIL_BACKEND = 'myapp.email_backends.SlackBackend' SLACK_OAUTH_ACCESS_TOKEN = 'Your OAuth Access Token'
SLACK_CHANNEL = 'Your Channel ID'
メールの送信処理 EmailMessage( subject='Django', body='DjangoCongress JP 2019 にようこそ!', from_email=settings.REAL_MAIL_FROM, to=settings.REAL_MAIL_TO).send()
結果
ここまでのまとめ EmailMessage でメール情報を作る EmailBackend を使って送信する
目次 Django のメール送信の流れ unittest ショートカット関数 エラー通知メール
開発時の悩み メール内容の確認をしたい 実メールサーバに送信したくない
実メールサーバ以外での確認 EmailBackend を差し替え コンソールやファイルなど Python のSMTP サーバ DebuggingServer を宛先にして送信
unittest 時 メールは送信されない EmailBackend が locmem.EmailBackend に差し替わる
差し替えているところ settings.TEST_RUNNER django.test.runner.DiscoverRunner setup_test_environment() django.test.utils.setup_test_environment() # https://github.com/django/django/blob/2.2.1/django/test/utils.py#L103 # EmailBackend の差し替え
settings.EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend' # メールボックスの初期化 mail.outbox = []
locmem に差し替わると django.core.mail.outbox に、EmailMessage のリストが設定
テスト対象の関数 def my_send_mail(encoding='utf-8', has_attachment=False): msg = EmailMessage( subject=' 件名', body='
本文', from_email=' 差出人 <
[email protected]
>', to=[' 送信先1 <
[email protected]
>', ' 送信先2 <
[email protected]
>'], cc=[' シーシー <
[email protected]
>'], bcc=[' ビーシーシー <
[email protected]
>'], reply_to=[' 返信先 <
[email protected]
>'], headers={'Sender': '
[email protected]
'}) if has_attachment: # 静的ディレクトリにあるファイルを添付する img = pathlib.Path(settings.STATICFILES_DIRS[0]).joinpath( 'images', 'shinanogold.png') msg.attach_file(img) msg.send()
mail.outbox の動作確認 class TestSendMail(TestCase): def _callFUT(self, encoding='utf-8', has_attachment=False): from myapp.utils
import my_send_mail my_send_mail(encoding=encoding, has_attachment=has_attachment) def test_send_multiple(self): # 実行前はメールボックスに何もない self.assertEqual(len(mail.outbox), 0) # 1 回実行すると、メールが1 通入る self._callFUT() self.assertEqual(len(mail.outbox), 1) # もう1 回実行すると、メールが2 通入る self._callFUT() self.assertEqual(len(mail.outbox), 2)
各フィールドの検証 def test_mail_fields(self): self._callFUT() actual = mail.outbox[0] self.assertEqual(actual.subject, ' 件名')
self.assertEqual(actual.body, ' 本文') self.assertEqual(actual.from_email, ' 差出人 <
[email protected]
>') # 宛先系はlist として設定される self.assertEqual(actual.to, [' 送信先1 <
[email protected]
>', ' 送信先2 <
[email protected]
>'],) # 追加ヘッダも含まれる self.assertEqual(actual.extra_headers['Sender'], '
[email protected]
')
添付ファイルがある場合 def test_attachment(self): self._callFUT(has_attachment=True) actual = mail.outbox[0] # 添付ファイル自体を検証 img
= pathlib.Path(settings.STATICFILES_DIRS[0]).joinpath( 'images', 'shinanogold.png') with img.open('rb') as f: expected_img = f.read() # tuple(filename, content, mimetype) self.assertEqual(actual.attachments[0][1], expected_img)
ここまでのまとめ unittest では、メールは送信されない django.core.mail.outbox に、EmailMessage のリストが設定
目次 Django のメール送信の流れ unittest ショートカット関数 エラー通知メール
ショートカット関数 User モデル email_user() 一括送信 send_mass_mail() 管理者宛送信 mail_admins() mail_managers()
User モデルの email_user() # ユーザー foo を取得 foo_user = User.objects.get(username='foo')
# ユーザー foo のメールアドレスへ送信 foo_user.email_user( subject='Hello', message='Welcome!', from_email='
[email protected]
', connection=console.EmailBackend(), )
send_mass_mail() 1 接続で複数のメールを送信 複数件送信する場合、 send_mail() より効率が良い msg1 = ('shortcut subject1',
'Shortcut Body', '
[email protected]
', ['
[email protected]
', '
[email protected]
']) msg2 = ('shortcut subject2', 'Shortcut Body', '
[email protected]
', ['
[email protected]
', '
[email protected]
']) send_mass_mail((msg1, msg2), connection=console.EmailBackend())
注意点 機能 EmailMessage send_mail() send_mass_mail() 宛先 To, Cc, Bcc, Reply-To
To To HTML メール 添付ファイル
管理者宛メールのショートカット関数 mail_admins() settings.ADMINS 宛にメール送信 mail_managers() settings.MANAGERS 宛にメール送信
ADMINS とMANAGERS の違い 公式ドキュメントより ADMINS = site admins MANAGERS =
site managers Django のエラー通知機能にて、両者の違いを見る
目次 Django のメール送信の流れ unittest ショートカット関数 エラー通知メール
Django のエラー通知メール Django アプリでエラーが発生した時に、メールで通知 サーバーエラー HTTP 500 リンク切れ Referer あり
& HTTP 404 存在しないページに直接アクセスした時は通知しない
サーバーエラー通知メールの仕組み # django.utils.log.py DEFAULT_LOGGING = { 'filters': { 'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse'}, 'handlers': { 'mail_admins': { 'level': 'ERROR', 'filters': ['require_debug_false'], 'class': 'django.utils.log.AdminEmailHandler'}}, 'loggers': { 'django': { 'handlers': ['console', 'mail_admins']}}
設定の変更例 エラー通知メールは常にコンソール出力 LOGGING = { 'handlers': { 'mail_admins': { ...
'email_backend': # 追加 'django.core.mail.backends.console.EmailBackend'}
自作 AdminEmailHandler を継承し、 send_mail() をオーバーライド
例 エラーレポートのHTML をパスワード付zip ファイルにして送信 class MyAdminEmailHandler(AdminEmailHandler): def send_mail(self, subject, message,
*args, **kwargs): with TemporaryDirectory() as temp_dir: html_file = pathlib.Path(temp_dir).joinpath('report.html') with html_file.open('w') as f: f.write(kwargs.get('html_message')) # パスワード付zip ファイルを添付・送信 zip_file = pathlib.Path(temp_dir).joinpath('dst.zip') pyminizip.compress(str(html_file), None, str(zip_file), 'pass', 0) msg = EmailMessage(...) msg.attach_file(zip_file) msg.send()
結果
リンク切れ通知メールの仕組み django.middleware.common.BrokenLinkEmailsMiddleware
通知先の違い サーバーエラー MAIL_ADMINS リンク切れ MAIL_MANAGERS
サーバーエラーの通知設定 # 本番運用モードにする DEBUG = False # 送信先の site admins
のメールアドレスを設定 # (' メールアドレスコメント', ' メールアドレス') ADMINS = [('Admin1', '
[email protected]
')] # 他、使用するEmailBackend の設定を行う
サーバーエラー通知設定 ( 任意) # 送信元のメールアドレス # デフォルト:root@localhost SERVER_EMAIL = '
[email protected]
'
# エラー通知メールの件名のPrefix # デフォルト:[Django] EMAIL_SUBJECT_PREFIX = '[Hello]'
動作確認 $ curl http://localhost:8000/force_500 <h1>Server Error (500)</h1>
受信メール エラー内容やsettings 、POST データなどが含まれる デフォルトでは、ローカル変数は含まれない Subject: [Hello]ERROR (EXTERNAL IP): Internal
Server Error: /force_500 From:
[email protected]
To:
[email protected]
Internal Server Error: /force_500 ... Request Method: GET Request URL: http://localhost:8000/force_500 ... YEAR_MONTH_FORMAT =3D 'F Y'
ローカル変数もほしい時 単純な方法:Debug=True の時のHTML を含める # settings DEFAULT_LOGGING['handlers']['mail_admins']['include_html'] = True
動作確認 例外を発生するView def force_500(request): my_local_value = ' ハロー' raise Exception('Error')
ローカル変数を含むサーバーエラー通知メール Subject: [Hello]ERROR (EXTERNAL IP): Internal Server Error: /force_500 From:
[email protected]
To:
[email protected]
... <!DOCTYPE html> <tr> <td>my_local_value</td> <td class="code"><pre>' ハロー'</pre></td> </tr>
サーバーエラー通知メールの注意点 サーバー情報が漏洩する可能性あり settings ローカル変数 POST データ 必要に応じて、内容をマスクする
settings に対する、自動マスク機能 以下のパターンを含む設定名は、Django が自動でマスク API KEY PASS SECRET SIGNATURE TOKEN
動作確認 settings DJANGO_CONGRESS_PASSPORT = '123' DJANGO_CONGRESS_PASSWORD = '456' DJANGO_CONGRESS_PASTA =
'789'
結果 DJANGO_CONGRESS_PASSPORT =3D '********************' DJANGO_CONGRESS_PASSWORD =3D '********************' DJANGO_CONGRESS_PASTA =3D '789'
ローカル変数に対するマスク 以下の場合、デフォルトではそのまま表示 DEFAULT_LOGGING['handlers']['mail_admins']['include_html'] = True @sensitive_variables デコレータを使う
一部をマスクする例 View @method_decorator(sensitive_variables('region', 'year'), name='dispatch') class MaskedLocalVariableView(TemplateView): """ 一部ローカル変数をマスク """
template_name = 'myapp/breaking.html' def get(self, request, *args, **kwargs): conference = 'DjangoCongress' region = 'JP' # マスクする year = '2019' # マスクする raise Exception
一部マスクの結果 <li class="frame user"> <h2>Local Vars</h2> <td>conference</td> <td class="code"><pre>'DjangoCongress'</pre></td> <td>region</td>
<td class="code"><pre>'********************'</pre></td> <td>year</td> <td class="code"><pre>'********************'</pre></td>
すべてをマスクする例 View @method_decorator(sensitive_variables(), name='dispatch') class AllMaskedLocalVariableView(TemplateView): ...
エラー通知に含まれるPOST データ デフォルトでは、そのまま表示 Exception Type: Exception at /post_parameters POST: apple
=3D 'Shinano Gold' grape =3D 'Shine Muscat' pear =3D 'Southern Suite' @sensitive_post_parameters デコレータを使う
一部をマスクする例 View @method_decorator(sensitive_post_parameters('grape', 'pear'), name='dispatch') class MaskedPostParameterView(FormView): def post(self, request,
*args, **kwargs): raise Exception
一部マスクの結果 Request information: POST: apple =3D 'Shinano Gold' grape =3D
'********************' pear =3D '********************'
すべてをマスクする例 View @method_decorator(sensitive_post_parameters(), name='dispatch') class AllMaskedPostParameterView(FormView): ...
ちなみに ローカル変数とPOST データを両方マスク @method_decorator(sensitive_variables('year'), name='dispatch') @method_decorator(sensitive_post_parameters('grape'), name='dispatch') class DoubleMaskedView(FormView): ...
マスク機能を自作 デコレータをView に付けたくない 一括でマスクしたい 自作のマスク機能を作成することで対応可能 SafeExceptionReporterFilter のサブクラスが推奨
マスク機能 class MyReporterFilter(SafeExceptionReporterFilter): def get_post_parameters(self, request): """ POST データをマスク """
cleansed = request.POST.copy() cleansed['grape'] = '?' * 30 return cleansed def get_traceback_frame_variables(self, request, tb_frame): """ トレースバック中のローカル変数をマスク """ cleansed = {} for name, value in tb_frame.f_locals.items(): if name == 'year': value = '?' * 30 else: value = self.cleanse_special_types(request, value) cleansed[name] = value return cleansed.items()
自作マスク機能を使用 settings DEFAULT_EXCEPTION_REPORTER_FILTER = 'myapp.reporter_filter.MyReporterFilter'
マスク結果 Request information: POST: apple =3D 'Shinano Gold' grape =3D
'??????????????????????????????' pear =3D 'Southern Suite' ... <li class="frame user"> <h2>Local Vars</h2> <td>conference</td> <td class="code"><pre>'DjangoCongress'</pre></td> <td>region</td> <td class="code"><pre>'JP'</pre></td> <td>year</td> <td class="code"><pre>'??????????????????????????????'</pre></td>
リンク切れの通知設定 # 本番運用モードにする DEBUG = False # リンク切れを通知するミドルウェアを追加 MIDDLEWARE +=
['django.middleware.common.BrokenLinkEmailsMiddleware'] # 送信先の site managers のメールアドレスを設定 # (' メールアドレスコメント', ' メールアドレス') MANAGERS = [('Manager1', '
[email protected]
')] # 他、使用するEmailBackend の設定を行う
リンク切れの通知設定 ( 任意) # ADMINS と共用 SERVER_EMAIL = '...' EMAIL_SUBJECT_PREFIX
= '...' # HTTP404 でもエラーレポートメールを送信したくないURL がある場合は、正規表現で指定 IGNORABLE_404_URLS = [ re.compile(r'^/ignore_404$'), ]
動作確認 Referer ありで、HTTP 404 # 実際は1 行 $ curl -H
"Referer:http://localhost:8000/breaking_link" http://localhost:8000/force_404 <h1>Not Found</h1><p>The requested resource was not found on this server.</p>
受信メール Subject: [Hello]Broken INTERNAL link on localhost:8000 From:
[email protected]
To:
[email protected]
Referrer: http://localhost:8000/breaking_link Requested URL: /force_404 User agent: curl/7.54.0 IP address: 127.0.0.1
まとめ メール送信まわりは標準搭載 unittest ショートカット関数 エラー通知 メール送信まわりの機能は、いずれも拡張・自作可 エラー通知メールは、情報漏えいに注意 必要に応じてマスク
Enjoy Django email life!!
Appendix
エンコーディングの変更 メールのエンコーディング 非ASCII 文字を送信するために使用 ヘッダ 件名 Content-Type
デフォルトの挙動 件名に日本語をセット email = EmailMessage(subject=' 件名です', ...) 受信メール Content-Type: text/plain;
charset="utf-8" Content-Transfer-Encoding: 7bit Subject: =?utf-8?b?5Lu25ZCN44Gn44GZ?=
メールのエンコーディング設定 settings.DEFAULT_CHARSET utf-8 HttpResponse オブジェクトのエンコーディング
EmailMessage オブジェクトの属性 encoding メールのエンコーディングを変更可能
例 エンコーディングを ISO-2022-JP に変更 email = EmailMessage(subject=' 件名です', ...) email.encoding
= 'iso-2022-jp'
結果 受信メール Content-Type: text/plain; charset="iso-2022-jp" Content-Transfer-Encoding: 7bit Subject: =?iso-2022-jp?b?GyRCN29MPiRHJDkbKEI=?=
Thanks!!