Email system
Companion doc
For step-by-step "how do I send mail as [email protected] from my Gmail" instructions, see Send mail from @sponicgardens.com via Gmail. This page is the architecture / current-state map.
One-paragraph summary
Inbound: mail to any @sponicgardens.com address hits Cloudflare's MX, Email Routing matches it against rule literals (or the catch-all), and forwards to a verified Gmail destination. Outbound: Gmail's "Send mail as" relays through Resend's SMTP gateway (smtp.resend.com:587), which signs the message with our DKIM key on the sponicgardens.com zone and ships it via Amazon SES under the hood. Replies come back to the alias and re-enter the inbound flow. No real mailboxes; everything is aliases plus filters.
Domains
| Domain | Status | Role |
|---|---|---|
sponicgardens.com |
primary | Live marketing site (sponicgardens.com), intranet (in.sponicgardens.com), and all email. Resend-verified. CF Email Routing active. |
sponicgarden.com |
deprecated | Old single-s domain. Web traffic redirects to sponicgardens.com. Email Routing still active (catch-all → journeymistique) so old correspondents don't bounce. Resend domain still registered but should be removed once no further mail expected. |
spogn.com |
active | Short-domain. Web redirects to sponicgardens.com. Email forwards via chain (sonia/[email protected] → @sponicgardens.com → gmails) so future Workspace migration only touches the Sponicgardens side. |
Inbound flow (Cloudflare Email Routing)
Active rules on sponicgardens.com
| Match (To) | Forward to | Status |
|---|---|---|
[email protected] | [email protected] | enabled |
[email protected] | [email protected] | enabled |
[email protected] | [email protected] | enabled |
[email protected] | [email protected] | enabled |
| catch-all (everything else) | — (disabled, drops) | disabled |
Note: Catch-all is disabled on sponicgardens.com — mail to an undefined alias bounces. On sponicgarden.com the catch-all is enabled and forwards to journeymistique, since old correspondents may still type the deprecated domain.
Active rules on sponicgarden.com (deprecated)
| Match (To) | Forward to | Status |
|---|---|---|
[email protected] | [email protected] | enabled |
| catch-all | [email protected] | enabled |
Verified destinations (account-wide on wingsiebird CF account)
Cloudflare requires every destination address to be verified before any rule can target it. Verification = CF sends a one-time link to the address; the human clicks it.
[email protected]— primary inbox foraccounts@, catch-alls[email protected]— rahulio's @-of-record[email protected]— rahul's main personal gmail (alsoteam@destination)[email protected]— sonia's gmail[email protected]— carryover from alpacapps; safe to remove if unused[email protected]+[email protected]— chain-routing pivots used when forwarding fromspogn.comthroughsponicgardens.comon to gmails
Outbound flow (Resend SMTP)
DNS records that make it work (on sponicgardens.com zone, wingsiebird CF account)
| Type | Name | Value | Purpose |
|---|---|---|---|
| MX 41 | @ | route1.mx.cloudflare.net | Inbound — CF Email Routing accepts mail |
| MX 70 | @ | route2.mx.cloudflare.net | |
| MX 10 | @ | route3.mx.cloudflare.net | |
| TXT | @ | v=spf1 include:_spf.mx.cloudflare.net ~all | SPF for inbound (CF auto-adds) |
| TXT | resend._domainkey | p=MIGfMA0GCSqGSIb3... (Resend DKIM) | Outbound DKIM signing |
| MX 10 | send | feedback-smtp.us-east-1.amazonses.com | Outbound bounces / FBL |
| TXT | send | v=spf1 include:amazonses.com ~all | SPF on the bounce subdomain |
Resend account state
- Domain:
sponicgardens.com, status verified (id36395b8c-d435-4f8a-8049-7fa828607a12, region us-east-1). - Old domain:
sponicgarden.com(single-s) still registered and verified; should be removed once no app code or saved Gmail "Send mail as" entry still references it. - Limits: Free tier — 3,000 emails/month, 100/day. Plenty for personal correspondence; bump if a real send-blast lands in scope.
- SMTP creds for Gmail "Send mail as": host
smtp.resend.com, port587, userresend(literal), password = Resend API key from BW itemResend — Sponic Gardens Email(id87184c25-8884-497c-94d2-b41600586a6e), custom fieldAPI Key (Sending Only).
How to add things
A new alias forwarding to an existing verified gmail
Most common case — e.g. [email protected] → [email protected]. One CF API call:
export BW_PASSWORD=$(security find-generic-password -a "[email protected]" -s "bitwarden-cli" -w)
SESSION=$(/opt/homebrew/bin/bw unlock --passwordenv BW_PASSWORD --raw 2>/dev/null)
TOK=$(/opt/homebrew/bin/bw get item d0fdd0eb-e175-4bb4-ada4-b43a003fdd99 --session "$SESSION" | jq -r '.login.password')
ZONE="b0129785b612f8b7b34f7af37c449a37"
curl -sS -X POST "https://api.cloudflare.com/client/v4/zones/${ZONE}/email/routing/rules" \
-H "Authorization: Bearer $TOK" -H "Content-Type: application/json" \
-d '{
"name": "[email protected] → [email protected]",
"matchers": [{"type":"literal","field":"to","value":"[email protected]"}],
"actions": [{"type":"forward","value":["[email protected]"]}],
"enabled": true,
"priority": 10
}' | jq .success
A new gmail destination (must verify before use)
ACCT="9cd3a280a54ce2a5b382602f0247b577"
curl -sS -X POST "https://api.cloudflare.com/client/v4/accounts/${ACCT}/email/routing/addresses" \
-H "Authorization: Bearer $TOK" -H "Content-Type: application/json" \
-d '{"email":"[email protected]"}' | jq .
CF sends a verification email directly to that gmail (it does not go through any CF rule, so the address must really exist and you must have access). Click the link, then it can be used as a forward target.
Forwarding to multiple destinations
Multi-destination is silently broken — one rule per destination
The CF API accepts "value": ["a@x", "b@x"] on a forward action and the dashboard shows it as valid — but the MTA only delivers when the array has exactly one entry. Multi-value forwards return 550 5.1.1 Address does not exist at SMTP time, indistinguishable from a non-existent address. The API also rejects multiple action objects on one rule (error 2007: only one action per rule is allowed).
To fan out: create one rule per destination, all with the same to matcher. CF allows multiple rules with identical matchers; both/all will fire.
The catch-all rule uses a special endpoint
The catch-all has a regular rule id but the generic PUT /rules/{id} endpoint returns code 2020: Invalid rule operation. Always use:
PUT /zones/{zone}/email/routing/rules/catch_all
with the same body shape (matchers / actions / enabled).
Send mail as (Gmail outbound)
One Send mail as entry per alias, set up in Gmail Settings → Accounts. Use Resend SMTP creds (above). Verification email round-trips through CF (alias → destination gmail → you click). Full step-by-step: Send mail from @sponicgardens.com via Gmail.
Known gotchas
- Multi-destination MTA bug (above) — one destination per rule, always.
- Catch-all needs the dedicated endpoint (above) — can't use
/rules/{id}. - Gmail self-loop dedup — if you send from
[email protected](via an alias) to any address that ultimately routes back tojourneymistique, Gmail silently suppresses the inbox copy. Test forwarding only from a non-Gmail external sender or a different Gmail account. - CF MTA rule edits take 30–60s to propagate. If you update a rule and immediately send a probe, you may still hit the old behavior. Wait a minute or use the openssl SMTP probe (below) to confirm.
- Gmail filter "Forward to" is independent of CF — if a Gmail filter on
journeymistiquere-forwards to another gmail, that target must be added at the Gmail level (Settings → Forwarding and POP/IMAP → Add a forwarding address) and verified at the Gmail level. Filters silently no-op until then. - Resend domain must be verified before sends — sends from
[email protected]bounce550 The sponicgardens.com domain is not verifieduntil Resend flips the status to verified. DNS-visibility is not enough; Resend has its own internal check that takes 15min–2hr after DNS detection. - Tokens are scoped per zone. The
d0fdd0eb-…token only works onsponicgardens.com; the16dc95de-…token only onsponicgarden.com. They each carry DNS Write + Email Routing R/W.
Diagnostic recipes
Probe a rule directly at CF's MTA (skip Resend)
( sleep 1; printf "EHLO test.com\r\n"; sleep 0.3; printf "MAIL FROM:<[email protected]>\r\n"; \
sleep 0.3; printf "RCPT TO:<[email protected]>\r\n"; sleep 0.3; printf "QUIT\r\n"; sleep 0.3 ) \
| openssl s_client -starttls smtp -crlf -quiet -connect route1.mx.cloudflare.net:25 2>&1 \
| grep -E "Address does not exist|2.1.0 Ok"
2.1.0 Ok after RCPT TO → CF accepts. 550 Address does not exist from a rule the API shows as enabled → either the multi-destination bug or an unverified destination.
Send a real test via Resend
RESEND_KEY=$(/opt/homebrew/bin/bw get item 87184c25-8884-497c-94d2-b41600586a6e --session "$SESSION" \
| jq -r '.fields[]|select(.name=="API Key (Sending Only)").value')
curl -sS -X POST https://api.resend.com/emails \
-H "Authorization: Bearer $RESEND_KEY" -H "Content-Type: application/json" \
-d '{"from":"[email protected]","to":["[email protected]"],"subject":"chain-test","text":"body"}'
Returns {"id":"…"} on queue success. Check that the message lands in the destination inbox within a minute. Always send from a domain alias, not from your real gmail (Gmail self-loop dedup will hide it).
Future work
- Profile pictures in Gmail recipient avatars. Today recipients see initial-circles for our @sponicgardens.com mail. Three paths: Gravatar (free, doesn't apply in Gmail), BIMI (~$1k/year + DMARC strictness, gives a domain-wide brand mark), or Google Workspace ($7/user/mo, real per-mailbox photos). Deferred pending a Workspace decision.
- DMARC. Currently no DMARC record. Recommended first step:
v=DMARC1; p=none; rua=mailto:[email protected]as a monitoring-only baseline. Required before BIMI; harmless to set up early. - Decommission
sponicgarden.comResend domain. Once no Gmail "Send mail as" entry references it, delete the Resend domain to keep the dashboard clean. - Workspace evaluation. Single mailbox + 30 free aliases ($7/mo) vs per-person mailboxes ($7/user/mo) vs continuing with the Gmail-aliases-and-CF-routing scheme (free).
Doc owner: Rahul. Last updated 2026-05-03. Operational secrets in BW folder devops-sponic; full token / endpoint / ID index lives in the auto-memory at ~/.claude/projects/-Users-rahulio-Documents-CodingProjects-sponic/memory/service-access.md.