Repository navigation
Description
The gap
docs/architecture/license/portal.md lists this under Known gaps:
A bad SMTP configuration raises on the first send, not at startup, so
/license/trialcan create the trial and then fail with 500.
Why it matters
The trial route is careful about every other failure. It checks mail is enabled before it does anything, answers 503 if Keygen is unreachable, 503 if the mail server is temporarily down and says the trial exists, and 400 if the recipient was refused. The one case it cannot see is its own misconfiguration: SmtpMailer.__init__ raises ValueError for a missing host, a missing sender or an SMTP_SECURITY that would send credentials in the clear, and the mailer is not built until the first send — which happens after the trial has been created in Keygen.
So the caller gets an opaque 500, and the address is spent: trialKey now carries their folded email, so trying again answers 409 "already claimed". One typo in a deployment's .env turns every first trial attempt into a burned address, and nothing in the deployment says so until a user hits it.
Where
surfsense_backend/app/mailer/smtp.py—SmtpMailer.__init__holds the four configuration checks, each raisingValueError. Its own docstring says a deployment with noFromat all "should fail at startup, not at the first send".surfsense_backend/app/mailer/factory.py—get_mailer()builds lazily and caches;build_mailer()andis_mail_enabled()are the two seams already exposed.surfsense_backend/app/license/router.py—_require_mailer()is the existing guard, called first by both POST routes precisely so the refusal is outcome-independent. Whatever you add belongs with it rather than beside it.surfsense_backend/app/license/issue.py—issue_trial_license()is what runs before the send and is what makes the ordering matter.
What done looks like
- A deployment with
SMTP_ENABLEDon and a broken SMTP configuration is visibly broken before it serves a request, or at the latest refuses the request before spending anything. /license/trialon such a deployment never creates a trial it cannot deliver.- One decision, yours: fail the process at startup, or validate inside
_require_mailer()so the route answers 503 like every other unavailable-mail case. Starting up loudly matches the factory's stated philosophy — mail being unreachable is "an error to fix, never a reason to silently drop mail" — while a 503 keeps the licence routes' habit of never turning an outage into an unhandled error. Pick one and say why; do not do both halfway. - Update the doc's "Mail" bullet that records this as a gap, and delete the Known gaps line in the same PR.
What not to change
_require_mailer() must keep running before any Keygen lookup, and /license/resend must keep answering identically whatever it finds. Both exist so the endpoint cannot be used to discover who is a customer, and a configuration error that reported differently depending on the address would undo that. Leave the two error types in protocol.py alone as well — a configuration fault is neither MailerUnavailableError nor MailerRejectedError, and forcing it into one of them loses the distinction between "our server is down" and "our settings are wrong".
How to verify
surfsense_backend/tests/unit/mailer/test_factory.py and tests/unit/mailer/test_smtp.py already cover the configuration checks and the error mapping. Add the failing test first: mail enabled, configuration bad, and assert the trial is never created.
cd surfsense_backend
uv run pytest -m unit
uv run ruff check .Before you start
Read CONTRIBUTING.md, the license portal doc and ADR 0024. This code is under surfsense_backend/app/license/ and app/mailer/, not app/proprietary/, so it is open to contributors. Never put a real host, sender or credential in a test fixture, a log line or the PR description. Open your PR against dev, not main. Comment here to claim this.
Activity
mehul2409 commentedon Sep 29, 2026
I'd like to claim this one. I'll start with the failing test (mail enabled, bad SMTP configuration, trial never created) and then make the fix.
fix(license): answer 503 before Keygen when SMTP config is invalid
MODSetter commentedon Sep 29, 2026
Fixed by #2047,
fix(license): answer 503 before Keygen when SMTP config is invalid, by @mehul2409. Merged intodev.Closing by hand rather than by keyword:
Fixes #1963only auto-closes on a merge to the default branch, and this repo merges throughdev(CONTRIBUTING), so this would have stayed open and claimable while the work was already done. It reachesmainwith the next release.Nothing left to pick up here.