Skip to content

fix(deploy): plan and apply refuse an unset or example email.from - #132

Merged
jayleaton merged 1 commit into
mainfrom
fix/deploy-email-from-required
Oct 23, 2026
Merged

jayleaton merged 1 commit into
mainfrom
fix/deploy-email-from-required

Conversation

@jayleaton

Copy link
Copy Markdown
Owner

Summary

#103 makes web and admin refuse to boot in production with an unset FROM_EMAIL or one on a reserved example/test domain. Every Railway service runs with APP_ENV=production, and deploy/ defaulted email.from to noreply@example.com, so a fresh make railway-setup on defaults would deploy apps that never boot. This makes the deploy side refuse first. It goes in before #103.

 deploy/platform
+  RealSender(address)        same rule as #103's realSender: example.com/.net/.org, example, test, invalid, localhost; "Name <a@b>" ok
+  Config.SenderProblems()    per environment: email.from, or per_environment.<env>.from_email (manual: not checked)
~  email.from default        noreply@example.com -> unset
~  Edit                      sets dotted keys (email.from)
 cmd/railway-setup
+  plan / setup / apply      refuse before touching anything, error + nextSteps entry
+  apply on a terminal, no --yes: asks for the sender, writes it into platform.yaml
+  --email-from / PLATFORM_EMAIL_FROM
+  doctor                    fails the email.from check
 deploy/platform.yaml        email.from: "" with a "required" comment
 docs/deploy.md, AGENTS.md   a real sender is required; agents ask the developer for it

Evidence

Read-only bin/railway-setup runs against a scratch platform.yaml (staging,production; project gfs-senderprobe-261023, which does not exist and was never created: railway list shows 0).

case command result
unset plan --json exit 1: refusing to deploy apps that would not boot: production needs a real FROM_EMAIL (staging: email.from is not set; production: email.from is not set): set email.from in deploy/platform.yaml … and nextSteps[0] Set a real sender for auth emails: …
noreply@example.com plan --json exit 1: staging: "noreply@example.com" is an example or test address; production: …
"Acme <noreply@acme.dev>" + per_environment.staging.from_email: qa@mail.test plan --json exit 1: only staging: "qa@mail.test" is an example or test address
"Acme <noreply@acme.dev>" plan --json exit 2, 9 change(s) pending (a normal plan)
unset, non-interactive apply --yes --json < /dev/null exit 1, "project": null, the nextSteps entry; nothing created
unset plan --email-from noreply@acme.dev platform.yaml: set email.from (commit it); the file now has from: noreply@acme.dev, exit 2
unset doctor --json exit 4, email.from check fail

make check-fast ok, make deploy-vars ok (44 app variables), make lint-layering ok.

No unit tests, on purpose: the brief asked for tests in deploy/platform, but scripts/check-test-boundaries.sh (AGENTS.md "Test boundaries", mandatory) allows tests only in the five service/handler/auth directories and rejects any _test.go in deploy/. deploy/ has no tests on main. The runs above cover the unset, example and real-sender cases instead. To get tests here, JJ would have to sign off on changing that rule.

Merge Danger

Door: two-way.

Blast Radius: deploys

Projects whose platform.yaml still says noreply@example.com (or has no sender) now get a refused plan/apply with the fix spelled out. That is intended: with #103 their apps would not boot. A manual from_email is not checked, because its value lives in Railway.

🤖 Generated with Claude Code

Every service runs with APP_ENV=production, and the apps refuse to boot
with an unset FROM_EMAIL or one on a reserved example/test domain (#103).
deploy/ defaulted email.from to noreply@example.com, so a fresh make
railway-setup on defaults would deploy apps that never boot.

- platform: RealSender, the apps' exact rule (example.com/.net/.org,
  example, test, invalid, localhost; "Name <a@b>" allowed), and
  SenderProblems per environment (email.from or
  per_environment.<env>.from_email; manual is not checked). email.from
  now defaults to unset.
- railway-setup: plan, setup and apply refuse before touching anything,
  with a next step; apply on a terminal without --yes asks for the
  sender and writes it into platform.yaml. New --email-from /
  PLATFORM_EMAIL_FROM (platform.Edit now sets dotted keys). doctor fails
  the email.from check.
- docs/deploy.md and AGENTS.md: a real sender is required.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jayleaton
jayleaton merged commit ece908d into main Oct 23, 2026
3 checks passed
jayleaton added a commit that referenced this pull request Oct 23, 2026
The apps' realSender and deploy/platform's reservedSenderDomains (#132)
refuse the same domains; both now say so. docs/deploy.md links to #132's
paragraph instead of repeating it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jayleaton added a commit that referenced this pull request Oct 23, 2026
* fix(config): production refuses an unset or example FROM_EMAIL

Both apps fell back to noreply@example.com when FROM_EMAIL was unset, and
accepted it in production, where no mail provider sends from it: every
verification, reset and invite email would fail, visible only in the log.
In production the web and admin configs now refuse an unset FROM_EMAIL and
any sender on a reserved example or test domain (RFC 2606/6761), in either
"a@b" or "Name <a@b>" form, so the deploy fails its boot with the reason.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(smoke): the release smoke boots with a FROM_EMAIL off the example domains

Production now refuses an unset or example sender, so the smoke (which
boots with only what production requires) sets one. Nothing is sent: the
Resend key is a dummy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(config): point FROM_EMAIL's checks at each other

The apps' realSender and deploy/platform's reservedSenderDomains (#132)
refuse the same domains; both now say so. docs/deploy.md links to #132's
paragraph instead of repeating it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant