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期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?