|
# ============================================================================ |
|
# 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" |
|
} |