Inbound email setup for receiving forwarded invoices from customers.
Two receiving domains across two AWS accounts, both routing to the same S3 bucket:
| Domain | AWS Account | Profile | Use Case |
|---|---|---|---|
mail.getorcha.com |
Management | orcha |
Customer-facing, per-client plus-addressing |
mail.prod.getorcha.com |
Production | orcha-prod |
Internal/testing |
Flow: SES → S3 (in prod account) → SQS (email-acquire queue) → worker processing
flowchart LR
subgraph Management Account
SES1[SES: mail.getorcha.com]
end
subgraph Production Account
SES2[SES: mail.prod.getorcha.com]
S3[S3 Bucket]
SQS[email-acquire queue]
Worker[Email Acquire Worker]
end
Customer[Customer Email] -->|documents+clientcode@| SES1
Customer -->|documents@| SES2
SES1 -->|cross-account write| S3
SES2 --> S3
S3 -->|notification| SQS
SQS -->|polls| Worker
orcha profile)Handles mail.getorcha.com - the primary customer-facing domain.
Receipt Rule Set: orcha-mail-forwarding
| Rule | Recipients | ScanEnabled | Notes |
|---|---|---|---|
<client>-no-scan |
documents+<clientcode>@mail.getorcha.com |
false | Per-client rules, bypass spam filter |
forward-to-prod-bucket |
documents@mail.getorcha.com |
true | Catch-all for base address |
Rules are evaluated in order. Client-specific rules have a StopAction to prevent the catch-all from also firing.
Why ScanEnabled=false? Some legitimate forwarded invoices trigger SES spam detection. Client-specific rules can disable scanning when needed.
orcha-prod profile)Handles mail.prod.getorcha.com - secondary domain.
Receipt Rule Set: v1-orcha-prod-inbound
store-to-s3documents@mail.prod.getorcha.comInfrastructure defined in infra/stacks/foundation_stack.py.
Both domains have MX records pointing to SES:
dig MX mail.getorcha.com +short
# 10 inbound-smtp.eu-central-1.amazonaws.com.
dig MX mail.prod.getorcha.com +short
# 10 inbound-smtp.eu-central-1.amazonaws.com.
| Account | Route53 Zone | MX Record Location |
|---|---|---|
Management (orcha) |
getorcha.com |
mail.getorcha.com MX record here |
Production (orcha-prod) |
prod.getorcha.com |
mail.prod.getorcha.com MX record here |
v1-orcha-ses-emails-700558745280v1-orcha-global-email-acquirev1-orcha-global-email-acquire-dlq (after 3 failures)First: Identify which account handles the email based on domain:
*@mail.getorcha.com → Management account (--profile orcha)*@mail.prod.getorcha.com → Production account (--profile orcha-prod)dig MX mail.getorcha.com +short
dig MX mail.prod.getorcha.com +short
# Expected: 10 inbound-smtp.eu-central-1.amazonaws.com.
# For mail.getorcha.com
aws ses get-identity-verification-attributes \
--profile orcha --region eu-central-1 \
--identities mail.getorcha.com
# For mail.prod.getorcha.com
aws ses get-identity-verification-attributes \
--profile orcha-prod --region eu-central-1 \
--identities mail.prod.getorcha.com
# Expected: "VerificationStatus": "Success"
# Management account (mail.getorcha.com)
aws ses describe-active-receipt-rule-set \
--profile orcha --region eu-central-1
# Expected: RuleSetName = orcha-mail-forwarding
# Production account (mail.prod.getorcha.com)
aws ses describe-active-receipt-rule-set \
--profile orcha-prod --region eu-central-1
# Expected: RuleSetName = v1-orcha-prod-inbound
Receipt rules match exact addresses. Plus-addressed emails (e.g., documents+clientcode@) need explicit rules.
# Check management account rules
aws ses describe-active-receipt-rule-set \
--profile orcha --region eu-central-1 \
--query 'Rules[].{Name:Name,Recipients:Recipients,ScanEnabled:ScanEnabled}'
# Check production account rules
aws ses describe-active-receipt-rule-set \
--profile orcha-prod --region eu-central-1 \
--query 'Rules[].{Name:Name,Recipients:Recipients,ScanEnabled:ScanEnabled}'
# List recent emails (both accounts write here)
aws s3 ls s3://v1-orcha-ses-emails-700558745280/ \
--profile orcha-prod --region eu-central-1
# Search for specific email by message ID or date
aws s3 ls s3://v1-orcha-ses-emails-700558745280/ \
--profile orcha-prod --region eu-central-1 \
--recursive | grep "2026-02-23"
aws sqs get-queue-attributes \
--profile orcha-prod --region eu-central-1 \
--queue-url https://sqs.eu-central-1.amazonaws.com/700558745280/v1-orcha-global-email-acquire \
--attribute-names ApproximateNumberOfMessages ApproximateNumberOfMessagesNotVisible
# Check DLQ for failed messages
aws sqs get-queue-attributes \
--profile orcha-prod --region eu-central-1 \
--queue-url https://sqs.eu-central-1.amazonaws.com/700558745280/v1-orcha-global-email-acquire-dlq \
--attribute-names ApproximateNumberOfMessages
| Symptom | Likely Cause |
|---|---|
| Email bounces immediately | MX record missing or wrong |
| Email accepted but not in S3 | Receipt rule set not active, or recipient doesn't match rule |
| Plus-addressed email not received | No explicit rule for that plus-address (rules are exact match) |
| Email rejected as spam | ScanEnabled=true and content triggered spam filter |
| Email in S3 but not processed | SQS notification not configured, or worker not running |
| Sender gets 550 rejection | SES domain not verified |
| Works for some senders, not others | SES sandbox mode (only verified senders allowed) |
When a client's forwarded emails are being rejected by spam scanning, add a dedicated rule:
# First, list existing rules to find positioning
aws ses describe-active-receipt-rule-set \
--profile orcha --region eu-central-1 \
--query 'Rules[].Name'
# Add new rule (position before the catch-all)
aws ses create-receipt-rule \
--profile orcha --region eu-central-1 \
--rule-set-name orcha-mail-forwarding \
--after "<previous-rule-name>" \
--rule '{
"Name": "<client>-no-scan",
"Enabled": true,
"Recipients": ["documents+<clientcode>@mail.getorcha.com"],
"Actions": [
{"S3Action": {"BucketName": "v1-orcha-ses-emails-700558745280"}},
{"StopAction": {"Scope": "RuleSet"}}
],
"ScanEnabled": false
}'
Rules are evaluated in order. Use --after to position the rule before forward-to-prod-bucket (the catch-all). Include StopAction to prevent the catch-all from also matching.
New SES accounts start in sandbox mode:
Check sandbox status:
aws ses get-account \
--profile orcha-prod --region eu-central-1
# Look for "ProductionAccessEnabled": true