Slide 1

Slide 1 text

Django でのメール送信 ~ 設定からテストまで ~ DjangoCongress JP 2019 talk 2019/5/18 @thinkAmi

Slide 2

Slide 2 text

お前誰よ / Who are you ? @thinkAmi Python & Django Blog :メモ的な思考的な (http://thinkami.hatenablog.com/) ( 株) 日本システム技研 PyCon JP 2016-18 Silver Sponsor ギークラボ長野 Python Boot Camp in 長野 みんなのPython 勉強会 in 長野

Slide 3

Slide 3 text

質問

Slide 4

Slide 4 text

Django でメール送信の経験がある方

Slide 5

Slide 5 text

簡単! Django でメール送信

Slide 6

Slide 6 text

設定ファイル # settings.py EMAIL_HOST = 'smtp.example.com' # 送信メールサーバ EMAIL_PORT = '587' # 送信メールポート EMAIL_HOST_USER = 'test_user' # 送信ユーザ EMAIL_HOST_PASSWORD = 'Passw0rd' # 送信パスワード

Slide 7

Slide 7 text

send_mail() 関数の実行 send_mail(subject='my subject', # 件名 message='My Body', # 本文 from_email='[email protected]', # 送信者 recipient_list=['[email protected]']) # 受信者(To) のリスト

Slide 8

Slide 8 text

受信メール

Slide 9

Slide 9 text

メールの送信 send_mail(subject='my subject', # 件名 message='My Body', # 本文 from_email='[email protected]', # 送信者 recipient_list=['[email protected]']) # 受信者(To) のリスト

Slide 10

Slide 10 text

話すこと Django のメール送信の流れ EmailMessage EmailBackend メール送信とunittest ショートカット関数 エラー通知メール エラー通知の仕組み 内容のマスク

Slide 11

Slide 11 text

話さないこと 仕組み以外の話 メールのRFC メール配信サービスの設定 SendGrid Amazon SES

Slide 12

Slide 12 text

注意 このトークを聴いても、Django アプリは作れません スライドは公開 スライド中のソースコードは、一部省略 GitHub に全体を公開済 https://github.com/thinkAmi/DjangoCongress_JP_2019_talk

Slide 13

Slide 13 text

環境 Python 3.7.3 Django 2.2.1 LTS

Slide 14

Slide 14 text

目次 Django のメール送信の流れ unittest ショートカット関数 エラー通知メール

Slide 15

Slide 15 text

Django のメールの仕組み send_mail() 関数を元に理解する

Slide 16

Slide 16 text

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()

Slide 17

Slide 17 text

send_mail() 関数 メール送信のショートカット関数 実際 EmailMessage EmailBackend

Slide 18

Slide 18 text

EmailMessage について

Slide 19

Slide 19 text

EmailMessage の役割 メール属性を持つ EmailBackend を使ってメールを送信

Slide 20

Slide 20 text

EmailMessage のメール属性 from_email, to, cc, bcc, reply_to subject body attachments extra_headers connection EmailBackend

Slide 21

Slide 21 text

EmailMessage とsend_mail() の違い できること どちらでも EmailMessage のみ

Slide 22

Slide 22 text

どちらでもできる例 メールアドレスに表示名を付けたい HTML メールを送信したい メールテンプレートを使いたい

Slide 23

Slide 23 text

メールアドレスの表示名とは from_user

Slide 24

Slide 24 text

メールアドレスに表示名を付ける # EmailMessage の場合 email = EmailMessage( from_email='from_user ', ...) # send_mail() の場合 send_mail(from_email='from user ')

Slide 25

Slide 25 text

受信メール ( ヘッダ) From: from_user

Slide 26

Slide 26 text

HTML メールを送信したい # EmailMessage( のサブクラス) の場合 email = EmailMultiAlternatives( body='My Body', ...) email.attach_alternative('HTML メール です', 'text/html') email.send() # send_mail() の場合 send_mail(html_message='HTML メール です')

Slide 27

Slide 27 text

受信メール テキスト表示 HTML 表示

Slide 28

Slide 28 text

メールでテンプレートを使いたい render_to_string() 関数と組み合わせる

Slide 29

Slide 29 text

テンプレートエンジンを変更 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')], }, ]

Slide 30

Slide 30 text

テキストメールのテンプレート # base.txt メールのベースです {% block mail_content %} {% endblock %} {% include './signature.txt' %} # content.txt {% extends './base.txt' %} {% block mail_content %} {{ message }} {% endblock %} # signature.txt from thinkAmi

Slide 31

Slide 31 text

テンプレートを利用して送信 template_body = render_to_string('mail/content.txt', context={'message': ' 埋め込みます'}) # EmailMessage の場合 email = EmailMessage(body=template_body, ...) # send_mail() の場合 send_mail(message=template_body, ...)

Slide 32

Slide 32 text

受信メール

Slide 33

Slide 33 text

EmailMessage のみできる例 添付ファイル

Slide 34

Slide 34 text

添付ファイル EmailMessage のメソッドを使用 関数 引数 添付ファイル名 __init__() MIMEBase 系, tuple ( *) 指定可 attach() 〃 〃 attach_file() ファイルシステム上のパス ファイルシステムと同一 * tuple(filename, content, mimetype)

Slide 35

Slide 35 text

メソッドの使い方 # 静的ディレクトリにあるファイルを添付する 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()

Slide 36

Slide 36 text

__init__ での結果

Slide 37

Slide 37 text

attach_file での結果

Slide 38

Slide 38 text

EmailBackend について

Slide 39

Slide 39 text

EmailBackend とは メールを送信する手段 拡張や自作も可能

Slide 40

Slide 40 text

EmailBackend の種類 Django 標準 django.core.mail.backends メール内容をどこに出力するかで使い分け SMTP コンソール ファイル インメモリ ダミー

Slide 41

Slide 41 text

EmailBackend の指定 settings の EMAIL_BACKEND デフォルト SMTP

Slide 42

Slide 42 text

EmailBackend に必要な設定 EmailBackend により異なる # SMTP 時のsettings EMAIL_HOST = 'smtp.example.com' # 送信メールサーバ EMAIL_PORT = '587' # 送信メールポート EMAIL_HOST_USER = 'test_user' # 送信ユーザ EMAIL_HOST_PASSWORD = 'Passw0rd' # 送信パスワード

Slide 43

Slide 43 text

EmailBackend の拡張 例 console.EmailBackend でも、件名を日本語で見たい Subject: =?utf-8?b?5Lu25ZCN44Gn44GZ?=

Slide 44

Slide 44 text

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)

Slide 45

Slide 45 text

settings EMAIL_BACKEND = 'myapp.email_backends.ReadableSubjectEmailBackend'

Slide 46

Slide 46 text

結果 Subject ( 日本語表示): ジャンゴ Content-Type: text/plain; charset="utf-8" Subject: =?utf-8?b?44K444Oj44Oz44K0?=

Slide 47

Slide 47 text

EmailBackend を自作 send_messages() メソッドを実装したクラスを用意 django.core.mail.backends.base.BaseEmailBackend を継承すると楽

Slide 48

Slide 48 text

例 メール送信ではなく、Slack に投稿したい SlackBackend

Slide 49

Slide 49 text

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, )

Slide 50

Slide 50 text

settings EMAIL_BACKEND = 'myapp.email_backends.SlackBackend' SLACK_OAUTH_ACCESS_TOKEN = 'Your OAuth Access Token' SLACK_CHANNEL = 'Your Channel ID'

Slide 51

Slide 51 text

メールの送信処理 EmailMessage( subject='Django', body='DjangoCongress JP 2019 にようこそ!', from_email=settings.REAL_MAIL_FROM, to=settings.REAL_MAIL_TO).send()

Slide 52

Slide 52 text

結果

Slide 53

Slide 53 text

ここまでのまとめ EmailMessage でメール情報を作る EmailBackend を使って送信する

Slide 54

Slide 54 text

目次 Django のメール送信の流れ unittest ショートカット関数 エラー通知メール

Slide 55

Slide 55 text

開発時の悩み メール内容の確認をしたい 実メールサーバに送信したくない

Slide 56

Slide 56 text

実メールサーバ以外での確認 EmailBackend を差し替え コンソールやファイルなど Python のSMTP サーバ DebuggingServer を宛先にして送信

Slide 57

Slide 57 text

unittest 時 メールは送信されない EmailBackend が locmem.EmailBackend に差し替わる

Slide 58

Slide 58 text

差し替えているところ 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 = []

Slide 59

Slide 59 text

locmem に差し替わると django.core.mail.outbox に、EmailMessage のリストが設定

Slide 60

Slide 60 text

テスト対象の関数 def my_send_mail(encoding='utf-8', has_attachment=False): msg = EmailMessage( subject=' 件名', body=' 本文', from_email=' 差出人 ', to=[' 送信先1 ', ' 送信先2 '], cc=[' シーシー '], bcc=[' ビーシーシー '], reply_to=[' 返信先 '], headers={'Sender': '[email protected]'}) if has_attachment: # 静的ディレクトリにあるファイルを添付する img = pathlib.Path(settings.STATICFILES_DIRS[0]).joinpath( 'images', 'shinanogold.png') msg.attach_file(img) msg.send()

Slide 61

Slide 61 text

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)

Slide 62

Slide 62 text

各フィールドの検証 def test_mail_fields(self): self._callFUT() actual = mail.outbox[0] self.assertEqual(actual.subject, ' 件名') self.assertEqual(actual.body, ' 本文') self.assertEqual(actual.from_email, ' 差出人 ') # 宛先系はlist として設定される self.assertEqual(actual.to, [' 送信先1 ', ' 送信先2 '],) # 追加ヘッダも含まれる self.assertEqual(actual.extra_headers['Sender'], '[email protected]')

Slide 63

Slide 63 text

添付ファイルがある場合 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)

Slide 64

Slide 64 text

ここまでのまとめ unittest では、メールは送信されない django.core.mail.outbox に、EmailMessage のリストが設定

Slide 65

Slide 65 text

目次 Django のメール送信の流れ unittest ショートカット関数 エラー通知メール

Slide 66

Slide 66 text

ショートカット関数 User モデル email_user() 一括送信 send_mass_mail() 管理者宛送信 mail_admins() mail_managers()

Slide 67

Slide 67 text

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(), )

Slide 68

Slide 68 text

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())

Slide 69

Slide 69 text

注意点 機能 EmailMessage send_mail() send_mass_mail() 宛先 To, Cc, Bcc, Reply-To To To HTML メール 添付ファイル

Slide 70

Slide 70 text

管理者宛メールのショートカット関数 mail_admins() settings.ADMINS 宛にメール送信 mail_managers() settings.MANAGERS 宛にメール送信

Slide 71

Slide 71 text

ADMINS とMANAGERS の違い 公式ドキュメントより ADMINS = site admins MANAGERS = site managers Django のエラー通知機能にて、両者の違いを見る

Slide 72

Slide 72 text

目次 Django のメール送信の流れ unittest ショートカット関数 エラー通知メール

Slide 73

Slide 73 text

Django のエラー通知メール Django アプリでエラーが発生した時に、メールで通知 サーバーエラー HTTP 500 リンク切れ Referer あり & HTTP 404 存在しないページに直接アクセスした時は通知しない

Slide 74

Slide 74 text

サーバーエラー通知メールの仕組み # 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']}}

Slide 75

Slide 75 text

設定の変更例 エラー通知メールは常にコンソール出力 LOGGING = { 'handlers': { 'mail_admins': { ... 'email_backend': # 追加 'django.core.mail.backends.console.EmailBackend'}

Slide 76

Slide 76 text

自作 AdminEmailHandler を継承し、 send_mail() をオーバーライド

Slide 77

Slide 77 text

例 エラーレポートの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()

Slide 78

Slide 78 text

結果

Slide 79

Slide 79 text

リンク切れ通知メールの仕組み django.middleware.common.BrokenLinkEmailsMiddleware

Slide 80

Slide 80 text

通知先の違い サーバーエラー MAIL_ADMINS リンク切れ MAIL_MANAGERS

Slide 81

Slide 81 text

サーバーエラーの通知設定 # 本番運用モードにする DEBUG = False # 送信先の site admins のメールアドレスを設定 # (' メールアドレスコメント', ' メールアドレス') ADMINS = [('Admin1', '[email protected]')] # 他、使用するEmailBackend の設定を行う

Slide 82

Slide 82 text

サーバーエラー通知設定 ( 任意) # 送信元のメールアドレス # デフォルト:root@localhost SERVER_EMAIL = '[email protected]' # エラー通知メールの件名のPrefix # デフォルト:[Django] EMAIL_SUBJECT_PREFIX = '[Hello]'

Slide 83

Slide 83 text

動作確認 $ curl http://localhost:8000/force_500

Server Error (500)

Slide 84

Slide 84 text

受信メール エラー内容や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'

Slide 85

Slide 85 text

ローカル変数もほしい時 単純な方法:Debug=True の時のHTML を含める # settings DEFAULT_LOGGING['handlers']['mail_admins']['include_html'] = True

Slide 86

Slide 86 text

動作確認 例外を発生するView def force_500(request): my_local_value = ' ハロー' raise Exception('Error')

Slide 87

Slide 87 text

ローカル変数を含むサーバーエラー通知メール Subject: [Hello]ERROR (EXTERNAL IP): Internal Server Error: /force_500 From: [email protected] To: [email protected] ... my_local_value
'
ハロー'

Slide 88

Slide 88 text

サーバーエラー通知メールの注意点 サーバー情報が漏洩する可能性あり settings ローカル変数 POST データ 必要に応じて、内容をマスクする

Slide 89

Slide 89 text

settings に対する、自動マスク機能 以下のパターンを含む設定名は、Django が自動でマスク API KEY PASS SECRET SIGNATURE TOKEN

Slide 90

Slide 90 text

動作確認 settings DJANGO_CONGRESS_PASSPORT = '123' DJANGO_CONGRESS_PASSWORD = '456' DJANGO_CONGRESS_PASTA = '789'

Slide 91

Slide 91 text

結果 DJANGO_CONGRESS_PASSPORT =3D '********************' DJANGO_CONGRESS_PASSWORD =3D '********************' DJANGO_CONGRESS_PASTA =3D '789'

Slide 92

Slide 92 text

ローカル変数に対するマスク 以下の場合、デフォルトではそのまま表示 DEFAULT_LOGGING['handlers']['mail_admins']['include_html'] = True @sensitive_variables デコレータを使う

Slide 93

Slide 93 text

一部をマスクする例 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

Slide 94

Slide 94 text

一部マスクの結果
  • Local Vars

    conference
    'DjangoCongress'
    region
    '********************'
    year
    '********************'
  • Slide 95

    Slide 95 text

    すべてをマスクする例 View @method_decorator(sensitive_variables(), name='dispatch') class AllMaskedLocalVariableView(TemplateView): ...

    Slide 96

    Slide 96 text

    エラー通知に含まれるPOST データ デフォルトでは、そのまま表示 Exception Type: Exception at /post_parameters POST: apple =3D 'Shinano Gold' grape =3D 'Shine Muscat' pear =3D 'Southern Suite' @sensitive_post_parameters デコレータを使う

    Slide 97

    Slide 97 text

    一部をマスクする例 View @method_decorator(sensitive_post_parameters('grape', 'pear'), name='dispatch') class MaskedPostParameterView(FormView): def post(self, request, *args, **kwargs): raise Exception

    Slide 98

    Slide 98 text

    一部マスクの結果 Request information: POST: apple =3D 'Shinano Gold' grape =3D '********************' pear =3D '********************'

    Slide 99

    Slide 99 text

    すべてをマスクする例 View @method_decorator(sensitive_post_parameters(), name='dispatch') class AllMaskedPostParameterView(FormView): ...

    Slide 100

    Slide 100 text

    ちなみに ローカル変数とPOST データを両方マスク @method_decorator(sensitive_variables('year'), name='dispatch') @method_decorator(sensitive_post_parameters('grape'), name='dispatch') class DoubleMaskedView(FormView): ...

    Slide 101

    Slide 101 text

    マスク機能を自作 デコレータをView に付けたくない 一括でマスクしたい 自作のマスク機能を作成することで対応可能 SafeExceptionReporterFilter のサブクラスが推奨

    Slide 102

    Slide 102 text

    マスク機能 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()

    Slide 103

    Slide 103 text

    自作マスク機能を使用 settings DEFAULT_EXCEPTION_REPORTER_FILTER = 'myapp.reporter_filter.MyReporterFilter'

    Slide 104

    Slide 104 text

    マスク結果 Request information: POST: apple =3D 'Shinano Gold' grape =3D '??????????????????????????????' pear =3D 'Southern Suite' ...
  • Local Vars

    conference
    'DjangoCongress'
    region
    'JP'
    year
    '??????????????????????????????'
  • Slide 105

    Slide 105 text

    リンク切れの通知設定 # 本番運用モードにする DEBUG = False # リンク切れを通知するミドルウェアを追加 MIDDLEWARE += ['django.middleware.common.BrokenLinkEmailsMiddleware'] # 送信先の site managers のメールアドレスを設定 # (' メールアドレスコメント', ' メールアドレス') MANAGERS = [('Manager1', '[email protected]')] # 他、使用するEmailBackend の設定を行う

    Slide 106

    Slide 106 text

    リンク切れの通知設定 ( 任意) # ADMINS と共用 SERVER_EMAIL = '...' EMAIL_SUBJECT_PREFIX = '...' # HTTP404 でもエラーレポートメールを送信したくないURL がある場合は、正規表現で指定 IGNORABLE_404_URLS = [ re.compile(r'^/ignore_404$'), ]

    Slide 107

    Slide 107 text

    動作確認 Referer ありで、HTTP 404 # 実際は1 行 $ curl -H "Referer:http://localhost:8000/breaking_link" http://localhost:8000/force_404

    Not Found

    The requested resource was not found on this server.

    Slide 108

    Slide 108 text

    受信メール 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

    Slide 109

    Slide 109 text

    まとめ メール送信まわりは標準搭載 unittest ショートカット関数 エラー通知 メール送信まわりの機能は、いずれも拡張・自作可 エラー通知メールは、情報漏えいに注意 必要に応じてマスク

    Slide 110

    Slide 110 text

    Enjoy Django email life!!

    Slide 111

    Slide 111 text

    Appendix

    Slide 112

    Slide 112 text

    エンコーディングの変更 メールのエンコーディング 非ASCII 文字を送信するために使用 ヘッダ 件名 Content-Type

    Slide 113

    Slide 113 text

    デフォルトの挙動 件名に日本語をセット email = EmailMessage(subject=' 件名です', ...) 受信メール Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Subject: =?utf-8?b?5Lu25ZCN44Gn44GZ?=

    Slide 114

    Slide 114 text

    メールのエンコーディング設定 settings.DEFAULT_CHARSET utf-8 HttpResponse オブジェクトのエンコーディング

    Slide 115

    Slide 115 text

    EmailMessage オブジェクトの属性 encoding メールのエンコーディングを変更可能

    Slide 116

    Slide 116 text

    例 エンコーディングを ISO-2022-JP に変更 email = EmailMessage(subject=' 件名です', ...) email.encoding = 'iso-2022-jp'

    Slide 117

    Slide 117 text

    結果 受信メール Content-Type: text/plain; charset="iso-2022-jp" Content-Transfer-Encoding: 7bit Subject: =?iso-2022-jp?b?GyRCN29MPiRHJDkbKEI=?=

    Slide 118

    Slide 118 text

    Thanks!!