目次

Amazon SES 完全ガイド v2.0

初心者から実務者向けの包括的解説


ドキュメントの目的

本ガイドは以下を対象としています。

  • 初心者向け: Amazon SES とは何か、メール送受信の基本を学びたい方
  • 開発者向け: API / SMTP でメール送信を実装したい方
  • SRE・運用向け: バウンス・苦情管理、レピュテーション監視を実装したい方
  • 意思決定者向け: SendGrid・Mailgun・Postmark・Resend との比較・投資判断

2025-2026 年の Amazon SES 最新動向

  • Mail Manager 新機能(2026年4月):受信メール処理の強化。複雑なメールルーティング・スパムフィルタリングが標準化
  • DMARC 厳格要件(2025年5月 Microsoft 要件):送信者認証(SPF・DKIM・DMARC)の必須化。SES 上での DMARC ポリシー管理
  • Suppression List 自動管理:バウンス・苦情メールの自動抑制リスト登録で送信レピュテーション保護
  • Account-level Reputation Metrics:アカウント全体の送信レピュテーション(バウンス率・苦情率)をリアルタイム監視
  • Dedicated IP 運用最適化:複数 Dedicated IP の一括管理・ウォーミング自動化

目次

  1. 本質・定義
  2. SES が解決する課題
  3. 主な特徴
  4. アーキテクチャ
  5. コアコンポーネント
  6. ドメイン設定と認証
  7. メール送信(SMTP・API)
  8. テンプレート・バルク送信
  9. バウンス・苦情管理
  10. 受信メール処理(Mail Manager)
  11. Configuration Set・Event Destination
  12. Suppression List・レピュテーション管理
  13. VPC Endpoint・セキュリティ
  14. 類似サービス比較表
  15. セキュリティ・ベストプラクティス
  16. トラブルシューティング
  17. 2025-2026 最新動向
  18. コスト最適化
  19. 学習リソース・参考文献
  20. 実装例・チェックリスト
  21. まとめ

本質・定義

定義

Amazon Simple Email Service (SES) は、「$0.10/千通の低コストで大量メール送受信を実現するマネージドメールサービス」。パスワードリセット・注文確認などのトランザクションメール、ニュースレター・キャンペーンメールまで対応。AWS インフラを活用した高い到達率・配信信頼性が特徴。

初心者向けメモ: SES は「あなたのメールサーバー」を AWS が管理してくれるサービス。SMTP インターフェースか API でメール送信するだけで、AWS が配信・レピュテーション管理・バウンス・苦情対応をすべて行う。

このサービスを選ぶ理由

課題 SendGrid・Mailgun SES での解決
大量メール送信のコスト 月額固定 + 従量課金で高額化 $0.10/千通(EC2 から無料)で業界最安
送信レピュテーション管理 ベンダー頼み Dedicated IP・送信プール・自動抑制で自社管理
AWS との統合 API のみ Lambda・SNS・S3・EventBridge とネイティブ統合
受信メール処理 オプション(有料) S3 保存・Lambda トリガー・Mail Manager 標準
DMARC・DKIM 設定 UI ウィザード Route 53 と簡単統合・Easy DKIM
月 100 万通以上の送信 割高 SES に切り替えで 70-80% コスト削減

SES が解決する課題

  1. メール配信の信頼性:AWS の高品質インフラで 99%+ の到達率を実現
  2. レピュテーション管理:バウンス・苦情を自動監視。アカウント停止を防止
  3. 大規模メール送信:月間数億通の送信でも自動スケール
  4. メール受信の自動化:受信メール → S3 → Lambda で処理パイプライン構築
  5. 送信者認証:SPF・DKIM・DMARC 統合で詐欺・フィッシング対策

主な特徴

特徴 説明
SMTP インターフェース 既存のメール送信コード(Rails・Django・PHP)を最小変更で移行
API(v2) JSON ベースの REST API。バッチ送信・テンプレート変数対応
Configuration Set バウンス・苦情・開封・クリック追跡を per-email 単位で設定
Event Destination バウンス・苦情 → SNS・CloudWatch・Kinesis Firehose・Pinpoint に自動転送
Suppression List 永続的なバウンス・苦情メールを自動登録。再送信防止
Dedicated IP 自社専用 IP で送信レピュテーション独占(オプション)
Mail Manager 受信メール → S3・Lambda・SNS への自動ルーティング
DMARC・DKIM・SPF 送信者認証を Route 53・Easy DKIM で簡単設定
テンプレート メールテンプレート管理。変数置き換えで個人化メール送信
バルク送信 最大数百万件のメールアドレスに一括送信
graph TB
    subgraph Application["アプリケーション層"]
        APIGateway["API Gateway"]
        Lambda["Lambda 関数"]
        ECS["ECS タスク"]
    end
    
    subgraph SES["Amazon SES"]
        SMTPEndpoint["SMTP エンドポイント"]
        APIv2["API v2"]
        ConfigSet["Configuration Set"]
        Templates["Email Templates"]
        SuppressionList["Suppression List"]
    end
    
    subgraph Outbound["送信(Outbound)"]
        DNSValidation["DNS Validation<br/>SPF / DKIM / DMARC"]
        SendQueue["Send Queue<br/>Rate Limiting"]
        Recipients["受信者メールサーバー"]
    end
    
    subgraph Inbound["受信(Inbound)"]
        InboundMessages["受信メッセージ"]
        MailManager["Mail Manager"]
        Rules["処理ルール<br/>S3 / Lambda / SNS"]
    end
    
    subgraph Monitoring["監視・追跡"]
        ConfigEvents["Configuration Events<br/>Bounce / Complaint / Open / Click"]
        SNSNotify["SNS 通知"]
        CloudWatchMetrics["CloudWatch メトリクス"]
    end
    
    APIGateway --> APIv2
    Lambda --> SMTPEndpoint
    ECS --> SMTPEndpoint
    
    APIv2 --> ConfigSet
    ConfigSet --> Templates
    ConfigSet --> SuppressionList
    
    SMTPEndpoint --> DNSValidation
    DNSValidation --> SendQueue
    SendQueue --> Recipients
    
    InboundMessages --> MailManager
    MailManager --> Rules
    
    ConfigSet --> ConfigEvents
    ConfigEvents --> SNSNotify
    ConfigEvents --> CloudWatchMetrics
    
    style SES fill:#ccffcc
    style Outbound fill:#e1f5ff
    style Inbound fill:#f0f4c3

アーキテクチャ

送信フロー(Outbound)

【アプリケーション層】
├── SDK / API / SMTP
│   ├── Python boto3 / boto
│   ├── Ruby aws-sdk
│   ├── Node.js AWS SDK
│   └── Rails ActionMailer / Django

【SES サービス層】
├── SMTP / API ゲートウェイ
├── 認証・認可(IAM)
├── メールキュー・レート制限
├── 送信レピュテーション管理

【DNS 認証・配信】
├── SPF レコード検証
├── DKIM 署名
├── DMARC ポリシー確認
├── TLS 暗号化送信

【イベント追跡】
├── Bounce(永続的・一時的)
├── Complaint(苦情)
├── Delivery(配信成功)
├── Open / Click(クリック追跡)

【通知・保存】
├── SNS トピック
├── CloudWatch Logs
├── Kinesis Firehose
└── Pinpoint(マーケティング統合)

受信フロー(Inbound)

【外部メールサーバー】
    ↓
【Amazon SES 受信エンドポイント】
├── スパム・ウイルス スキャン
├── DKIM / SPF 検証
└── メール受け入れ

【Mail Manager(新機能 2025年)】
├── 複雑なルーティングルール
├── スパム / フィッシング フィルタ
├── メール操作(転送・削除・マーク)
└── コンテンツ フィルタリング

【アクション実行】
├── S3 に .eml 形式で保存
├── Lambda トリガー(メール解析)
├── SNS 発行(通知)
├── WorkMail 転送
└── SQS キューイング

コアコンポーネント

1. SMTP インターフェース

既存の Ruby on Rails・Django・PHP のメール設定を最小変更で SES に接続可能。

Rails(ActionMailer)の例

# config/environments/production.rb
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
  address: 'email-smtp.ap-northeast-1.amazonaws.com',
  port: 587,
  user_name: 'your-smtp-username',
  password: 'your-smtp-password',
  authentication: :login,
  enable_starttls_auto: true
}

2. REST API(SESv2)

JSON ベースの送信。テンプレート変数・個別ヘッダ設定・バルク送信に対応。

3. Configuration Set

バウンス・苦情・開封・クリック イベントの追跡・タグ付け。per-email 単位でイベント分類。

aws sesv2 create-configuration-set \
  --configuration-set-name production-config

# イベント監視先を SNS に設定
aws sesv2 create-configuration-set-event-destination \
  --configuration-set-name production-config \
  --event-destination-name bounce-tracking \
  --event-destination '{
    "Enabled": true,
    "MatchingEventTypes": ["BOUNCE", "COMPLAINT"],
    "SnsDestination": {
      "TopicArn": "arn:aws:sns:ap-northeast-1:123456789012:ses-events"
    }
  }'

4. Email Identity(ドメイン・メールアドレス認証)

送信元ドメイン・メールアドレスの所有権確認。DKIM・SPF・DMARC 設定。

# ドメイン検証
aws sesv2 create-email-identity \
  --email-identity example.com \
  --dkim-signing-attributes SigningAttributesOrigin=AWS_SES

# または BYODKIM(お客さんの DKIM キー)
aws sesv2 create-email-identity \
  --email-identity example.com \
  --dkim-signing-attributes \
    SigningAttributesOrigin=EXTERNAL,\
    SigningAttributesSecretKeySelector=my-dkim-secret

5. Suppression List

バウンス・苦情メールアドレスを自動登録。再送信防止。

# 抑制リストにメールアドレスを追加
aws sesv2 put-suppressed-destination \
  --email-address invalid@example.com \
  --reason BOUNCE

# 抑制リストの確認
aws sesv2 get-suppressed-destination \
  --email-address invalid@example.com

ドメイン設定と認証

SPF(Sender Policy Framework)

ドメインから送信できるメールサーバー IP を定義。

# Route 53 / DNS TXT レコード
example.com TXT v=spf1 include:amazonses.com ~all

DKIM(DomainKeys Identified Mail)

メールが改ざんされていないことを暗号署名で証明。

SES による Easy DKIM

# AWS Console で自動生成される CNAME レコード
aws sesv2 get-email-identity \
  --email-identity example.com \
  --query 'DkimAttributes.Tokens'

# 出力:
# [
#   "hzijbeyoxjf4id7qxxxxxxx.dkim.amazonses.com",
#   "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.dkim.amazonses.com",
#   "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.dkim.amazonses.com"
# ]

# これらを DNS CNAME として登録

DMARC(Domain-based Message Authentication, Reporting and Conformance)

SPF・DKIM の結果に基づいて配信ポリシー(Accept / Quarantine / Reject)を定義。

# Route 53 / DNS TXT レコード
_dmarc.example.com TXT v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com

# p=none   : DMARC 失敗でも配信。レポートのみ受け取り(テストモード)
# p=quarantine : DMARC 失敗時にスパムフォルダへ
# p=reject  : DMARC 失敗時に受け取り拒否(最も厳密)

Microsoft 新要件(2025年5月)

  • 5,000 メッセージ/日以上の送信者
  • DMARC p=quarantine 以上の設定が必須
  • SPF・DKIM の両方有効化が必須

メール送信(SMTP・API)

Python SDK による API 送信

import boto3
from botocore.exceptions import ClientError

ses_client = boto3.client('sesv2', region_name='ap-northeast-1')

def send_order_confirmation(to_email: str, order_id: str, amount: float):
    try:
        response = ses_client.send_email(
            FromEmailAddress='noreply@example.com',
            Destination={
                'ToAddresses': [to_email],
                'CcAddresses': ['support@example.com'],
                'BccAddresses': []
            },
            Content={
                'Simple': {
                    'Subject': {
                        'Data': f'ご注文確認 #{order_id}',
                        'Charset': 'UTF-8'
                    },
                    'Body': {
                        'Html': {
                            'Data': f'''
                            <html>
                                <body>
                                    <h1>ご注文ありがとうございます</h1>
                                    <p>注文番号: {order_id}</p>
                                    <p>合計金額: ¥{amount:,.0f}</p>
                                    <p>24時間以内に発送予定です。</p>
                                </body>
                            </html>
                            ''',
                            'Charset': 'UTF-8'
                        },
                        'Text': {
                            'Data': f'注文番号: {order_id}\n合計金額: ¥{amount:,.0f}',
                            'Charset': 'UTF-8'
                        }
                    }
                }
            },
            # Configuration Set の指定
            ConfigurationSetName='production-config',
            # メール追跡用タグ
            EmailTags=[
                {'Name': 'order-id', 'Value': order_id},
                {'Name': 'email-type', 'Value': 'order-confirmation'},
                {'Name': 'campaign', 'Value': 'transactional'}
            ],
            # リスト登録解除リンク(オプション)
            ListManagementOptions={
                'ContactListName': 'monthly-newsletter'
            }
        )
        
        print(f'Email sent. Message ID: {response["MessageId"]}')
        return response['MessageId']
        
    except ClientError as e:
        error_code = e.response['Error']['Code']
        print(f'Error: {error_code}')
        
        if error_code == 'MessageRejected':
            print('メールが受け入れられませんでした。送信レート制限・抑制リスト確認。')
        elif error_code == 'MailFromDomainNotVerified':
            print('送信元ドメインが検証されていません。')
        
        raise

SMTP による送信(既存システム互換)

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

# SES SMTP エンドポイント
HOST = 'email-smtp.ap-northeast-1.amazonaws.com'
PORT = 587
USERNAME = 'your-smtp-username'
PASSWORD = 'your-smtp-password'

server = smtplib.SMTP(HOST, PORT)
server.starttls()
server.login(USERNAME, PASSWORD)

msg = MIMEMultipart('alternative')
msg['Subject'] = 'ご注文確認'
msg['From'] = 'noreply@example.com'
msg['To'] = 'customer@example.com'

text = 'ご注文ありがとうございます。'
html = '<html><body><h1>ご注文ありがとうございます</h1></body></html>'

part1 = MIMEText(text, 'plain')
part2 = MIMEText(html, 'html')

msg.attach(part1)
msg.attach(part2)

server.sendmail('noreply@example.com', ['customer@example.com'], msg.as_string())
server.quit()

テンプレート・バルク送信

Email Template 定義

aws sesv2 create-email-template \
  --template '{
    "TemplateName": "OrderConfirmation",
    "SubjectPart": "ご注文確認 {{ORDER_ID}}",
    "HtmlPart": "<h1>{{CUSTOMER_NAME}} 様へ</h1><p>注文番号: {{ORDER_ID}}</p><p>合計: ¥{{TOTAL_AMOUNT}}</p>",
    "TextPart": "{{CUSTOMER_NAME}} 様へ\n注文番号: {{ORDER_ID}}\n合計: ¥{{TOTAL_AMOUNT}}"
  }'

バルクメール送信

import json

response = ses_client.send_bulk_templated_email(
    FromEmailAddress='noreply@example.com',
    DefaultTemplate='OrderConfirmation',
    DefaultTemplateData=json.dumps({
        'CUSTOMER_NAME': 'customer',
        'ORDER_ID': 'ORD-000',
        'TOTAL_AMOUNT': '0'
    }),
    BulkEmailEntries=[
        {
            'Destination': {
                'ToAddresses': ['user1@example.com']
            },
            'ReplacementTemplateData': json.dumps({
                'CUSTOMER_NAME': '田中太郎',
                'ORDER_ID': 'ORD-001',
                'TOTAL_AMOUNT': '10000'
            })
        },
        {
            'Destination': {
                'ToAddresses': ['user2@example.com']
            },
            'ReplacementTemplateData': json.dumps({
                'CUSTOMER_NAME': '山田花子',
                'ORDER_ID': 'ORD-002',
                'TOTAL_AMOUNT': '20000'
            })
        }
    ],
    ConfigurationSetName='production-config'
)

print(f'Bulk email sent. Status: {response["Status"]}')

バウンス・苦情管理

Bounce・Complaint イベント処理

SES が バウンス・苦情を検知したとき、SNS 経由で Lambda が自動応答。

import json
import boto3

ses_client = boto3.client('sesv2')

def handle_bounce_complaint(event, context):
    """
    SES バウンス・苦情通知(SNS → Lambda)
    """
    for record in event['Records']:
        message = json.loads(record['Sns']['Message'])
        notification_type = message.get('notificationType')
        
        if notification_type == 'Bounce':
            bounce_type = message['bounce']['bounceType']
            bounce_subtype = message['bounce'].get('bounceSubType', '')
            
            for recipient in message['bounce']['bouncedRecipients']:
                email = recipient['emailAddress']
                status = recipient.get('status', 'Unknown')
                
                print(f'Bounce - Type: {bounce_type}, Email: {email}, Status: {status}')
                
                # 永続的なバウンス → 抑制リストに登録(再送信防止)
                if bounce_type == 'Permanent':
                    suppress_email(email, reason='BOUNCE')
                    print(f'Permanently bounced email suppressed: {email}')
                
                # 一時的なバウンス → 監視するが削除しない
                elif bounce_type == 'Transient':
                    print(f'Temporary bounce for {email}. Will retry later.')
        
        elif notification_type == 'Complaint':
            for recipient in message['complaint']['complainedRecipients']:
                email = recipient['emailAddress']
                complaint_feedback_type = recipient.get('complaintFeedbackType', 'unknown')
                
                print(f'Complaint - Email: {email}, Type: {complaint_feedback_type}')
                
                # 苦情 → 即座に抑制リストに登録
                suppress_email(email, reason='COMPLAINT')
                print(f'Email suppressed due to complaint: {email}')

def suppress_email(email: str, reason: str):
    """抑制リストにメールを追加"""
    try:
        ses_client.put_suppressed_destination(
            EmailAddress=email,
            Reason=reason  # 'BOUNCE' or 'COMPLAINT'
        )
        print(f'Successfully suppressed {email}')
    except Exception as e:
        print(f'Error suppressing {email}: {str(e)}')
        raise

アカウント停止の防止

def monitor_reputation_metrics():
    """
    SES アカウント全体のバウンス率・苦情率を監視
    """
    import boto3
    
    cloudwatch = boto3.client('cloudwatch')
    
    # バウンス率を確認
    bounce_metric = cloudwatch.get_metric_statistics(
        Namespace='AWS/SES',
        MetricName='Bounce',
        StartTime='...',
        EndTime='...',
        Period=3600,
        Statistics=['Sum']
    )
    
    # 苦情率を確認
    complaint_metric = cloudwatch.get_metric_statistics(
        Namespace='AWS/SES',
        MetricName='Complaint',
        StartTime='...',
        EndTime='...',
        Period=3600,
        Statistics=['Sum']
    )
    
    # バウンス率 > 5% でアラート
    # 苦情率 > 0.1% でアラート(SES アカウント停止の閾値)
    
    print(f'Bounce rate: {bounce_rate}%')
    print(f'Complaint rate: {complaint_rate}%')

受信メール処理(Mail Manager)

Mail Manager ルール(2025年新機能)

複雑なメール処理ロジックを JSON で定義。スパム・フィッシング検知、自動ルーティング。

# 受信ルールセット作成
aws ses create-receipt-rule-set \
  --rule-set-name support-rules

# サポートメール → S3 + Lambda で処理
aws ses create-receipt-rule \
  --rule-set-name support-rules \
  --rule '{
    "Name": "support-email-processing",
    "Enabled": true,
    "Recipients": ["support@example.com"],
    "ScanEnabled": true,
    "TlsPolicy": "Require",
    "Actions": [
      {
        "S3Action": {
          "BucketName": "support-emails-bucket",
          "ObjectKeyPrefix": "raw/",
          "KmsKeyArn": "arn:aws:kms:ap-northeast-1:123456789012:key/xxxxxxxx"
        }
      },
      {
        "LambdaAction": {
          "FunctionArn": "arn:aws:lambda:ap-northeast-1:123456789012:function:ProcessSupportEmail",
          "InvocationType": "Event"
        }
      },
      {
        "SNSAction": {
          "TopicArn": "arn:aws:sns:ap-northeast-1:123456789012:support-notifications"
        }
      }
    ]
  }'

# 初期化ルールセット
aws ses set-active-receipt-rule-set \
  --rule-set-name support-rules

Lambda で受信メール解析

import json
import boto3
import email
from email.parser import Parser

s3 = boto3.client('s3')
dynamodb = boto3.resource('dynamodb')

def process_support_email(event, context):
    """
    S3 の .eml メールファイルを解析してチケット作成
    """
    # イベントから S3 メタデータを取得
    message_id = event['Records'][0]['ses']['mail']['messageId']
    bucket = event['Records'][0]['ses']['mail']['destination'][0].split('@')[0]
    
    # S3 からメールを取得
    obj = s3.get_object(
        Bucket='support-emails-bucket',
        Key=f'raw/{message_id}'
    )
    
    email_content = obj['Body'].read().decode('utf-8')
    email_message = Parser().parsestr(email_content)
    
    # メール情報を抽出
    sender = email_message['From']
    subject = email_message['Subject']
    body = email_message.get_payload(decode=True).decode('utf-8')
    
    # DynamoDB にチケット記録
    table = dynamodb.Table('SupportTickets')
    table.put_item(
        Item={
            'ticket_id': message_id,
            'sender': sender,
            'subject': subject,
            'body': body,
            'status': 'new',
            'created_at': event['Records'][0]['ses']['mail']['timestamp']
        }
    )
    
    print(f'Support ticket created: {message_id}')
    return {'statusCode': 200}

Configuration Set・Event Destination

イベント追跡の有効化

# CloudWatch に送信イベントを記録
aws sesv2 create-configuration-set-event-destination \
  --configuration-set-name production-config \
  --event-destination-name cloudwatch-tracking \
  --event-destination '{
    "Enabled": true,
    "MatchingEventTypes": ["SEND", "DELIVERY", "BOUNCE", "COMPLAINT", "OPEN", "CLICK"],
    "CloudWatchDestination": {
      "DimensionConfigurations": [
        {
          "DimensionName": "email-source",
          "DimensionValueSource": "MESSAGE_TAG",
          "DefaultDimensionValue": "unknown"
        },
        {
          "DimensionName": "email-type",
          "DimensionValueSource": "MESSAGE_TAG",
          "DefaultDimensionValue": "transactional"
        }
      ]
    }
  }'

CloudWatch メトリクスの確認

# 開封率を計算
aws cloudwatch get-metric-statistics \
  --namespace AWS/SES \
  --metric-name Open \
  --dimensions Name=email-type,Value=marketing \
  --start-time 2026-04-01T00:00:00Z \
  --end-time 2026-04-26T23:59:59Z \
  --period 86400 \
  --statistics Sum

Suppression List・レピュテーション管理

抑制リスト戦略

def manage_suppression_list():
    """
    Global Suppression List(AWS 内部の既知バウンスメール)と
    Account-level Suppression List(あなたのアカウント固有)を管理
    """
    import boto3
    
    ses = boto3.client('sesv2')
    
    # グローバル抑制リストの確認
    global_list = ses.get_account_suppression_attributes()
    print(f'Global suppression list enabled: {global_list["SuppressedReasons"]}')
    
    # アカウント抑制リストに追加(手動)
    ses.put_suppressed_destination(
        EmailAddress='hardcoded@invalid.example',
        Reason='BOUNCE'
    )
    
    # アカウント抑制リストから確認
    account_list = ses.list_suppressed_destinations(
        Reason='BOUNCE',
        PageSize=50
    )
    print(f'Suppressed addresses: {len(account_list["SuppressedDestinationSummaries"])}')

Account-level Reputation Metrics

# アカウント全体のバウンス率・苦情率を監視
aws cloudwatch get-metric-statistics \
  --namespace AWS/SES \
  --metric-name Reputation.BounceRate \
  --start-time 2026-04-01T00:00:00Z \
  --end-time 2026-04-26T23:59:59Z \
  --period 3600 \
  --statistics Average

# 苦情率確認(アカウント停止の閾値: 0.1%)
aws cloudwatch get-metric-statistics \
  --namespace AWS/SES \
  --metric-name Reputation.ComplaintRate \
  --start-time 2026-04-01T00:00:00Z \
  --end-time 2026-04-26T23:59:59Z \
  --period 3600 \
  --statistics Average

VPC Endpoint・セキュリティ

VPC Endpoint 経由で SES 接続

プライベートサブネット内から SES へ安全にアクセス。

# VPC Endpoint 作成
aws ec2 create-vpc-endpoint \
  --vpc-id vpc-xxxxxxxx \
  --vpc-endpoint-type Interface \
  --service-name com.amazonaws.ap-northeast-1.email-smtp \
  --subnet-ids subnet-xxxxxxxx subnet-xxxxxxxx

IAM ポリシーの最小権限

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ses:SendEmail",
        "ses:SendRawEmail",
        "sesv2:SendEmail"
      ],
      "Resource": "arn:aws:ses:ap-northeast-1:123456789012:identity/example.com"
    },
    {
      "Effect": "Allow",
      "Action": [
        "secretsmanager:GetSecretValue"
      ],
      "Resource": "arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:ses/*"
    }
  ]
}

類似サービス比較表

項目 SES SendGrid Mailgun Postmark Resend
価格 `0.10/千通 `0.10-0.50/千通 `0.50-1.00/千通 月額`1500~ 月額$20~
AWS 統合 ✅ ネイティブ ❌ API ❌ API ❌ API ❌ API
受信メール ✅(Mail Manager)
API
SMTP
テンプレート
Dedicated IP
到達率監視
Analytics 基本的 高度 中程度 高度 シンプル
開発者体験

判断基準

  • AWS を主要インフラ・コスト重視 → SES
  • マーケティング自動化・高度な分析 → SendGrid
  • メール受信・複雑なルーティング → SES Mail Manager
  • スタートアップ・メールのみ → Resend・Postmark

セキュリティ・ベストプラクティス

✅ 推奨される設定

項目 推奨 理由
Configuration Set 本番 Canary で有効 バウンス・苦情の自動追跡
Suppression List 管理 Lambda で自動登録 バウンス・苦情メールの再送信防止
DMARC p=quarantine 本番環境必須(2025年5月) Microsoft 要件対応
Dedicated IP(大量送信) 月 500万通以上 送信レピュテーション独占
VPC Endpoint プライベート環境 ネットワーク隔離
Secrets Manager SMTP 認証情報管理 ハードコード回避
Event Destination SNS・CloudWatch に通知 イベント追跡・自動対応
メールテンプレート サニタイズ・HTML 検証 XSS・インジェクション攻撃防止

❌ 避けるべきパターン

アンチパターン 問題 対策
バウンス・苦情通知無視 アカウント停止(>5% バウンス・0.1% 苦情) SNS で自動監視・Lambda 自動対応
サンドボックス環境で本番送信 送信できない Production Access 申請
メール抑制リスト未設定 バウンス・苦情メールに再送信 Suppression List 自動登録
DMARC 未設定 スパムフォルダ行き DMARC p=quarantine 以上に設定
一括送信スピード無制限 スロットル・拒否 Rate limit(秒間 14 通)を遵守

トラブルシューティング

症状 原因 対策
「MessageRejected」エラー 抑制リストに載っている・バウンス率超過 抑制リスト確認・バウンス率監視
メール配信されない DKIM・SPF・DMARC 未検証 DNS レコード確認・Easy DKIM 設定
送信レート制限(420 エラー) 秒間 14 通を超えた レート制限に従う・キューイング実装
「MailFromDomainNotVerified」 送信元ドメイン未検証 SES Console でドメイン検証
高い苦情率 メール コンテンツ問題・オプトイン不足 リスト品質確認・オプトイン強化
受信メール処理失敗 Mail Manager ルール設定エラー CloudWatch Logs でルール動作確認

2025-2026 最新動向

1. Mail Manager 新機能(2026年4月)

受信メール処理の強化。複雑なルーティング・スパムフィルタリングが標準化。

2. DMARC 厳格要件(Microsoft 2025年5月)

送信者認証(SPF・DKIM・DMARC)の必須化。p=quarantine 以上が要件。

3. Account-level Reputation Metrics

アカウント全体のバウンス率・苦情率をリアルタイムで監視。アカウント停止を予防的に対応。

4. Dedicated IP 運用最適化

複数 Dedicated IP の一括管理・ウォーミング自動化。IP ローテーション戦略が簡素化。


コスト最適化

料金体系

課金項目 価格
メール送信(EC2 / Lambda 経由) $0.10/千通
メール送信(外部SMTP) $0.10/千通
受信メール $0.10/千通 + S3 保管料
Dedicated IP $24.95/月(追加 IP)

コスト削減例

【月間 100 万通送信】
SendGrid: $500-1000/月
Mailgun: $1000/月
SES: $100/月

削減効果: 80-90% ✅

学習リソース・参考文献

AWS 公式

  1. Amazon SES Developer Guide
  2. SES Email Authentication(DMARC・DKIM・SPF)
  3. SES Global Suppression List
  4. SES Pricing

ブログ・ガイド

  1. AWS Messaging & Targeting Blog
  2. Navigate Bulk Sender Requirements with Amazon SES
  3. DMARC Policy and Authentication(RFC 7489)
  4. DKIM RFC 6376

比較ツール

  1. SendGrid Documentation
  2. Mailgun Documentation
  3. Postmark Documentation
  4. Resend Documentation

実装例・チェックリスト

フェーズ 1:基本設定(1週間)

  • [ ] ドメイン検証(SPF・DKIM・DMARC)
  • [ ] Configuration Set 作成
  • [ ] IAM ロール設定
  • [ ] SMTP 認証情報生成

フェーズ 2:統合・テスト(1-2週間)

  • [ ] API / SMTP 統合テスト
  • [ ] Event Destination 設定(SNS)
  • [ ] バウンス・苦情処理 Lambda 作成
  • [ ] CloudWatch メトリクス確認

フェーズ 3:本番化(1週間)

  • [ ] 抑制リスト管理自動化
  • [ ] Production Access 申請・取得
  • [ ] メール テンプレート定義
  • [ ] アラーム設定(バウンス率・苦情率)

フェーズ 4:運用・最適化(継続)

  • [ ] バウンス率・苦情率 監視
  • [ ] DMARC レポート分析
  • [ ] 定期的な送信レピュテーション確認
  • [ ] Dedicated IP 検討(大規模送信時)

まとめ

Amazon SES は、$0.10/千通の低コストで大量メール送受信を実現するフルマネージドメールサービス。DKIM・SPF・DMARC 設定で高い到達率を実現し、バウンス・苦情の自動管理でレピュテーションを保護する。

主要なポイント

  1. 低コスト:SendGrid 比で 80-90% のコスト削減
  2. AWS ネイティブ統合:Lambda・SNS・S3・EventBridge と深く統合
  3. レピュテーション管理:自動抑制リスト・バウンス・苦情追跡
  4. 受信メール処理:Mail Manager で複雑なルーティング実装
  5. 2025年対応:DMARC p=quarantine 要件への自動対応

SendGrid・Mailgun との比較で、特に 月間 100 万通以上の大量送信・AWS インフラ活用・受信メール処理が必要な場合に SES は最適選択。


最終更新:2026-04-26 バージョン:v2.0 著者:Claude (Anthropic) — i のメモ拡充プロジェクト