Status (2026-10-07). SES in
ap-south-1is in the sandbox. Two earlier requests were denied (on 2026-07-11 and 2026-07-19) with only "for security reasons we cannot provide specific details". A third draft was written and never sent. Nothing has been submitted since.
- The code changes (declared-use guard, invite and sign-up limits, owner alerts, credentials kept out of logs, role mailboxes) were deployed and pushed to
mainon 2026-10-06.- The ten role mailboxes exist. Only platform-sent tests have run; the outside-account test is pending (R4).
hello@andfounder@stay on their Cloudflare rules by decision (gap-log G55, G56).- The operator is decided (2026-10-07) and the four new policy pages are live (deployed by the owner on 2026-10-07; the live text matches the repository). They promise a monitored
abuse@and no unconsented lists, so the outside-account test (R4) andrevoke-unconsented-import.mjsare now overdue.What is left is in
gap-log.md("Open at a glance") and the gates inreadiness-checklist.md. Submit only when every gate is green.Files here:
contact-addresses.md·readiness-checklist.md·case-message.md·reply-templates.md·evidence-checklist.md·case-log.md·gap-log.md·deploy-runbook.md·phase-b.md. The older draft,../aws-ses-production-access-case.md, is superseded.
1. The decision
Ask for the narrow thing first. This request is only for platform system mail: login and verification codes, password resets, workspace
invitations, account messages (welcome, extra workspace created), billing and credit notices, and alerts to workspace owners. The platform sends all
of it, to people who signed up, were invited, or (alerts) own the workspace. It is declared TRANSACTIONAL. The list comes from what the code
sends (the manager's email templates and comms' owner alerts); if a new system email is ever added, update the case text first.
Not in this request: newsletters, mailbox user-to-user mail, form notifications and replies, flows and automation mail, chat mail, tenants'
custom sending domains, the mail.vlozi.app shared lane, any imported list. That is a separate, later request (phase-b.md).
Why the earlier requests probably failed
AWS never confirmed a reason, so this is our assessment:
- They described a multi-tenant platform that sends other people's mail (newsletters, business mailboxes) from a new account with no sending history. That profile gets the hardest review, because it looks like a possible spam relay.
- The draft claimed controls that were not true (abuse@ monitored, DMARC quarantine, auto-pause, Turnstile on sign-up). A reviewer who checks one and finds it false rejects all of it.
- The public site gave no identity or abuse contact: no operator name, no address, a one-line acceptable-use rule, and a home page selling a free newsletter tool.
- The declared mail type did not match the story:
TRANSACTIONALdeclared, marketing described.
A narrow first request does not unblock a launch by itself: Resend already delivers vlozi.app system mail through the sandbox failover. Its value is a
foothold and a clean sending record for the tenant-mail request that follows. If SES keeps refusing, see "If SES keeps refusing" in phase-b.md.
2. What is true today (what the case may and may not say)
| Topic | Reality |
|---|---|
abuse@, postmaster@ |
Mailboxes on vlozi.app in Vlozi's own workspace since 2026-10-06. Platform-sent tests arrived; the outside-account test is pending (R4). Mail to an address nobody owns is accepted and dropped with no bounce. |
| DMARC | p=none with Cloudflare's report address; mail.vlozi.app has no record of its own. Tightening is optional (R3). |
| Custom MAIL FROM | None on the platform domains, so SPF is not aligned. bounce.vlozi.app does not exist yet (R3). |
| Bounce and complaint pipeline | SNS topic confirmed, signature check strict, suppression written. Needs a simulator proof (R5). |
| Reputation auto-pause | Built, but REPUTATION_ENFORCE is not set in production (checked 2026-10-07), so nothing pauses automatically; a pause is a manual staff action (gap-log G68). System mail is exempt. Do not claim automatic pausing. |
| Unsubscribe | Per workspace, not platform-wide. Irrelevant to Phase A (no marketing mail), but do not claim otherwise. |
| Public site | Operator identity and the four new policy pages are live since 2026-10-07 (R1, R8). The Abuse and Anti-spam pages promise things that are not yet tested or done (R4, gap-log G61). |
| Vlozi's own newsletter list | About 503 contacts who never opted in. Never send to them through SES; consent is revoked by a script you run (gap-log G17). |
| Support plan | Our notes disagree (Developer or Business vs Basic). Confirm in the console (R2). |
3. Go / no-go gates
Nothing is submitted until all ten are green. Evidence for each goes in readiness-checklist.md.
| # | Gate | Who |
|---|---|---|
| R1 | Operator identity published: name, address, contact on the site and in the policies | you |
| R2 | AWS account in good standing: payment method, account contact = operator, root MFA, support plan known, root email receives mail | you |
| R3 | Domain authentication: DKIM SUCCESS; custom MAIL FROM bounce.vlozi.app passing; DMARC published and the digest read (tightening optional) |
you (DNS); verify read-only |
| R4 | The role addresses (hello@, privacy@, abuse@, postmaster@, security@, legal@, plus private ops@) tested from outside; see contact-addresses.md |
you |
| R5 | Bounce and complaint pipeline proven with the SES mailbox simulator | both |
| R6 | Declared-use guard: SES carries platform mail only | deployed 2026-10-06; verify |
| R7 | Platform-mail abuse bounds (sign-up, resend and invitation limits) | deployed 2026-10-06; verify |
| R8 | Public site accurate; policy pages published | live 2026-10-07; the outside test and the revoke run are overdue |
| R9 | CloudWatch alarms on SES bounce and complaint rate, with an email target | you |
| R10 | Case log kept: every AWS message verbatim | file exists; open until the case closes |
4. Step by step
Roughly two to three weeks, most of it in parallel.
Phase 0: facts and decisions (you)
- Paste every previous AWS message you still have, even partial, into
case-log.md. If none remain, say so; the log starts now. Record every later AWS message verbatim, with the case ID and date. - In the AWS console confirm the account's age, payment method, root MFA, that the account contact matches the operator, and the Support plan
(the plan decides whether you can open a technical case and reach a human). Send a test mail to the root address (
founder@vlozi.app) from an outside account, because AWS replies there. Check you are in the right account: the SES account is the one signed in asfounder@vlozi.app, withvlozi.appunder Verified identities in Mumbai. The AWS CLI on the development machine points at a different, empty account (gap-log G77), so runaws sts get-caller-identitybefore any CLI command in this runbook, or use the console. - The address spelling is confirmed (2026-10-07). Make sure the AWS account contact carries the identical name and address.
- Put the docs site (
handbook.vlozi.app) behind a login. It is internal-only by intent, but it answers without one today (checked 2026-10-07, gap-log G70).
Phase 1: code changes (done 2026-10-06)
- The declared-use guard (
SES_TENANT_SENDING) is live. Tenant mail on the SES account is refused with 503TENANT_SENDING_NOT_ENABLEDunless Resend serves the sending domain. So today, workspaces onmail.vlozi.appcannot send campaigns, mailbox replies or form notifications, while mail fromvlozi.appgoes through Resend (gap-log G22, G23). It stays off SES until the separate tenant-mail request. - The invite, sign-up and verification-resend limits are live in the manager.
- The runbook for that deploy, with versions and rollback targets, is
deploy-runbook.md.
Phase 2: DNS and AWS configuration (you; verify with read-only lookups)
- Custom MAIL FROM (R3). SES console → Verified identities →
vlozi.app→ Authentication → Custom MAIL FROM domain →bounce.vlozi.app, "Use default value" on MX failure. In Cloudflare DNS (both DNS only) addMX bounce→feedback-smtp.ap-south-1.amazonses.compriority10, andTXT bounce→v=spf1 include:amazonses.com ~all. Wait until the console shows Successful. - DMARC (R3). Keep Cloudflare's
rua. Leavep=nonefor a week and read the digest; confirm SES and Resend both pass. Tightening top=quarantine; pct=25, thenpct=100, with relaxed alignment (adkim=r; aspf=r), is optional; never tighten before the digest is clean. - Role addresses (R4). The mailboxes exist. Send a test from an outside address to each and confirm it appears, then reply once and confirm it
arrives from the same address (
contact-addresses.md§4). Repeat monthly: a failure here is silent. - IAM. The
vlozi-ses-policypolicy already exists, soaws iam create-policyfails. Edit it in the IAM console (Policies →vlozi-ses-policy→ Edit → JSON) to match../aws-setup.md§2.1, which now includesses:GetAccount. From the command line, useaws iam create-policy-version --policy-arn <arn> --policy-document file://policy.json --set-as-default, withpolicy.jsonsaved as UTF-8 without a BOM (PowerShell 5.1Set-Content -Encoding utf8adds one and the call fails). Then runaws sesv2 get-account --region ap-south-1with a profile that hasses:GetAccountand recordProductionAccessEnabled, the real send quota and the suppression attributes. - Account-level suppression. Leave the SES default for now. The planned bounce-only change (
ses-account-suppression.mjs) belongs to the tenant-mail phase; do not run it. - CloudWatch alarms (R9).
Reputation.BounceRatewarn 2% / alarm 4%,Reputation.ComplaintRatewarn 0.05% / alarm 0.08%, to an email you read that does not depend on this platform being up. - Optional: register
vlozi.appin Google Postmaster Tools.
Phase 3: prove it and collect evidence
- The sandbox only sends to verified recipients. For each Gmail, Outlook or Yahoo address you want to test with, create it as an SES identity
(SES → Verified identities → Create identity → Email address, then click the link) and delete the identity afterwards. Without that, SES refuses and
the mail fails over to Resend, and the headers will show Resend, not SES. Send a login code and an invitation to each; in "Show original" confirm
SPF pass (aligned via
bounce.vlozi.app), DKIM pass, DMARC pass, and check that the comms log showsprovider: sesfor the send. - Run the simulator proof in R5 (invitations to the simulator addresses from the Vlozi workspace).
- File everything under
evidence/perevidence-checklist.md.
Phase 4: publish
Done on 2026-10-07 (the pages are live). What is left is the overdue pre-flight in R8: the outside-account test and the revoke script. Re-read each page against the case text before submitting.
Phase 5: submit (you, in the console)
SES → Account dashboard → Request production access. Region ap-south-1, mail type Transactional, website https://vlozi.app. Paste the text
from case-message.md after every bracketed tag is resolved or deleted. Submit, then copy the case ID and the submitted text into
case-log.md the same day.
Phase 6: handling the reply (within hours, not days)
- Approved: go to Phase 7.
- AWS asks questions: answer in the same case the same day, using
reply-templates.md, with evidence. - Denied with generic text: do not resubmit. Open a Support case (technical, SES) citing the case ID and ask which requirement is unmet; use phone or chat if the plan allows. Fix anything named, wait at least a week, then submit a new case.
- Denied with specifics: answer each point; fix the product or the site, not the wording.
Phase 7: approval day
- Read the granted quota and rate in the console and record them in
case-log.md. - Set the comms secret
SES_SANDBOXtofalse. Fromapps\communication, with$env:CLOUDFLARE_ACCOUNT_IDset, write{"SES_SANDBOX":"false"}to a file without a BOM (the Write tool, or[IO.File]::WriteAllTextwithUTF8Encoding($false)), runnpx wrangler secret bulk <file>, and delete the file. Never pipe intosecret puton Windows. - Check that
EMAIL_PROVIDERis notresend(if it is, SES is never tried), and that a platform send now showsprovider: sesin the comms log. - Leave
SES_TENANT_SENDINGunset so tenant mail stays off SES. - Keep Resend only as a transient-error fallback (
RESEND_DOMAINS). - Watch the SES reputation dashboard and the CloudWatch alarms daily for two weeks; stay under 2% bounce and 0.05% complaint.
- Update the website Status page and
../aws-setup.md§9 with the outcome.
5. Ground rules
- Say only what is true that day, and be able to show it. If a claim is not live, delete the sentence. The list of claims that must not appear is in
case-message.md. - No resubmitting after a denial without a Support case first.
- Never send to the ~503 un-consented contacts, from any provider. A "please confirm" email to people who never opted in is itself unsolicited mail.
- Keep the policy text, the website and the case consistent with each other; reviewers compare them.
6. Risks
- AWS may deny again even when everything is true: new accounts with no spend history are screened heavily, and the reply text is generic.
- DMARC tightened too early blocks your own mail.
- Publishing policies that nobody qualified has read: the drafts in
../../legal/are not legal advice. - An unconsented list is the easiest way to lose the account after approval.