Skip to content

Instantly share code, notes, and snippets.

@allan-gar2x
Created April 3, 2026 07:33
Show Gist options
  • Select an option

  • Save allan-gar2x/9b4d22fdca443c8fc1dcf182bf027677 to your computer and use it in GitHub Desktop.

Select an option

Save allan-gar2x/9b4d22fdca443c8fc1dcf182bf027677 to your computer and use it in GitHub Desktop.
Starlight Practice — AWS SES OpenTofu Implementation (Tasks 1-5)

Deployment Order

Step 1 — Apply prod (starlightpractice.com is root domain, apply first)

cd accounts/starlightpractice-prod
tofu init
tofu plan
tofu apply

Step 2 — Apply dev

cd accounts/starlightpractice-dev
tofu init
tofu plan
tofu apply

Step 3 — Wait for DKIM verification (5–30 minutes)

Poll until Status = "SUCCESS":

# Prod
aws sesv2 get-email-identity \
  --email-identity starlightpractice.com \
  --region us-east-2 \
  --profile starlightpractice-prod \
  --query 'DkimAttributes.Status'

# Dev
aws sesv2 get-email-identity \
  --email-identity dev.starlightpractice.com \
  --region us-east-2 \
  --profile starlightpractice-dev \
  --query 'DkimAttributes.Status'

Step 4 — Submit sandbox removal requests

See task-2-sandbox-removal.md. Do this for both accounts once DKIM = SUCCESS.

Step 5 — Wire Lambda role (once SST app is deployed)

# Prod
cd accounts/starlightpractice-prod
tofu apply -var='lambda_role_name=<role-name-from-sst>'

# Dev
cd accounts/starlightpractice-dev
tofu apply -var='lambda_role_name=<role-name-from-sst>'

Step 6 — Dev team instruction

Every SendEmail SDK call must include:

  • Prod: ConfigurationSetName = "starlight-prod"
  • Dev: ConfigurationSetName = "starlight-dev"

Key facts verified before implementation

  • Route53 zone IDs: prod = Z03036173FL99T10J98KK, dev = Z028466637NTWD1K6UX8Z
  • Both zones already in OpenTofu state in b4m-infra
  • prod main.tf already has us_east_1 provider alias
  • dev main.tf needs us_east_1 provider alias added (see starlightpractice-dev-main-addition.tf)
  • No existing SES identities on either account
  • Both accounts in sandbox mode as of 2026-04-03
# ============================================================================
# accounts/starlightpractice-dev/main.tf — ADDITION ONLY
# ============================================================================
# Add this provider block to the EXISTING main.tf in starlightpractice-dev.
# Do NOT replace the existing file — just append this block.
#
# Required for CloudWatch SES reputation alarms (AWS publishes SES metrics
# to us-east-1 regardless of the sending region).
#
# Insert after the existing:
# provider "aws" { region = "us-east-2", profile = "starlightpractice-dev" }
# ============================================================================
provider "aws" {
alias = "us_east_1"
region = "us-east-1"
profile = "starlightpractice-dev"
}
# ============================================================================
# accounts/starlightpractice-dev/ses.tf
# ============================================================================
# SES full implementation for dev.starlightpractice.com (staging/dev)
# Tasks: 1 (domain + DKIM + MAIL FROM), 3 (sending identity),
# 4 (IAM for Lambda), 5 (config set + SNS + CloudWatch)
#
# Prerequisites:
# - aws_route53_zone.main already exists in main.tf (zone: dev.starlightpractice.com)
# - Add us_east_1 provider alias to main.tf (see starlightpractice-dev-main-addition.tf)
# - Run `tofu apply` before submitting sandbox removal request (Task 2)
# ============================================================================
# ============================================================================
# Task 1: SES Domain Identity + DKIM
# ============================================================================
resource "aws_sesv2_email_identity" "domain" {
email_identity = "dev.starlightpractice.com"
configuration_set_name = aws_sesv2_configuration_set.main.configuration_set_name
dkim_signing_attributes {
next_signing_key_length = "RSA_2048_BIT"
}
tags = {
Environment = "dev"
Project = "starlightpractice"
ManagedBy = "OpenTofu"
}
}
# Publish 3 DKIM CNAME records into the existing Route53 zone
resource "aws_route53_record" "ses_dkim_cname" {
for_each = toset(aws_sesv2_email_identity.domain.dkim_signing_attributes[0].tokens)
zone_id = aws_route53_zone.main.zone_id
name = "${each.value}._domainkey.dev.starlightpractice.com"
type = "CNAME"
ttl = 300
records = ["${each.value}.dkim.amazonses.com"]
}
# ============================================================================
# Task 1b: MAIL FROM Domain (improves deliverability / DMARC alignment)
# ============================================================================
resource "aws_sesv2_email_identity_mail_from_attributes" "domain" {
email_identity = aws_sesv2_email_identity.domain.email_identity
mail_from_domain = "mail.dev.starlightpractice.com"
behavior_on_mx_failure = "USE_DEFAULT_VALUE"
}
resource "aws_route53_record" "ses_mail_from_mx" {
zone_id = aws_route53_zone.main.zone_id
name = "mail.dev.starlightpractice.com"
type = "MX"
ttl = 300
records = ["10 feedback-smtp.us-east-2.amazonses.com"]
}
resource "aws_route53_record" "ses_mail_from_spf" {
zone_id = aws_route53_zone.main.zone_id
name = "mail.dev.starlightpractice.com"
type = "TXT"
ttl = 300
records = ["\"v=spf1 include:amazonses.com ~all\""]
}
# ============================================================================
# Task 3: Sending Identity
# ============================================================================
resource "aws_sesv2_email_identity" "from_address" {
email_identity = "[email protected]"
configuration_set_name = aws_sesv2_configuration_set.main.configuration_set_name
tags = {
Environment = "dev"
Project = "starlightpractice"
ManagedBy = "OpenTofu"
}
depends_on = [aws_sesv2_email_identity.domain]
}
# ============================================================================
# Task 4: IAM Permissions for Lambda
# ============================================================================
variable "lambda_role_name" {
description = "Name of the Lambda execution role that needs SES send permissions. Populate once the SST app is deployed."
type = string
default = "" # Leave empty until Lambda role exists
}
resource "aws_iam_role_policy" "lambda_ses_send" {
count = var.lambda_role_name != "" ? 1 : 0
name = "ses-send-dev"
role = var.lambda_role_name
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "SESSendFromVerifiedDomain"
Effect = "Allow"
Action = [
"ses:SendEmail",
"ses:SendRawEmail"
]
Resource = [
"arn:aws:ses:us-east-2:421807902331:identity/dev.starlightpractice.com"
]
},
{
Sid = "SESGetQuota"
Effect = "Allow"
Action = ["ses:GetSendQuota"]
Resource = ["*"]
}
]
})
}
# ============================================================================
# Task 5: Configuration Set + SNS + CloudWatch Monitoring
# ============================================================================
resource "aws_sesv2_configuration_set" "main" {
configuration_set_name = "starlight-dev"
sending_options {
sending_enabled = true
}
suppression_options {
suppressed_reasons = ["BOUNCE", "COMPLAINT"]
}
tags = {
Environment = "dev"
Project = "starlightpractice"
ManagedBy = "OpenTofu"
}
}
resource "aws_sns_topic" "ses_events" {
name = "ses-events-dev"
tags = {
Environment = "dev"
Project = "starlightpractice"
ManagedBy = "OpenTofu"
}
}
variable "alarm_email" {
description = "Email address to receive SES bounce/complaint alarm notifications."
type = string
default = "[email protected]"
}
resource "aws_sns_topic_subscription" "ses_events_email" {
topic_arn = aws_sns_topic.ses_events.arn
protocol = "email"
endpoint = var.alarm_email
}
resource "aws_sesv2_configuration_set_event_destination" "sns" {
configuration_set_name = aws_sesv2_configuration_set.main.configuration_set_name
event_destination_name = "sns-bounce-complaint"
event_destination {
enabled = true
matching_event_types = ["BOUNCE", "COMPLAINT", "DELIVERY_DELAY"]
sns_destination {
topic_arn = aws_sns_topic.ses_events.arn
}
}
}
# SES reputation metrics are published to us-east-1 by AWS regardless of sending region.
# Requires the us_east_1 provider alias — see starlightpractice-dev-main-addition.tf
resource "aws_cloudwatch_metric_alarm" "ses_bounce_rate" {
provider = aws.us_east_1
alarm_name = "ses-bounce-rate-dev"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 1
metric_name = "Reputation.BounceRate"
namespace = "AWS/SES"
period = 900
statistic = "Average"
threshold = 0.05 # 5% — AWS suspends at 10%
alarm_description = "SES bounce rate exceeded 5% in dev. AWS suspends sending at 10%."
treat_missing_data = "notBreaching"
alarm_actions = [aws_sns_topic.ses_events.arn]
ok_actions = [aws_sns_topic.ses_events.arn]
tags = {
Environment = "dev"
Project = "starlightpractice"
ManagedBy = "OpenTofu"
}
}
resource "aws_cloudwatch_metric_alarm" "ses_complaint_rate" {
provider = aws.us_east_1
alarm_name = "ses-complaint-rate-dev"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 1
metric_name = "Reputation.ComplaintRate"
namespace = "AWS/SES"
period = 900
statistic = "Average"
threshold = 0.001 # 0.1% — AWS suspends at 0.5%
alarm_description = "SES complaint rate exceeded 0.1% in dev. AWS suspends sending at 0.5%."
treat_missing_data = "notBreaching"
alarm_actions = [aws_sns_topic.ses_events.arn]
tags = {
Environment = "dev"
Project = "starlightpractice"
ManagedBy = "OpenTofu"
}
}
# ============================================================================
# Outputs
# ============================================================================
output "ses_domain_identity_arn" {
value = aws_sesv2_email_identity.domain.arn
description = "SES domain identity ARN — use in Lambda IAM policies"
}
output "ses_configuration_set_name" {
value = aws_sesv2_configuration_set.main.configuration_set_name
description = "Pass as ConfigurationSetName in every Lambda SendEmail call"
}
output "ses_events_sns_topic_arn" {
value = aws_sns_topic.ses_events.arn
description = "SNS topic ARN for SES bounce/complaint events"
}
# ============================================================================
# accounts/starlightpractice-prod/ses.tf
# ============================================================================
# SES full implementation for starlightpractice.com (production)
# Tasks: 1 (domain + DKIM + MAIL FROM), 3 (sending identity),
# 4 (IAM for Lambda), 5 (config set + SNS + CloudWatch)
#
# Prerequisites:
# - aws_route53_zone.root already exists in main.tf (zone: starlightpractice.com)
# - provider "aws" { alias = "us_east_1" } already exists in main.tf
# - Run `tofu apply` before submitting sandbox removal request (Task 2)
# ============================================================================
# ============================================================================
# Task 1: SES Domain Identity + DKIM
# ============================================================================
resource "aws_sesv2_email_identity" "domain" {
email_identity = "starlightpractice.com"
configuration_set_name = aws_sesv2_configuration_set.main.configuration_set_name
dkim_signing_attributes {
next_signing_key_length = "RSA_2048_BIT"
}
tags = {
Environment = "prod"
Project = "starlightpractice"
ManagedBy = "OpenTofu"
}
}
# Publish 3 DKIM CNAME records into the existing Route53 zone
resource "aws_route53_record" "ses_dkim_cname" {
for_each = toset(aws_sesv2_email_identity.domain.dkim_signing_attributes[0].tokens)
zone_id = aws_route53_zone.root.zone_id
name = "${each.value}._domainkey.starlightpractice.com"
type = "CNAME"
ttl = 300
records = ["${each.value}.dkim.amazonses.com"]
}
# ============================================================================
# Task 1b: MAIL FROM Domain (improves deliverability / DMARC alignment)
# ============================================================================
resource "aws_sesv2_email_identity_mail_from_attributes" "domain" {
email_identity = aws_sesv2_email_identity.domain.email_identity
mail_from_domain = "mail.starlightpractice.com"
behavior_on_mx_failure = "USE_DEFAULT_VALUE"
}
resource "aws_route53_record" "ses_mail_from_mx" {
zone_id = aws_route53_zone.root.zone_id
name = "mail.starlightpractice.com"
type = "MX"
ttl = 300
records = ["10 feedback-smtp.us-east-2.amazonses.com"]
}
resource "aws_route53_record" "ses_mail_from_spf" {
zone_id = aws_route53_zone.root.zone_id
name = "mail.starlightpractice.com"
type = "TXT"
ttl = 300
records = ["\"v=spf1 include:amazonses.com ~all\""]
}
# ============================================================================
# Task 3: Sending Identity
# ============================================================================
resource "aws_sesv2_email_identity" "from_address" {
email_identity = "[email protected]"
configuration_set_name = aws_sesv2_configuration_set.main.configuration_set_name
tags = {
Environment = "prod"
Project = "starlightpractice"
ManagedBy = "OpenTofu"
}
depends_on = [aws_sesv2_email_identity.domain]
}
# ============================================================================
# Task 4: IAM Permissions for Lambda
# ============================================================================
variable "lambda_role_name" {
description = "Name of the Lambda execution role that needs SES send permissions. Populate once the SST app is deployed."
type = string
default = "" # Leave empty until Lambda role exists
}
resource "aws_iam_role_policy" "lambda_ses_send" {
count = var.lambda_role_name != "" ? 1 : 0
name = "ses-send-prod"
role = var.lambda_role_name
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "SESSendFromVerifiedDomain"
Effect = "Allow"
Action = [
"ses:SendEmail",
"ses:SendRawEmail"
]
Resource = [
"arn:aws:ses:us-east-2:524471258033:identity/starlightpractice.com"
]
},
{
Sid = "SESGetQuota"
Effect = "Allow"
Action = ["ses:GetSendQuota"]
Resource = ["*"]
}
]
})
}
# ============================================================================
# Task 5: Configuration Set + SNS + CloudWatch Monitoring
# ============================================================================
resource "aws_sesv2_configuration_set" "main" {
configuration_set_name = "starlight-prod"
sending_options {
sending_enabled = true
}
suppression_options {
suppressed_reasons = ["BOUNCE", "COMPLAINT"]
}
tags = {
Environment = "prod"
Project = "starlightpractice"
ManagedBy = "OpenTofu"
}
}
resource "aws_sns_topic" "ses_events" {
name = "ses-events-prod"
tags = {
Environment = "prod"
Project = "starlightpractice"
ManagedBy = "OpenTofu"
}
}
variable "alarm_email" {
description = "Email address to receive SES bounce/complaint alarm notifications."
type = string
default = "[email protected]"
}
resource "aws_sns_topic_subscription" "ses_events_email" {
topic_arn = aws_sns_topic.ses_events.arn
protocol = "email"
endpoint = var.alarm_email
}
resource "aws_sesv2_configuration_set_event_destination" "sns" {
configuration_set_name = aws_sesv2_configuration_set.main.configuration_set_name
event_destination_name = "sns-bounce-complaint"
event_destination {
enabled = true
matching_event_types = ["BOUNCE", "COMPLAINT", "DELIVERY_DELAY"]
sns_destination {
topic_arn = aws_sns_topic.ses_events.arn
}
}
}
# SES reputation metrics are published to us-east-1 by AWS regardless of sending region.
# Prod already has the us_east_1 provider alias in main.tf — no change needed there.
resource "aws_cloudwatch_metric_alarm" "ses_bounce_rate" {
provider = aws.us_east_1
alarm_name = "ses-bounce-rate-prod"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 1
metric_name = "Reputation.BounceRate"
namespace = "AWS/SES"
period = 900
statistic = "Average"
threshold = 0.05 # 5% — AWS suspends at 10%
alarm_description = "SES bounce rate exceeded 5% in prod. AWS suspends sending at 10%."
treat_missing_data = "notBreaching"
alarm_actions = [aws_sns_topic.ses_events.arn]
ok_actions = [aws_sns_topic.ses_events.arn]
tags = {
Environment = "prod"
Project = "starlightpractice"
ManagedBy = "OpenTofu"
}
}
resource "aws_cloudwatch_metric_alarm" "ses_complaint_rate" {
provider = aws.us_east_1
alarm_name = "ses-complaint-rate-prod"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 1
metric_name = "Reputation.ComplaintRate"
namespace = "AWS/SES"
period = 900
statistic = "Average"
threshold = 0.001 # 0.1% — AWS suspends at 0.5%
alarm_description = "SES complaint rate exceeded 0.1% in prod. AWS suspends sending at 0.5%."
treat_missing_data = "notBreaching"
alarm_actions = [aws_sns_topic.ses_events.arn]
tags = {
Environment = "prod"
Project = "starlightpractice"
ManagedBy = "OpenTofu"
}
}
# ============================================================================
# Outputs
# ============================================================================
output "ses_domain_identity_arn" {
value = aws_sesv2_email_identity.domain.arn
description = "SES domain identity ARN — use in Lambda IAM policies"
}
output "ses_configuration_set_name" {
value = aws_sesv2_configuration_set.main.configuration_set_name
description = "Pass as ConfigurationSetName in every Lambda SendEmail call"
}
output "ses_events_sns_topic_arn" {
value = aws_sns_topic.ses_events.arn
description = "SNS topic ARN for SES bounce/complaint events"
}

Task 2: SES Sandbox Removal — Manual Steps

No API or OpenTofu resource exists for this. Must be done manually via AWS Console.

IMPORTANT: Run tofu apply first so the domain is already verified when AWS reviewers check it. Verified domains improve approval chances.

Steps (repeat for BOTH accounts)

Account 1: starlightpractice-dev (421807902331)

Account 2: starlightpractice-prod (524471258033)

  1. Sign in to AWS Console for the account

  2. Navigate to SES → Account dashboard

  3. Click "Request production access" (banner at top)

  4. Fill in:

    Field Value
    Mail type TRANSACTIONAL
    Website URL https://starlightpractice.com

    Use case description:

    "HIPAA-compliant pediatric medical practice sending transactional emails — appointment reminders, billing notices, wellness check notifications, and patient onboarding to families. All recipients have explicitly opted in through the patient portal registration flow. We maintain unsubscribe mechanisms and honor all opt-outs within 10 business days."

  5. Submit — AWS typically responds within 24 hours

  6. Repeat for the second account

Current Status (verified 2026-04-03)

Account SendingEnabled ProductionAccess
starlightpractice-dev (421807902331) ✅ Yes ❌ Sandbox
starlightpractice-prod (524471258033) ✅ Yes ❌ Sandbox
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment