- AWS
Amazon SESメール送信|AWS CLIとboto3の手順・エラー対処
Amazon SES(Simple Email Service)でメールを送る手順は、「送信元を検証する → 検証状態を確認する → send-email を呼ぶ」の3ステップです。 はじめはサンドボックスという試用の状態になっていて、宛先も検証済みのアドレスに限られます。ここを知らずに進めると、最初の1通で MessageRejected に出会います。
この記事では、検証から送信、添付つきの送信、SESv2 での送信までを AWS CLI と boto3 の対で示し、エラーコード別の対処と、サンドボックスの制限・上限の確認方法を表にまとめます。
確認環境:Ubuntu 24.04 / AWS CLI 1.46.1 / Python 3.11 / boto3 1.43。AWS の API をローカルで再現するツール(moto 5.2.3)で実行しました。掲載するコマンドとコードは、実際の AWS でそのまま使える形(接続先の指定なし)で書いています。検証メールの受信、DNS の反映、サンドボックスの制限など AWS 側の動きは、AWS 公式ドキュメントの記載にもとづきます。
Amazon SESメール送信の早見表
| やりたいこと | AWS CLI(SES v1) | boto3 |
|---|---|---|
| メールアドレスを検証する | aws ses verify-email-identity --email-address アドレス |
ses.verify_email_identity(EmailAddress=...) |
| ドメインを検証する(DKIM) | aws ses verify-domain-dkim --domain ドメイン |
ses.verify_domain_dkim(Domain=...) |
| 検証状態を見る | aws ses get-identity-verification-attributes --identities ... |
ses.get_identity_verification_attributes(Identities=[...]) |
| 検証済みの一覧 | aws ses list-identities |
ses.list_identities() |
| テキスト / HTML を送る | aws ses send-email --from ... --to ... --subject ... --text ... |
ses.send_email(Source=..., Destination=..., Message=...) |
| 添付つきで送る | aws ses send-raw-email --raw-message file://raw.json |
ses.send_raw_email(RawMessage={"Data": ...}) |
| 上限と送信数を見る | aws ses get-send-quota |
ses.get_send_quota() |
| SESv2 で送る | aws sesv2 send-email --from-email-address ... --content file://... |
boto3.client("sesv2").send_email(...) |
| つまずきやすい点 | 内容 |
|---|---|
| 検証はリージョンごと | 東京で検証したアドレスは、ほかのリージョンでは別に検証が必要 |
| サンドボックス | 宛先も検証済みに限る/24時間に200通/1秒に1通 |
| 添付ファイル | send-email では付けられない。MIME を組み立てて raw で送る |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Amazon SESメール送信|AWS CLIとboto3の手順・エラー対処」
手順1:送信元を検証する
SES は、検証を済ませたメールアドレスまたはドメイン(まとめて ID と呼びます)からだけ送信できます。
| 検証の種類 | 方法 | 送れる範囲 | 向く場面 |
|---|---|---|---|
| メールアドレス | 届いた確認メールのリンクを開く | そのアドレス1つ | 動作確認、少数の送信元 |
| ドメイン | DNS に CNAME レコードを3つ登録する(DKIM) | そのドメインとサブドメインの全アドレス | 本番運用、複数の送信元アドレス |
メールアドレスを検証する
aws ses verify-email-identity --email-address sender@example.com
成功時は何も表示されません。AWS公式ドキュメントによると、このあと SES から確認メールが届き、メール内のリンクを開くと検証が完了します。リンクの有効期間は24時間です。メールアドレスは大文字と小文字が区別されるため、sender@EXAMPLE.com で検証した場合は、その表記のまま送信元に指定します。
boto3 では次のとおりです。
import boto3
ses = boto3.client("ses", region_name="ap-northeast-1")
ses.verify_email_identity(EmailAddress="sender@example.com")
ドメインを検証する
aws ses verify-domain-dkim --domain example.com
{
"DkimTokens": [
"vvjuipp74whm76gqoni7qmwwn4w4qusjiainivf6sf",
"3frqe7jn4obpuxjpwpolz6ipb3k5nvt2nhjpik2oy",
"wrqplteh7oodxnad7hsl4mixg2uavzneazxv5sxi2"
]
}
返ってきた3つのトークンそれぞれについて、DNS に次の CNAME レコードを登録します(AWS CLI リファレンスの記載)。
| 項目 | 値 |
|---|---|
| Name | トークン._domainkey.example.com |
| Type | CNAME |
| Value | トークン.dkim.amazonses.com |
SES が3つのレコードを検出すると検証が完了し、そのドメインからのメールに DKIM 署名が付きます。検出までは最長72時間かかることがあると案内されています。ドメインを検証すると、info@example.com や news@mail.example.com のように、そのドメインとサブドメインのどのアドレスからも送れます。
手順2:検証状態を確認する
aws ses get-identity-verification-attributes --identities example.com
{
"VerificationAttributes": {
"example.com": {
"VerificationStatus": "Success",
"VerificationToken": "ILQMESfEW0p6i6gIJcEWvO65TP5hg6B99hGFZ2lxrIs="
}
}
}
VerificationStatus が Success になっていれば送信できます。メールアドレスの場合は、リンクを開くまで Pending です(AWS CLI リファレンスの出力例)。
{
"VerificationAttributes": {
"user1@example.com": {
"VerificationStatus": "Success"
},
"user2@example.com": {
"VerificationStatus": "Pending"
}
}
}
| VerificationStatus | 意味 | 次にすること |
|---|---|---|
Pending |
確認待ち | 確認メールのリンクを開く/DNS の反映を待つ |
Success |
検証済み | 送信できる |
Failed |
検証が完了しなかった | もう一度検証を依頼する。DNS レコードの値を見直す |
TemporaryFailure |
一時的に確認できなかった | 時間をおいて再確認 |
NotStarted |
検証が始まっていない | 検証コマンドを実行する |
DKIM の状態は別のコマンドで見られます。
aws ses get-identity-dkim-attributes --identities example.com
{
"DkimAttributes": {
"example.com": {
"DkimEnabled": true,
"DkimVerificationStatus": "Success",
"DkimTokens": [
"vvjuipp74whm76gqoni7qmwwn4w4qusjiainivf6sf",
"3frqe7jn4obpuxjpwpolz6ipb3k5nvt2nhjpik2oy",
"wrqplteh7oodxnad7hsl4mixg2uavzneazxv5sxi2"
]
}
}
}
登録済みの ID の一覧です。--identity-type EmailAddress または Domain で絞れます。
aws ses list-identities
aws ses list-identities --identity-type EmailAddress
{
"Identities": [
"sender@example.com",
"example.com"
]
}
{
"Identities": [
"sender@example.com"
]
}
手順3:send-email で送る(AWS CLI)
オプションだけで送る
aws ses send-email \
--from sender@example.com \
--to user@example.net \
--subject "件名" \
--text "本文です"
{
"MessageId": "apgiydlurylfpkgu-jwamricz-oslg-eqkg-vcve-ovxvfjfejydg-tfhbkf"
}
MessageId が返れば、SES が送信を受け付けています(値は送信ごとに変わります)。ドメインを検証済みなら、そのドメインのアドレスを表示名つきで送信元にできます。--from に書く表示名は英数字にします(日本語の表示名は、後述の raw 送信で MIME エンコードして付けます)。
aws ses send-email \
--from "Info Desk <info@example.com>" \
--to user@example.net \
--subject "ドメイン検証済み" \
--text "本文です"
JSONファイルで送る(HTML・CC つき)
テキストと HTML の両方を入れるときは、本文を message.json に分けると読みやすくなります。
{
"Subject": {"Data": "【テスト】SESからの送信", "Charset": "UTF-8"},
"Body": {
"Text": {"Data": "AWS CLI から送ったテキスト本文です。", "Charset": "UTF-8"},
"Html": {"Data": "<p>AWS CLI から送った <b>HTML</b> 本文です。</p>", "Charset": "UTF-8"}
}
}
aws ses send-email \
--from sender@example.com \
--destination ToAddresses=user@example.net,CcAddresses=cc@example.net \
--message file://message.json
{
"MessageId": "zsjyffgsrffrdbwv-mankxidb-pyec-gyhs-qxok-hglxhqpofghw-tvgnsn"
}
HTML を表示しないメールソフトではテキスト側が使われるので、両方を入れておくと読み手の環境を選びません。
boto3 で送る(send_email)
# send_basic.py
import boto3
from botocore.exceptions import ClientError
ses = boto3.client("ses", region_name="ap-northeast-1")
def send(sender, to, subject, text, html=None):
body = {"Text": {"Data": text, "Charset": "UTF-8"}}
if html:
body["Html"] = {"Data": html, "Charset": "UTF-8"}
try:
res = ses.send_email(
Source=sender,
Destination={"ToAddresses": to},
Message={"Subject": {"Data": subject, "Charset": "UTF-8"}, "Body": body},
ReplyToAddresses=["support@example.com"],
)
print("送信OK:", res["MessageId"])
return res["MessageId"]
except ClientError as e:
err = e.response["Error"]
print("送信NG:", err["Code"])
return None
send("sender@example.com", ["user@example.net"], "ご登録ありがとうございます",
"テキスト本文です。", "<p><b>HTML</b>本文です。</p>")
send("other@example.org", ["user@example.net"], "検証していない送信元", "本文")
送信OK: cwbivpillhysihlq-hsskbcll-xezz-lfum-fssi-vscsynqrdhgc-jdoqul
送信NG: MessageRejected
1通目は検証済みの sender@example.com から、2通目は検証していない other@example.org からの送信です。2通目は MessageRejected になりました。エラーの本文は err["Message"] に入っています。
検証していない送信元で出る MessageRejected
検証を済ませていない送信元を指定すると、AWS CLI では次の形のエラーになります。
aws ses send-email --from other@example.org --to user@example.net --subject "x" --text "y"
An error occurred (MessageRejected) when calling the SendEmail operation: Email address is not verified. The following identities failed the check in region リージョン名: アドレス1, アドレス2
メッセージの文面は、AWS公式ドキュメントのトラブルシューティングに載っている形です。末尾に並ぶアドレスが「検証が済んでいない ID」 なので、ここを見れば直す対象がわかります。
| 末尾に出たアドレス | 原因 | 直し方 |
|---|---|---|
| 送信元(From / Source / Return-Path) | 送信元が検証されていない | そのアドレスかドメインを検証する |
| 宛先 | アカウントがサンドボックスにあり、宛先が検証されていない | 宛先を検証する、または本番利用を申請する |
| 検証したはずのアドレス | 別のリージョンで検証している | 送信に使うリージョンで検証する(--region と region_name をそろえる) |
| 大文字小文字が違うアドレス | メールアドレスは大文字小文字が区別される | 検証したときの表記で指定する |
サンドボックスの制限と解除申請
新しいアカウントの SES は、リージョンごとにサンドボックスの状態から始まります。AWS公式ドキュメントに書かれている制限は次のとおりです。
| 項目 | サンドボックス | 本番利用が認められたあと |
|---|---|---|
| 宛先 | 検証済みのアドレス・ドメイン、または SES のメールボックスシミュレーターだけ | 検証していない宛先にも送れる |
| 24時間の送信数 | 最大200通 | アカウントごとの上限(get-send-quota で確認) |
| 1秒あたりの送信数 | 最大1通 | アカウントごとの上限 |
| 送信元 | 検証が必要 | 検証が必要(変わらない) |
| 適用範囲 | リージョンごと | 申請したリージョンごと |
送信元の検証は、サンドボックスを出たあとも必要です。 解除されるのは「宛先の検証」と「送信数の上限」です。
解除(本番利用)の申請は、SES コンソールの「Account dashboard」から行うか、次のコマンドで行います(AWS公式ドキュメントの例)。
aws sesv2 put-account-details \
--production-access-enabled \
--mail-type TRANSACTIONAL \
--website-url https://example.com \
--additional-contact-email-addresses info@example.com \
--contact-language EN
| オプション | 書く内容 |
|---|---|
--mail-type |
TRANSACTIONAL(登録確認・通知など1通ずつの連絡)または MARKETING(お知らせの一斉配信) |
--website-url |
送信元となるサービスのURL |
--additional-contact-email-addresses |
連絡を受け取る追加のアドレス |
--contact-language |
EN または JA |
申請にあたっては、受け取りに同意した相手にだけ送ること、バウンス(届かなかったメール)と苦情を処理する仕組みを持つことへの同意が求められます。申請後、AWS サポートから24時間以内に最初の回答があると案内されています。送る相手の集め方と、バウンス・苦情への対応方法を先に決めておくと、やり取りが短く済みます。
上限と送信数を確認する(get-send-quota)
aws ses get-send-quota
{
"Max24HourSend": 200.0,
"MaxSendRate": 1.0,
"SentLast24Hours": 4.0
}
| キー | 意味 |
|---|---|
Max24HourSend |
24時間に送れる最大数(サンドボックスでは200) |
MaxSendRate |
1秒あたりに送れる最大数 |
SentLast24Hours |
直近24時間に送った数 |
一斉送信の前に残りを計算しておくと、途中で上限に当たるのを避けられます。
# quota.py
import boto3
ses = boto3.client("ses", region_name="ap-northeast-1")
q = ses.get_send_quota()
left = q["Max24HourSend"] - q["SentLast24Hours"]
print(f"24時間の上限 : {q['Max24HourSend']:.0f} 通")
print(f"送信済み : {q['SentLast24Hours']:.0f} 通")
print(f"残り : {left:.0f} 通")
print(f"1秒あたり : {q['MaxSendRate']:.0f} 通")
24時間の上限 : 200 通
送信済み : 13 通
残り : 187 通
1秒あたり : 1 通
添付つきで送る(send_raw_email)
send_email は件名と本文だけを受け取るため、添付ファイルは付けられません。添付を付けるときは、MIME 形式のメール全体を自分で組み立てて send_raw_email に渡します。 Python 標準ライブラリの email.message.EmailMessage を使うと、日本語の件名やファイル名のエンコードも任せられます。
# send_raw.py
import boto3
from email.message import EmailMessage
from email import message_from_bytes, policy
ses = boto3.client("ses", region_name="ap-northeast-1")
msg = EmailMessage()
msg["Subject"] = "9月分の集計レポート"
msg["From"] = "レポート配信 <sender@example.com>"
msg["To"] = "user@example.net"
msg.set_content("集計結果を添付します。")
msg.add_alternative("<p>集計結果を<b>添付</b>します。</p>", subtype="html")
csv_data = "item,qty\nペン,10\nノート,3\n".encode("utf-8")
msg.add_attachment(csv_data, maintype="text", subtype="csv", filename="レポート_2026-09.csv")
raw = msg.as_bytes()
res = ses.send_raw_email(
Source="sender@example.com",
Destinations=["user@example.net"],
RawMessage={"Data": raw},
)
print("送信OK:", res["MessageId"])
print("MIMEのサイズ:", len(raw), "バイト")
# 組み立てた MIME を読み戻して構造を確かめる
parsed = message_from_bytes(raw, policy=policy.default)
print("件名:", parsed["Subject"])
for part in parsed.walk():
print(" ", part.get_content_type(), part.get_filename() or "")
送信OK: lrogfvcfzbeaikuz-hmgtsquo-nsbo-wpvb-zigm-jbsssbphdunp-ijuebk
MIMEのサイズ: 1044 バイト
件名: 9月分の集計レポート
multipart/mixed
multipart/alternative
text/plain
text/html
text/csv レポート_2026-09.csv
テキストと HTML が multipart/alternative に、その外側に添付の text/csv が並ぶ構造になっています。PDF や画像を付けるときは、ファイルをバイナリで読み、maintype="application", subtype="pdf" のように種類を指定します。メッセージ全体のサイズは、SES v1 の SendEmail では最大10 MBです(APIリファレンスの記載)。
AWS CLI で添付つきを送る
CLI では、MIME の文字列を Data に入れた JSON を作って渡します。JSON 作りは Python に任せるのが確実です。
# make_raw.py
import json
from email.message import EmailMessage
msg = EmailMessage()
msg["Subject"] = "CLI から添付つきで送信"
msg["From"] = "sender@example.com"
msg["To"] = "user@example.net"
msg.set_content("添付を確認してください。")
msg.add_attachment("item,qty\nペン,10\n".encode("utf-8"),
maintype="text", subtype="csv", filename="report.csv")
json.dump({"Data": msg.as_string()}, open("raw.json", "w"))
print(msg.as_string())
Subject: CLI =?utf-8?b?44GL44KJ5re75LuY44Gk44GN44Gn6YCB5L+h?=
From: sender@example.com
To: user@example.net
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="===============2054154367016005447=="
--===============2054154367016005447==
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: base64
5re75LuY44KS56K66KqN44GX44Gm44GP44Gg44GV44GE44CCCg==
--===============2054154367016005447==
Content-Type: text/csv
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="report.csv"
MIME-Version: 1.0
aXRlbSxxdHkK44Oa44OzLDEwCg==
--===============2054154367016005447==--
python3 make_raw.py
aws ses send-raw-email --raw-message file://raw.json
{
"MessageId": "ozuijscddmgyotak-aoaddirn-hgbj-kfop-gtwp-nmpuzkwjbnts-tqicom"
}
上のコマンドは AWS CLI バージョン1(1.46.1)で確かめました。AWS CLI のユーザーガイドによると、バイナリを受け取るパラメーターに file:// で渡した内容は、バージョン1では文字列として読まれ、バージョン2では既定で base64 として扱われます。 バージョン2で同じ raw.json を使うときは、--cli-binary-format raw-in-base64-out を付けてバージョン1と同じ読み方にします。
SESv2 で送る
SES には、従来の API(CLI では aws ses、boto3 では "ses")と、API v2(aws sesv2、"sesv2")の2系統があります。送信そのものはどちらでもできます。
AWS CLI(sesv2 send-email)
v2 では、本文を Content の下の Simple・Raw・Template のどれかに入れます。content.json:
{
"Simple": {
"Subject": {"Data": "【テスト】SESv2からの送信", "Charset": "UTF-8"},
"Body": {
"Text": {"Data": "sesv2 send-email で送った本文です。", "Charset": "UTF-8"}
}
}
}
aws sesv2 send-email \
--from-email-address sender@example.com \
--destination ToAddresses=user@example.net \
--content file://content.json
{
"MessageId": "wjimrzxcbtngofnf-huvqtkma-pvwy-xxhm-jqhj-kxrfjmhmstkp-tvvjrr"
}
boto3(sesv2 の send_email)
v2 では、通常の送信も添付つきの送信も send_email の1つです。添付つきは Content の Raw に MIME を渡します。
# send_v2.py
import boto3
from email.message import EmailMessage
from botocore.exceptions import ClientError
sesv2 = boto3.client("sesv2", region_name="ap-northeast-1")
# 1) Simple:件名と本文を渡すだけ
res = sesv2.send_email(
FromEmailAddress="sender@example.com",
Destination={"ToAddresses": ["user@example.net"]},
Content={"Simple": {
"Subject": {"Data": "SESv2 からのお知らせ", "Charset": "UTF-8"},
"Body": {"Text": {"Data": "テキスト本文です。", "Charset": "UTF-8"},
"Html": {"Data": "<p>HTML本文です。</p>", "Charset": "UTF-8"}},
}},
)
print("Simple:", res["MessageId"])
# 2) Raw:添付つきの MIME をそのまま渡す
msg = EmailMessage()
msg["Subject"] = "添付つき(SESv2)"
msg["From"] = "sender@example.com"
msg["To"] = "user@example.net"
msg.set_content("添付を確認してください。")
msg.add_attachment(b"hello\n", maintype="text", subtype="plain", filename="memo.txt")
res = sesv2.send_email(
FromEmailAddress="sender@example.com",
Destination={"ToAddresses": ["user@example.net"]},
Content={"Raw": {"Data": msg.as_bytes()}},
)
print("Raw:", res["MessageId"])
# 3) 検証していない送信元
try:
sesv2.send_email(
FromEmailAddress="nobody@example.org",
Destination={"ToAddresses": ["user@example.net"]},
Content={"Simple": {"Subject": {"Data": "x"}, "Body": {"Text": {"Data": "y"}}}},
)
except ClientError as e:
print("NG:", e.response["Error"]["Code"])
Simple: oeinzvknpumxqzjw-qvukjtsn-fdcf-uitf-rljz-yhmbzwectjtz-nhwgov
Raw: fpskqzdzfhxuqjio-exlpavpl-davf-rsrq-ghxr-mrzkkfvbcgah-dgbosg
NG: MessageRejected
SES v1 と v2 のコマンド対応表
| やりたいこと | v1(aws ses) |
v2(aws sesv2) |
|---|---|---|
| ID を登録して検証を始める | verify-email-identity / verify-domain-dkim |
create-email-identity --email-identity アドレスまたはドメイン |
| 検証状態を見る | get-identity-verification-attributes |
get-email-identity --email-identity ... |
| ID の一覧 | list-identities |
list-email-identities |
| ID を削除する | delete-identity --identity ... |
delete-email-identity --email-identity ... |
| 通常の送信 | send-email |
send-email(Content.Simple) |
| 添付つきの送信 | send-raw-email |
send-email(Content.Raw) |
| 上限と送信数 | get-send-quota |
get-account(SendQuota) |
| サンドボックスかどうか | (該当なし) | get-account(ProductionAccessEnabled) |
| 本番利用の申請 | (該当なし) | put-account-details |
| 引数の違い | v1 | v2 |
|---|---|---|
| 送信元 | --from(boto3 は Source) |
--from-email-address(FromEmailAddress) |
| 宛先 | --to や --destination |
--destination |
| 本文 | --subject と --text / --html、または --message |
--content(Simple / Raw / Template) |
v2 の get-account は、送信上限に加えて ProductionAccessEnabled(true なら本番利用、false ならサンドボックス)と SendingEnabled を返します(AWS CLI リファレンスの記載)。サンドボックスかどうかをコマンドで確かめたいときは v2 を使います。
v2 の ID 一覧は、種類と検証状態をまとめて返します。
aws sesv2 list-email-identities
{
"EmailIdentities": [
{
"IdentityType": "EMAIL_ADDRESS",
"IdentityName": "sender@example.com",
"SendingEnabled": true,
"VerificationStatus": "SUCCESS"
},
{
"IdentityType": "DOMAIN",
"IdentityName": "example.com",
"SendingEnabled": true,
"VerificationStatus": "SUCCESS"
}
]
}
エラーコード別の対処(ClientError)
boto3 では、SES 側のエラーは ClientError として上がり、e.response["Error"]["Code"] にコードが入ります。「直してから送り直すもの」と「少し待てば通るもの」を分けて扱うのが基本です。
| コード(v1) | v2 での名前 | 起きる状況 | 対処 |
|---|---|---|---|
MessageRejected |
MessageRejected |
送信元(サンドボックスでは宛先も)が検証されていない、内容が受け付けられない | メッセージ末尾のアドレスを検証する。リージョンを確認する |
MailFromDomainNotVerified |
MailFromDomainNotVerifiedException |
カスタム MAIL FROM ドメインの MX レコードを SES が読めない | DNS の MX レコードを確認する |
ConfigurationSetDoesNotExist |
— | 指定した設定セットがない | 設定セット名とリージョンを確認する |
ConfigurationSetSendingPaused |
— | 設定セットの送信が止められている | 設定セットの送信を再開する |
AccountSendingPaused |
SendingPausedException |
アカウントの送信が一時停止されている | AWS からの通知メールを確認し、サポートに連絡する |
| — | AccountSuspendedException |
アカウントの送信が恒久的に制限されている | AWS サポートに連絡する |
| — | TooManyRequestsException |
リクエストが多すぎる | 間隔をあけて再送する |
| — | BadRequestException |
入力が正しくない | 引数の内容を直す |
| — | NotFoundException |
指定したリソースが存在しない | 名前とリージョンを確認する |
メッセージの文面で見分けるものもあります(AWS公式ドキュメントのトラブルシューティングより)。
| メッセージ | 意味 | 対処 |
|---|---|---|
Email address is not verified. … |
ID が検証されていない | 前の節の表を参照 |
Daily message quota exceeded |
24時間の上限に達した | 次の24時間枠まで待つ。上限の引き上げを申請する |
Maximum sending rate exceeded |
1秒あたりの上限を超えた | 送信の間隔をあけて再送する |
There are no recipients specified |
宛先が1つもない | To / CC / BCC のどれかにアドレスを入れる |
There are non-ASCII characters in the email address |
アドレスに日本語などが入っている | 表示名は MIME エンコードする。ドメイン部分は Punycode にする |
Account is paused |
送信が一時停止されている | AWS からの通知を確認する |
これらを1つの関数にまとめた例です。引数の形の誤り(ParamValidationError)は AWS に届く前に boto3 が知らせるので、別に受けます。
# errors.py
import time
import boto3
from botocore.exceptions import ClientError, ParamValidationError
ses = boto3.client("ses", region_name="ap-northeast-1")
RETRY = {"Throttling", "ServiceUnavailable"}
HINT = {
"MessageRejected": "送信元(サンドボックスでは宛先も)の検証状態とリージョンを確認する",
"MailFromDomainNotVerified": "カスタム MAIL FROM ドメインの MX レコードを確認する",
"ConfigurationSetDoesNotExist": "設定セット名とリージョンを確認する",
"ConfigurationSetSendingPaused": "設定セットの送信を再開する",
"AccountSendingPaused": "アカウントの送信が一時停止中。AWS からの通知を確認する",
}
def send_with_retry(max_retries=3, **params):
for attempt in range(max_retries + 1):
try:
return ses.send_email(**params)["MessageId"]
except ParamValidationError as e:
print("引数の形が違います:", str(e).splitlines()[1])
return None
except ClientError as e:
code = e.response["Error"]["Code"]
if code in RETRY and attempt < max_retries:
time.sleep(2 ** attempt)
continue
print(f"{code}: {HINT.get(code, e.response['Error']['Message'])}")
return None
msg = {"Subject": {"Data": "テスト"}, "Body": {"Text": {"Data": "本文"}}}
print(send_with_retry(Source="sender@example.com", Destination={"ToAddresses": ["user@example.net"]}, Message=msg))
print(send_with_retry(Source="other@example.org", Destination={"ToAddresses": ["user@example.net"]}, Message=msg))
print(send_with_retry(Source="sender@example.com", Destination={"ToAddresses": "user@example.net"}, Message=msg))
ygzsfuvexqsdjhxc-kfxqtykz-gyic-tkis-cxgh-dgqaehnvsbkh-vqgvkd
MessageRejected: 送信元(サンドボックスでは宛先も)の検証状態とリージョンを確認する
None
引数の形が違います: Invalid type for parameter Destination.ToAddresses, value: user@example.net, type: <class 'str'>, valid types: <class 'list'>, <class 'tuple'>
None
3つ目は ToAddresses に文字列をそのまま渡した例です。宛先は1件でもリストで渡します。
送信の決まりも合わせて押さえておきます(SES API リファレンスの SendEmail より)。
| 決まり | 内容 |
|---|---|
| 宛先の数 | To・CC・BCC を合わせて1通につき50件まで。それ以上は50件以下に分けて複数回送る |
| 宛先の形式 | 1つでも形式の合わないアドレスがあると、ほかが正しくてもメッセージ全体が受け付けられない |
| サイズ | メッセージは最大10 MB |
| 料金 | 受け付けられなかった送信には課金されない |
よくある質問
Q. 検証したのに MessageRejected になります。
A. 多いのは、検証したリージョンと送信しているリージョンが違う場合です。CLI の --region、boto3 の region_name を、検証したリージョンにそろえます。次に、サンドボックスの場合は宛先も検証済みかを確かめます。
Q. ドメインを検証すれば、メールアドレスの検証は要りませんか? A. 要りません。ドメインを検証すると、そのドメインとサブドメインのどのアドレスからも送れます。
Q. v1 と v2 のどちらを使えばよいですか?
A. 送信はどちらでもできます。サンドボックスの状態確認(get-account)や本番利用の申請(put-account-details)は v2 にあり、添付つきも send-email の1つで済みます。これから書くコードは v2 にそろえると、覚えるコマンドが少なくなります。
Q. MessageId が返れば、相手に届いていますか?
A. MessageId は SES が送信を受け付けたことを示します。相手のメールサーバーに届いたか、バウンスになったかは別の出来事で、設定セットのイベント発行などで追跡します。
まとめ
- 手順は「検証 → 状態確認 → 送信」。
verify-email-identityかverify-domain-dkimで始め、get-identity-verification-attributesでSuccessを確かめる。 - ドメイン検証は CNAME を3つ登録する。 そのドメインのどのアドレスからも送れる。
- サンドボックスでは宛先も検証が必要で、24時間200通・1秒1通。 送信元の検証は本番利用後も続く。
MessageRejectedはメッセージ末尾のアドレスを見る。 送信元か宛先か、リージョン違いかを切り分ける。- 添付は MIME を組み立てて raw で送る。 v1 は
send_raw_email、v2 はsend_emailのContent.Raw。 - 上限は
get-send-quota、サンドボックスかどうかはsesv2 get-accountで確かめる。
参考・出典
- Amazon SES 開発者ガイド「Creating and verifying identities in Amazon SES」「Request production access (Moving out of the Amazon SES sandbox)」「Amazon SES email sending errors」
- Amazon SES API Reference「SendEmail」/Amazon SES API v2 Reference「SendEmail」
- AWS CLI Command Reference「ses verify-domain-dkim」「ses get-identity-verification-attributes」「sesv2 get-account」
- AWS CLI ユーザーガイド「Common parameter types」(バイナリ値の扱い)
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Amazon SESメール送信|AWS CLIとboto3の手順・エラー対処」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。