AWS SES domain & deliverability setup
This guide is for DevOps / platform operators who configure Amazon SES before agents can connect email via the Any email (forwarding) path in Fiko.
Architecture overview
Inbound: MX on inbound.example.com → SES receipt rule → S3/SNS → Fiko messaging-service
Per-channel address: [email protected] (generated in Fiko UI)
Outbound From: [email protected]
Reply-To: tokenized address on reply domain for threading
Inbound domain
inbound.example.com
- MX → inbound-smtp.us-east-1.amazonaws.com
- Receipt rule → SNS / S3 → Fiko
- Per-channel: support-abc@inbound...
Reply domain
reply.example.com
- SPF → include:amazonses.com
- DKIM → 3× CNAME (Easy DKIM)
- DMARC → _dmarc.reply.example.com
- MAIL FROM → bounce.reply.example.com
SES email receiving is only available in specific regions (e.g. us-east-1, eu-west-1). Pick one region and keep MX, receipt rules, and API calls in the same region.
Before you start
AWS account with SES access
Permission to create identities, receipt rules, and IAM users.
Two subdomains you control
e.g. inbound.example.com (receive) and reply.example.com (send).
DNS admin access
To publish MX, TXT, CNAME records for both subdomains.
fiko-messaging-service .env access
For EMAIL_SES_* credentials and domain vars.
Step-by-step setup
Part A — Choose region and request production access
- 1Open AWS Console → switch to your target region (e.g. US East (N. Virginia) us-east-1).
- 2Go to Amazon SES → Account dashboard.
- 3If status shows Sandbox, click Request production access.
- Mail type: Transactional
- Use case: Customer support email for SaaS platform
- Approval typically within 24–48 hours
Expected result
In sandbox you can only send to verified addresses. Agents cannot go live until production access is granted.
Part B — Verify inbound domain (receive mail)
- 4SES → Verified identities → Create identity → type Domain.
- 5Enter inbound subdomain: e.g.
inbound.example.com→ Create. - 6Copy DNS records SES shows (DKIM CNAMEs + verification TXT) → publish in your DNS provider.
- 7Add MX record for inbound subdomain:
Host: inbound.example.com Type: MX Priority: 10 Value: inbound-smtp.us-east-1.amazonaws.com
Expected result
SES identity status becomes Verified (may take up to 72 hours for DNS).
Part C — Receipt rule set (route inbound mail to Fiko)
SES receives mail via MX → the receipt rule decides where to store the raw MIME and how to notify Fiko. Fiko reads the email from S3 (or from SNS inline payload if the message is small).
- 8SES → Email receiving → Rule sets → Create rule set.
- Rule set name: fiko-inbound
- Click Create
- 9Select the new rule set → Set as active rule set.
Expected result
Only one active rule set per region. Deactivate the old rule set first if needed. - 10Inside the rule set → Create rule. Fill each field below:
Receipt rule — every field to fill
| SES Console field | Value | Notes |
|---|---|---|
| Rule name | catch-all-inbound | Any descriptive name |
| Status | Enabled | Rule must be Enabled to receive mail |
| TLS | Optional (or Required if policy mandates) | Non-TLS mail still accepted if Optional |
| Spam and virus scanning | Enabled | Fiko reads spamVerdict / virusVerdict from SNS payload |
| Recipients | *@inbound.example.com | Replace inbound.example.com with EMAIL_INBOUND_DOMAIN. Wildcard matches all per-channel addresses |
| [email protected] | You can add specific addresses — but *@ is enough because Fiko auto-generates per channel |
Actions — order and configuration (critical)
| # | Action type | Detailed configuration |
|---|---|---|
| 1 | Deliver to S3 bucket | Bucket: fiko-ses-inbound-prod (create first, same region e.g. us-east-1). Object key prefix: emails/ (optional). IAM role for this action: create role with trust policy ses.amazonaws.com + s3:PutObject on bucket — SES Console can auto-create this role when you pick the bucket |
| 2 | Publish to Amazon SNS topic | Attach SNS to the S3 action (recommended) OR add a standalone SNS action. Topic: fiko-ses-inbound-notify. Encoding: UTF-8 |
- 11Create S3 bucket (if not exists): S3 → Create bucket → name
fiko-ses-inbound-prod→ same region as SES.- Block public access: ON (private bucket)
- Versioning: optional
- Lifecycle: optional — delete objects after 90 days to save cost
- 12Create SNS topic: SNS → Topics → Create topic → name
fiko-ses-inbound-notify→ Standard. - 13Subscribe topic to Fiko webhook:
- Topic → Create subscription
- Protocol: HTTPS
- Endpoint: https://msg.fikosrv.com/v2/email/ses/inbound
- Enable raw message delivery: OFF (Fiko verifies SNS signature on JSON envelope)
Expected result
SNS sends SubscriptionConfirmation — Fiko auto-confirms via SubscribeURL. Check messaging-service log: SES SNS subscription confirmed. - 14(Optional) Bounce/complaint events — separate topic subscribed to:
https://msg.fikosrv.com/v2/email/ses/events
Configure in SES → Configuration sets or identity notifications. - 15Save receipt rule → send test email to
[email protected]→ verify:- S3 bucket has a new .eml object
- SNS topic shows successful delivery to Fiko endpoint
- CloudWatch / messaging-service log shows no Invalid SNS signature errors
*@inbound.example.com
Actions (order)
fiko-email-inboundfiko-ses-inboundPart D — Verify reply domain (send mail)
- 16SES → Verified identities → Create identity → Domain →
reply.example.com. - 17Enable Easy DKIM → publish the 3 CNAME records SES provides.
- 18Configure Custom MAIL FROM domain:
- MAIL FROM: e.g. bounce.reply.example.com
- Publish MX + SPF records SES provides for MAIL FROM subdomain
- 19Add SPF on reply domain:
Host: reply.example.com Type: TXT Value: v=spf1 include:amazonses.com ~all
- 20Add DMARC (start monitoring):
Host: _dmarc.reply.example.com Type: TXT Value: v=DMARC1; p=none; rua=mailto:[email protected]
| Type | Host | Value |
|---|---|---|
| TXT | reply.example.com | v=spf1 include:amazonses.com ~all |
| CNAME | *._domainkey | ← SES Easy DKIM (×3) |
| TXT | _dmarc | v=DMARC1; p=none; rua=... |
| MX | bounce.reply | ← Custom MAIL FROM (SES) |
Part E — IAM user for Fiko
- 21IAM → Users → Create user → name
fiko-email-ses→ do not enable console access. - 22Attach inline policy (replace bucket name if different):
{ "Version": "2012-10-17", "Statement": [ { "Sid": "SesSend", "Effect": "Allow", "Action": ["ses:SendRawEmail", "ses:SendEmail"], "Resource": "*" }, { "Sid": "SesInboundS3Read", "Effect": "Allow", "Action": ["s3:GetObject"], "Resource": "arn:aws:s3:::fiko-ses-inbound-prod/*" } ] }Expected result
Use a dedicatedfiko-email-sesuser — do not reuse PRIVATE_AWS_* (company file-upload S3 keys). - 23Security credentials → Create access key → Use case: Application running outside AWS → copy Access key ID + Secret.
Part F — Environment variables
- 24On fiko-messaging-service:
EMAIL_SES_ACCESS_KEY_ID=AKIA... EMAIL_SES_SECRET_ACCESS_KEY=... AWS_SES_REGION=us-east-1 EMAIL_INBOUND_DOMAIN=inbound.example.com EMAIL_REPLY_DOMAIN=reply.example.com
- 25On fiko-core-service (validates inbound address when agent creates forwarding channel):
EMAIL_INBOUND_DOMAIN=inbound.example.com
- 26Redeploy / restart messaging-service and core-service after updating env.
Part G — Verify end-to-end
- 27In Fiko → Apps → Email → Add new → Any email (forwarding).
- 28Enter support address → Fiko generates inbound address like
support-{token}@inbound.example.com. - 29Configure forwarding on the mail server → Test configuration → Connect.
- 30Complete test configuration → create channel → send test email.
Expected result
Chat appears in Fiko; reply sends from @reply.example.com with spf=pass, dkim=pass in Gmail "Show original".
DNS records — complete reference
Replace example.com with your real domain. Production uses e.g. inbound.fiko.ai / reply.fiko.ai.
| Host / Name | Type | Value | Purpose |
|---|---|---|---|
| inbound.example.com | MX | 10 inbound-smtp.us-east-1.amazonaws.com | Inbound — receive mail |
| inbound.example.com | TXT | (SES domain verification token) | Inbound — verify identity |
| *._domainkey.inbound.example.com | CNAME | (3 DKIM CNAME from SES — if DKIM enabled on inbound) | Inbound — optional |
| reply.example.com | TXT | v=spf1 include:amazonses.com ~all | Reply — SPF |
| reply.example.com | TXT | (SES domain verification) | Reply — verify identity |
| *._domainkey.reply.example.com | CNAME | (3 Easy DKIM CNAME from SES) | Reply — DKIM required |
| bounce.reply.example.com | MX | (MAIL FROM MX from SES Custom MAIL FROM) | Reply — bounce alignment |
| bounce.reply.example.com | TXT | v=spf1 include:amazonses.com ~all | Reply — MAIL FROM SPF |
| _dmarc.reply.example.com | TXT | v=DMARC1; p=none; rua=mailto:[email protected] | Reply — DMARC monitor |
Fiko webhook endpoints (SES)
| Endpoint | Method | Caller | Purpose |
|---|---|---|---|
| https://msg.fikosrv.com/v2/email/ses/inbound | POST (HTTPS) | Amazon SNS | New mail → Fiko parses → creates chat |
| https://msg.fikosrv.com/v2/email/ses/events | POST (HTTPS) | Amazon SNS | Bounce / complaint notifications (optional) |
SNS subscription confirmation: Fiko auto-calls SubscribeURL when it receives Type=SubscriptionConfirmation. No manual confirm needed if messaging-service is running and the endpoint is publicly reachable.
Per-channel inbound addresses
When an agent creates a forwarding channel, Fiko auto-generates the address — no separate receipt rule per address if the rule uses *@inbound.example.com.
# Example format (random token per channel): [email protected] # Env must match the suffix: EMAIL_INBOUND_DOMAIN=inbound.example.com
SES sandbox vs production access
| Check | Sandbox (default) | Production (required for real customers) |
|---|---|---|
| Send to unverified addresses | Blocked | Allowed |
| Receive inbound mail | Works if DNS + receipt rule configured | Same |
| Daily send quota | 200 emails/day | Starts at 50,000/day (request increase) |
| How to exit sandbox | — | SES Console → Account dashboard → Request production access |
Request production access with: use case (customer support email), expected volume, bounce/complaint handling, and confirmation that SPF/DKIM/DMARC are configured on the reply domain.
Environment variables reference
| Variable | Service | Example |
|---|---|---|
| EMAIL_SES_ACCESS_KEY_ID | messaging only | AKIA... |
| EMAIL_SES_SECRET_ACCESS_KEY | messaging only | ... |
| AWS_SES_REGION | messaging only | us-east-1 |
| EMAIL_INBOUND_DOMAIN | messaging + core | inbound.example.com |
| EMAIL_REPLY_DOMAIN | messaging only | reply.example.com |
Deliverability checklist
- SPF on reply domain includes amazonses.com
- DKIM Easy DKIM shows Verified in SES
- Custom MAIL FROM configured and verified
- DMARC published (start p=none, tighten later)
- SES production access granted (not sandbox)
- Warm up sending volume gradually
- Test with mail-tester.com — target score 8+/10
In Gmail "Show original", look for spf=pass, dkim=pass, dmarc=pass.
Use dedicated EMAIL_SES_* IAM credentials — never commit to git or reuse company S3 keys.