Magic-link sign-in
Passwordless sign-in via a one-time link emailed to the user. Lives alongside the original email + password flow — both work, pick whichever you prefer per sign-in. No account migration; the same user can use either path interchangeably.
Email delivery is via Resend. If the RESEND_API_KEY
env is unset, the magic link tab still renders but send attempts fail
with a clear error — the password tab keeps working.
Sign in #
/sign-in shows two tabs: password (default) and magic link.
On the magic-link tab, enter your email and hit send link:
- The api emails a one-time link from
[email protected]. - You click the link — it lands at
/auth/verify?token=…. - The verify page exchanges the token with the api, the api sets a
session cookie, and you bounce to wherever you were headed (or
/if you came in fresh).
Tokens expire 15 minutes after send and are single-use — clicking
twice consumes the token on the first click and 410s the second. Sign-up
works the same way: /sign-up has the same two tabs.
Rate limits #
Two independent counters; first to trip wins:
- 3 requests per email per 15 minutes — the honest-user-with-stuck-inbox retry budget.
- 10 requests per IP per hour — bulk-abuse cap.
A 429 response renders inline on the form with a retry-after hint when the api includes one.
Opting out #
There's no STECH_NO_MAGIC_LINK env. The password tab is always
available — if you'd rather use email + password, just stay on the
default tab.
Troubleshooting #
Didn't get the email — check spam; the From is [email protected].
SPF/DKIM/DMARC are configured on the sender subdomain so deliverability
should be clean. If it still doesn't show after 2–3 minutes, request a
new link (the old one stays valid until used or expired).
Link expired — tokens live 15 minutes. Just request a new one from
/sign-in.
Already used — links are single-use. Request a new one.
Rate limited — wait out the window shown in the inline error. If you're hitting the per-IP limit (10/hour) from a shared NAT, switch to the password tab as a workaround.
Related #
- Invitation emails (invitations.md) reuse the same Resend setup; if magic-link emails work, invitation emails will too.
- Webhooks — same encrypted-at-rest posture
(
SecretsCrypto) for the per-endpoint signing secret.