AWS SES Sandbox Mode: Tại Sao Không Gửi Email Cho Khách Hàng Và Cách Yêu Cầu Production Access

Bạn vừa tích hợp xong Amazon SES, gửi thử email cho chính mình thì nhận được ngay — nhưng khi deploy lên production, toàn bộ email gửi cho khách hàng đều bị từ chối với lỗi MessageRejected. Đây là triệu chứng điển hình của SES Sandbox Mode, và nếu bạn không biết mình đang ở trong đó, bạn sẽ mất hàng giờ debug ở tầng application trong khi vấn đề nằm ở tầng account.

TL;DR — SES Sandbox Mode Là Gì Và Cần Làm Gì

Vấn đềNguyên nhânGiải pháp
Chỉ gửi được cho email đã verifyAccount đang ở Sandbox ModeRequest production access qua SES console
Bị giới hạn 200 email/ngàySandbox sending quotaYêu cầu tăng sending limit trong cùng request
Lỗi MessageRejectedĐịa chỉ đích chưa được verifyVerify địa chỉ đích hoặc thoát Sandbox
Không thấy nút request production accessSai region trong consoleKiểm tra đúng region đang dùng SES

SES Sandbox Mode Hoạt Động Như Thế Nào

Mọi AWS account khi bắt đầu sử dụng SES đều ở Sandbox Mode theo mặc định. AWS áp dụng điều này để ngăn chặn spam và bảo vệ reputation của shared sending infrastructure. Đây không phải lỗi cấu hình — đây là trạng thái mặc định có chủ ý.

Trong Sandbox, SES áp đặt ba ràng buộc chính:

  • Chỉ có thể gửi email đến địa chỉ hoặc domain đã được verify trong cùng AWS account.
  • Chỉ có thể gửi từ địa chỉ hoặc domain đã được verify.
  • Sending quota bị giới hạn — kiểm tra giá trị cụ thể trong SES console vì quota có thể thay đổi theo thời gian.

Khi bạn gửi email cho chính mình và nhận được, đó là vì cả địa chỉ gửi lẫn địa chỉ nhận đều đã được verify trong account của bạn. Khách hàng của bạn thì không.

graph TD A["Ứng dụng gọi SES SendEmail API"] --> B{"Account ở Sandbox Mode?"} B -- Có --> C{"Địa chỉ đích đã được verify trong account?"} C -- Không --> D["MessageRejected Error Email không được gửi"] C -- Có --> E["Email được gửi (chỉ trong giới hạn quota)"] B -- Không --> F["Production Mode"] F --> G{"Địa chỉ đích hợp lệ?"} G -- Có --> H["Email được gửi đến bất kỳ địa chỉ nào"] G -- Không --> I["Bounce hoặc Reject từ mail server đích"]
  1. Sandbox Mode: SES kiểm tra xem địa chỉ đích có nằm trong danh sách verified identities của account không. Nếu không — reject ngay.
  2. Production Mode: SES bỏ qua bước kiểm tra đó và gửi đến bất kỳ địa chỉ hợp lệ nào, với sending quota cao hơn nhiều.
  3. Trạng thái Sandbox/Production là per-region. Nếu bạn dùng SES ở us-east-1eu-west-1, bạn phải request production access riêng cho từng region.

Xác Nhận Account Đang Ở Sandbox Mode

Trước khi submit bất kỳ request nào, hãy xác nhận trạng thái thực tế. Nhiều người submit request rồi mới phát hiện mình đã ở production mode ở một region khác.

aws sesv2 get-account \
  --region us-east-1

Tìm field ProductionAccessEnabled trong output. Nếu giá trị là false, account đang ở Sandbox Mode cho region đó.

{
    "DedicatedIpAutoWarmupEnabled": true,
    "EnforcementStatus": "HEALTHY",
    "ProductionAccessEnabled": false,
    "SendQuota": {
        "Max24HourSend": 200.0,
        "MaxSendRate": 1.0,
        "SentLast24Hours": 0.0
    },
    "SendingEnabled": true
}

ProductionAccessEnabled: false — đây là xác nhận rõ ràng nhất. SendingEnabled: true chỉ có nghĩa là account không bị suspend, không có nghĩa là đã thoát Sandbox.

Verify Sending Identity Trước Khi Request Production Access

AWS sẽ đánh giá request của bạn dựa một phần vào việc bạn đã setup sending infrastructure đúng cách chưa. Verify domain với DKIM trước khi submit sẽ tạo ấn tượng tốt hơn và giúp email deliverability sau này.

Verify Domain (Khuyến nghị)

aws sesv2 create-email-identity \
  --email-identity example.com \
  --dkim-signing-attributes SigningAttributesOrigin=AWS_SES \
  --region us-east-1

Lệnh này tạo DKIM keys và trả về các DNS records cần thêm vào Route 53 hoặc DNS provider của bạn. Sau khi thêm DNS records, kiểm tra trạng thái verify:

aws sesv2 get-email-identity \
  --email-identity example.com \
  --region us-east-1

Chờ đến khi VerifiedForSendingStatus trả về true trước khi tiếp tục.

Verify Email Address Đơn Lẻ (Cho Testing)

aws sesv2 create-email-identity \
  --email-identity you@example.com \
  --region us-east-1

AWS sẽ gửi một email xác nhận đến địa chỉ đó. Click vào link trong email để hoàn tất verify.

Cách Request Production Access — Thoát SES Sandbox Mode

Đây là bước quan trọng nhất. Request được submit qua SES console hoặc AWS CLI, và AWS sẽ review thủ công. Thời gian xử lý thường trong vòng 24 giờ, nhưng không có SLA cố định.

Qua AWS Console

  1. Mở Amazon SES console → chọn đúng region.
  2. Trong navigation pane, chọn Account dashboard.
  3. Tìm section Sending limits → click Request production access.
  4. Điền form với thông tin use case của bạn.

Qua AWS CLI (SES v2)

aws sesv2 put-account-details \
  --mail-type TRANSACTIONAL \
  --website-url https://www.example.com \
  --use-case-description "We send transactional emails including order confirmations, password resets, and account notifications to registered users who have opted in through our sign-up flow. We maintain a suppression list and process unsubscribe requests within 10 business days." \
  --additional-contact-email-addresses admin@example.com \
  --production-access-enabled \
  --region us-east-1

Lưu ý: --mail-type chấp nhận TRANSACTIONAL hoặc MARKETING. Chọn đúng loại — AWS dùng thông tin này để đánh giá request.

Nội Dung Use Case Description Cần Có

Đây là phần quyết định request của bạn được approve hay bị yêu cầu bổ sung thông tin. Từ kinh nghiệm thực tế, một description tốt cần trả lời được bốn câu hỏi sau:

  • Bạn gửi loại email gì? — Transactional (order confirmation, password reset) hay marketing (newsletter, promotion).
  • Người nhận opt-in như thế nào? — Double opt-in, single opt-in, hay existing customers.
  • Bạn xử lý bounce và complaint như thế nào? — Có suppression list không, có xử lý SNS notifications từ SES không.
  • Volume dự kiến là bao nhiêu? — Số email/ngày và tốc độ tăng trưởng.
Nghĩ về request này như một cuộc phỏng vấn ngắn với AWS Trust & Safety team. Họ cần biết bạn hiểu email deliverability và sẽ không làm ảnh hưởng đến reputation của shared IP pool. Một description hai câu chung chung gần như chắc chắn sẽ bị bounce lại.
graph LR A["Submit Request qua Console hoặc CLI"] --> B["AWS Trust & Safety Review thủ công"] B --> C{"Kết quả?"} C -- "Approved" --> D["ProductionAccessEnabled: true Quota tăng lên"] C -- "Cần thêm thông tin" --> E["Support Case mở Trả lời trong case"] E --> B C -- "Rejected" --> F["Xem lý do trong Support Case Cải thiện use case và resubmit"] D --> G["Verify bằng CLI: aws sesv2 get-account"]
  1. Sau khi submit, AWS sẽ review request và gửi email thông báo kết quả về địa chỉ liên kết với AWS account.
  2. Nếu được approve, ProductionAccessEnabled sẽ chuyển thành true — verify lại bằng lệnh get-account ở trên.
  3. Nếu bị reject hoặc yêu cầu thêm thông tin, AWS sẽ mở một Support case — trả lời trực tiếp trong case đó.

Setup Bounce và Complaint Handling — Không Phải Tùy Chọn

AWS yêu cầu bạn xử lý bounce và complaint notifications để duy trì sending reputation. Nếu bounce rate hoặc complaint rate vượt ngưỡng, SES có thể tạm dừng sending của account. Đây là điều bạn cần setup trước khi gửi email production, không phải sau.

Tạo SNS Topic Cho Notifications

aws sns create-topic \
  --name ses-bounce-complaints \
  --region us-east-1

Cấu Hình SES Gửi Notifications Đến SNS

aws sesv2 put-configuration-set-event-destination \
  --configuration-set-name default \
  --event-destination-name bounce-complaint-handler \
  --event-destination '{"Enabled": true, "MatchingEventTypes": ["BOUNCE", "COMPLAINT"], "SnsDestination": {"TopicArn": "arn:aws:sns:us-east-1:123456789012:ses-bounce-complaints"}}' \
  --region us-east-1

Nếu chưa có configuration set, tạo trước:

aws sesv2 create-configuration-set \
  --configuration-set-name default \
  --region us-east-1

IAM Policy Cho Lambda Xử Lý SNS Notifications

Nếu bạn dùng Lambda để xử lý bounce/complaint notifications từ SNS:

🔽 Click để xem IAM policy
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ses:PutSuppressedDestination"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:us-east-1:123456789012:log-group:/aws/lambda/*"
    }
  ]
}

Khi nhận được bounce notification, thêm địa chỉ đó vào SES account-level suppression list để tránh gửi lại:

aws sesv2 put-suppressed-destination \
  --email-address bounced@customer.com \
  --reason BOUNCE \
  --region us-east-1

Bẫy Thực Tế: Misdiagnosis Mất 3 Giờ

Đây là pattern xảy ra thường xuyên hơn bạn nghĩ. Team nhận được lỗi MessageRejected: Email address is not verified và bắt đầu debug ở tầng application — kiểm tra lại SDK configuration, IAM permissions, thậm chí network connectivity. Tất cả đều ổn.

Sau đó họ verify thêm vài địa chỉ email test và thấy gửi được — kết luận vội vàng là 'vấn đề đã fix'. Deploy lại production, lỗi quay lại ngay lập tức.

Nguyên nhân thực sự: account vẫn ở Sandbox Mode. Địa chỉ test gửi được vì đã được verify. Địa chỉ khách hàng thì không. Lỗi Email address is not verified trong Sandbox Mode không có nghĩa là địa chỉ gửi của bạn có vấn đề — nó có nghĩa là địa chỉ nhận chưa được verify trong account của bạn.

Fix đúng: chạy aws sesv2 get-account, xác nhận ProductionAccessEnabled: false, submit production access request. Không có shortcut nào khác.

Kiểm Tra Trạng Thái Sau Khi Được Approve

aws sesv2 get-account \
  --region us-east-1

Output sau khi được approve:

{
    "ProductionAccessEnabled": true,
    "SendQuota": {
        "Max24HourSend": 50000.0,
        "MaxSendRate": 14.0,
        "SentLast24Hours": 0.0
    },
    "SendingEnabled": true
}

Sending quota sau khi thoát Sandbox phụ thuộc vào những gì bạn request và đánh giá của AWS. Nếu cần tăng thêm, submit thêm một request tăng quota riêng.

Wrap-Up: SES Sandbox Mode Và Các Bước Tiếp Theo

SES Sandbox Mode không phải lỗi — đó là cơ chế bảo vệ mặc định của AWS cho mọi account mới. Vấn đề chỉ xảy ra khi bạn không biết mình đang ở trong đó và bắt đầu debug sai tầng.

Checklist trước khi go-live với SES production:

  • ☑ Verify sending domain với DKIM
  • ☑ Setup configuration set với bounce/complaint notifications đến SNS
  • ☑ Implement suppression list handling
  • ☑ Submit production access request với use case description đầy đủ
  • ☑ Xác nhận ProductionAccessEnabled: true bằng CLI sau khi approve
  • ☑ Nhớ repeat cho mỗi region bạn dùng SES

Tham khảo thêm: AWS SES — Moving out of the Amazon SES sandboxMonitoring SES sending activity.

Glossary — Thuật Ngữ Chính

Thuật ngữGiải thích
Sandbox ModeTrạng thái mặc định của SES account mới — chỉ gửi được đến verified addresses
Verified IdentityEmail address hoặc domain đã được xác nhận ownership trong SES
Sending QuotaGiới hạn số email tối đa có thể gửi trong 24 giờ và tốc độ gửi tối đa
Bounce RateTỷ lệ email bị từ chối bởi mail server đích — ảnh hưởng trực tiếp đến sending reputation
Suppression ListDanh sách địa chỉ SES sẽ tự động bỏ qua để tránh gửi lại sau bounce hoặc complaint
Configuration SetTập hợp các rule áp dụng cho một nhóm email — dùng để routing events đến SNS, CloudWatch, v.v.

Nhận xét

Bài đăng phổ biến từ blog này

EC2 Không Có Internet Trong Custom VPC: Cách Gắn Internet Gateway và Cập Nhật Route Table

RDS Multi-AZ: Lợi Ích Thực Sự Là Gì và Khi Nào Nên Bật?

Lỗi CORS trên API Gateway: Cách bật CORS và Lambda phải trả về header gì