HTMLメールを AWS SES で送信する際、
画像をどのように配信するかは、表示率・到達率・セキュリティに大きく影響します。
本記事では、
-
既存の S3 バケットを流用
-
S3 は非公開のまま
-
CloudFront + HTTPS で画像配信
-
Python + Lambda で SES 送信
という、実務で最も事故が少ない構成を、具体的な手順付きで解説します。
なぜ「S3 + CloudFront + HTTPS」が最適なのか
HTMLメールでは、次の条件を満たす必要があります。
-
ほぼすべてのメールクライアントで画像が表示される
-
セキュリティ警告が出ない
-
スパム判定を受けにくい
-
運用・更新が容易
これらを満たすのが、
S3(非公開)+ CloudFront(HTTPS)+ img src にURL指定
という構成です。
ゴール構成(全体像)
S3(既存・非公開)
└ mail-assets/
└ logo.png
↑
CloudFront(OAC)
└ https://img.example.com/mail-assets/logo.png
↑
SES(HTMLメール)
-
S3 は 公開しない
-
CloudFront の Origin Access Control(OAC) で限定公開
-
HTMLメールでは HTTPS URL を指定するだけ
1. 既存S3バケットの準備
1-1. 画像配置(重要:Content-Type と Cache-Control)
aws s3 cp ./logo.png s3://my-existing-bucket/mail-assets/logo.v20260220.png \
--content-type "image/png" \
--cache-control "public, max-age=31536000, immutable"
ポイント
-
Content-Type
→ 誤ると一部環境で表示されないことがあります -
Cache-Control
→ CloudFront キャッシュを最大活用 -
ファイル名バージョニング
→ 画像更新時のキャッシュ事故を防ぐ最善策
1-2. S3は公開しない(重要)
-
Block Public Access:ONのまま
-
CloudFront からのみアクセス可能にします(後述)
2. CloudFront を既存S3に紐付ける(OAC)
2-1. CloudFront ディストリビューション作成
Origin
-
Origin domain:既存S3バケット
-
Origin access:Origin Access Control(OAC)
Cache behavior
-
Viewer protocol policy:Redirect HTTP → HTTPS
-
Allowed methods:GET, HEAD
-
Cache policy:Managed-CachingOptimized
2-2. S3バケットポリシー(CloudFront専用)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCloudFrontReadOnly",
"Effect": "Allow",
"Principal": { "Service": "cloudfront.amazonaws.com" },
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-existing-bucket/mail-assets/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::123456789012:distribution/EXAMPLEID"
}
}
}
]
}
ポイント
-
S3は非公開のまま
-
CloudFront 特定ディストリビューションのみ許可
-
既存用途がある場合も、
mail-assets/に限定すれば安全
3. HTTPS(独自ドメイン)設定(推奨)
3-1. ACM証明書(us-east-1)
CloudFront 用の証明書は us-east-1(バージニア北部) で発行します。
-
例:
img.example.com -
DNS検証(Route53なら簡単)
3-2. CloudFront に証明書を設定
-
Alternate domain name:
img.example.com -
Custom SSL certificate:発行したACM証明書
3-3. Route53(Alias)
-
img.example.com→ CloudFront distribution
独自ドメインを使うことで、
メール受信者からの 信頼性・見た目 が向上します。
4. HTMLメールの img タグ記述
<img
src="https://img.example.com/mail-assets/logo.v20260220.png"
alt="Your Service"
width="200"
style="display:block; max-width:100%; height:auto;"
>
実務ポイント
-
altは必須(画像非表示・到達率対策) -
CSSは インライン指定
-
widthを指定してレイアウト崩れ防止
5. Lambda(Python)から SES でHTMLメール送信
5-1. IAM権限(最小)
{
"Effect": "Allow",
"Action": [
"ses:SendEmail",
"ses:SendRawEmail"
],
"Resource": "*"
}
5-2. boto3 送信例
import boto3
ses = boto3.client("ses", region_name="ap-northeast-1")
def send_html_email(to_address: str) -> None:
subject = "Welcome!"
html_body = """
<html>
<body>
<img src="https://img.example.com/mail-assets/logo.v20260220.png"
alt="Your Service"
width="200"
style="display:block;">
<p>こんにちは!</p>
</body>
</html>
"""
ses.send_email(
Source="no-reply@example.com",
Destination={"ToAddresses": [to_address]},
Message={
"Subject": {"Data": subject, "Charset": "UTF-8"},
"Body": {
"Html": {"Data": html_body, "Charset": "UTF-8"},
"Text": {"Data": "こんにちは!", "Charset": "UTF-8"}
}
}
)
※ Textパート併記は到達率・アクセシビリティの観点で強く推奨されます。
6. よくあるトラブルと対策
画像が更新されない
-
原因:CloudFront キャッシュ
-
対策:ファイル名にバージョンを含める(最推奨)
AccessDenied(403)
-
OAC 用バケットポリシーの
-
Distribution ID
-
Account ID
-
パス指定
を再確認
-
受信側で画像が表示されない
-
受信者の設定による仕様
-
altテキスト+本文で内容が伝わる設計が重要
まとめ
-
HTMLメール画像は S3 + CloudFront + HTTPS が最適解
-
S3は 非公開のまま、OACで安全に配信
-
画像更新は ファイル名バージョニング
-
HTMLは インラインCSS+alt必須
-
SES送信時は Textパート併記
HTMLメールの画像配信は、
Webではなく「メール特有の制約」を前提に設計することが最大のポイントです。
必要であれば、
-
CloudFormation / Terraform テンプレート
-
Gmail / Outlook で崩れない HTML 雛形
-
開封率計測(トラッキングピクセル)設計
も記事化できます。