Skip to content

LOTTO v1.25.1 — SMTP refused in production

Choose a tag to compare

@MattGeiger MattGeiger released this 26 Aug 23:47
· 66 commits to main since this release

LOTTO v1.25.1 makes the SMTP transport structurally unavailable in production.

The gap

Every branch in auth-email-service.ts that reached smtpTransport() already checked isProduction — except the one where RESEND_API_KEY is absent or malformed, which fell through unguarded.

That was reachable in principle. src/lib/auth.ts refuses to start in production without a valid key, but /api/auth/otp/request imports the email service directly and never loads that config, so the OTP path was not covered by it. Delivery in production depended on the environment being correct rather than on the code refusing to do otherwise.

Caller Route Previously guarded?
sendMagicLinkEmail via src/lib/auth.ts ✅ config throws without a valid key
sendOtpEmail /api/auth/otp/request ❌ imports the service directly

Why it matters more than its severity

Practical impact of the old behaviour was low: an SMTP attempt on Vercel would have dialled localhost:1025, been refused, and failed the request closed without sending mail. The nodemailer advisory also needs attacker-controlled message options, and LOTTO builds the message itself.

The value is in what it settles. That advisory requires nodemailer 9.0.1 or later, which falls outside the Auth.js peer range of ^7.0.7 || ^8.0.5, so it cannot be resolved by upgrading while Auth.js stays on its current line. Production can no longer construct an SMTP transport at all — which makes the advisory's dev-only reachability a property of the code rather than of the deployment configuration.

Tests

tests/auth-email-transport.test.ts covers the delivery-transport contract, which previously had no tests at all. Both exported senders are exercised, because they are reached by different routes with different guards in front of them:

  • production refuses SMTP on the OTP path, the magic-link path, and when the key is present but malformed
  • Resend still delivers when the key is valid
  • development keeps the local SMTP/MailDev path on both senders

The three guard assertions were confirmed to fail with the guard removed, so they are regression guards rather than descriptions of current behaviour.

110 test files, 795 tests, tsc, lint, a production build and the legacy-bundle scan all pass.

Full changelog: v1.25.0...v1.25.1