feat(1455): SMTP test-email button on the settings page - #1460
Merged
Conversation
GitHub issue #1455 — operators had no way to verify SMTP credentials short of waiting for a real outbound mail (password reset, ban protest, etc.), so broken SMTP routinely shipped into production for days before anyone noticed. This change adds a "Send test email" affordance inside the SMTP card on Admin → Settings → Main that fires `system.test_email` (a new JSON handler) and surfaces an operator-actionable toast — success or one of four structured error envelopes (validation / smtp_not_configured / rate_limited / mail_failed). Key shape decisions: - Permission gate: `ADMIN_OWNER | ADMIN_WEB_SETTINGS` — matches every other settings-page-only handler (sel_theme / apply_theme / clear_cache / preview_intro_text). - Rate limit: 1 attempt / 10s per panel install, stamped BEFORE the SMTP I/O so a hung relay can't be hammered while the first call is mid-handshake. File-backed (`SB_CACHE/test-email-throttle`) with atomic tempfile + rename, mirroring `_api_system_release_save_cache`. Validation / smtp_not_configured short-circuit BEFORE the throttle file is stamped so a typo doesn't consume a slot. - Button disabled at first paint when smtp.host / smtp.user / config.mail.from_email are empty (server-rendered), AND live- re-evaluated as the operator edits the form inputs so a fresh- install operator who just typed valid creds doesn't have to save first to see the button enable. Server-side guard is the `smtp_not_configured` envelope. - Recipient defaults to the logged-in admin's email so the "send the test to me" path is one click; the operator can override. - Audit log: every send attempt lands a row in `:prefix_log` (success OR mail_failed) so test sends can't be used to silently probe SMTP credentials or enumerate valid relay endpoints. Row body interpolates the admin's name + recipient. Coverage: - 9 PHPUnit tests under `SystemTest::testTestEmail*` — anonymous reject / malformed recipient / oversized recipient (RFC 5321 cap via FILTER_VALIDATE_EMAIL) / smtp_not_configured / mail_failed / rate_limited / default recipient / "validation doesn't burn a slot" / "smtp_not_configured doesn't burn a slot". 4 paired snapshot files lock the wire format of every error envelope; the success-shape snapshot lives in the E2E suite (PHPUnit has no SMTP test seam). - 1 PermissionMatrixTest row pins the `ADMIN_OWNER | ADMIN_WEB_SETTINGS` gate. - 6 Playwright tests (3 specs × chromium + mobile-chromium) under `web/tests/e2e/specs/flows/smtp-test-email.spec.ts` — happy path drives the full chain through mailpit + asserts the email lands at the operator's address with the right subject, plus disabled-state + native-validation arms. Throttle-cache shim (`web/tests/e2e/scripts/clear-test-email-throttle-e2e.php`) clears the 10s lock between specs so parallel project profiles don't collide; the shim mirrors `reset-e2e-db.php`'s refuse-if-prod-DB guard. - FAQ doc entry under `docs/src/content/docs/faq/index.md` walks operators through the prerequisites + rate limit + audit-log surfacing.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1455.
Summary
Adds a
Send test emailaffordance to the SMTP card on?p=admin&c=settings§ion=settingsso operators can verifytheir SMTP credentials end-to-end (Symfony Mailer → relay →
recipient inbox) without waiting for the next real outbound mail
(password reset, ban protest, etc.). Pre-fix, broken SMTP routinely
shipped into production for days before anyone noticed.
system.test_email(gatedADMIN_OWNER | ADMIN_WEB_SETTINGS, mirroring every othersettings-page-only handler).
config.mail.from_email are empty; live-re-evaluated as the
operator types so a fresh-install operator who just entered
valid creds doesn't have to save first.
the common "send the test to me" path is one click.
BEFORE the SMTP I/O so a hung relay can't be hammered while the
first call is mid-handshake. File-backed throttle
(`SB_CACHE/test-email-throttle`) with atomic tempfile + rename
mirroring `_api_system_release_save_cache`.
the throttle file is stamped so a typo doesn't consume a slot.
row interpolating the admin's name + recipient — test sends
can't be used to silently probe SMTP credentials.
Operator-visible flow
from_email), save the form.
email; tweak if desired.
Send test email.is empty. Configure SMTP first and save the form, then try
again.`
seconds.`
under Admin → System Log for the cause.`
Test plan
diff.
assertions, all green (includes 9 new `testTestEmail*`
PHPUnit tests + 4 paired snapshot files).
green.
— 6 tests green (3 specs × chromium + mobile-chromium): happy
path drives the full chain through mailpit + asserts the email
lands, disabled-state arm, and native-validation arm.
`system.test_email => ADMIN_OWNER | ADMIN_WEB_SETTINGS`.
message in mailpit, disabled state behaves correctly, rate
limit fires the expected toast.
was addressed:
spec;
reality (the retry hint is in the message, not a separate
field on the wire);
subject and body identifier match;
so mail_failed outcomes also consume slots (intentional);
across mounts);
the count check so a future refactor that drops the
admin/recipient interpolation fails loudly.
I have read the CLA Document and I hereby sign the CLA