From ee506425fd76b764f9fc5033f89c7041ffeacf94 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Wed, 27 May 2026 14:46:33 +0000 Subject: [PATCH] Drop link from email body; fix paste + stale-resend-notice UX bugs Email body now contains the 6-digit code only. The /verify/[token] route and the EmailProvider link callback are intentionally left wired (just not advertised in the email) so reverting to a link-bearing template is a content-only change if the all-code variant doesn't improve deliverability for the Atkins-style blocked recipients. Hypothesis being tested: corporate gateway URL scanners are part of why some emails got silently quarantined, and a short transactional body without an auth-token URL clears more filters. Two small UX bugs surfaced in preview testing also fixed here: - Paste of "482 911" (with the visual space from the email's formatted code) was dropping a digit. Root cause: maxLength=6 on the input truncated the 7-char paste before our \D-stripping ran. Drop the attribute and let the existing .slice(0, 6) after stripping handle the bound. Pasting the formatted code now works. - After requesting a resend and then typing into the code field, the green "we've sent a new code" notice would re-appear as soon as the previous error message cleared. handleChange now clears the resend notice on the next keystroke, alongside the error clear it already did. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/app/auth/verify-code/VerifyCodeForm.tsx | 5 +- src/app/email_templates/magic_link.ts | 57 +++------------------ 2 files changed, 9 insertions(+), 53 deletions(-) diff --git a/src/app/auth/verify-code/VerifyCodeForm.tsx b/src/app/auth/verify-code/VerifyCodeForm.tsx index 6073bc2..a38ee9a 100644 --- a/src/app/auth/verify-code/VerifyCodeForm.tsx +++ b/src/app/auth/verify-code/VerifyCodeForm.tsx @@ -57,6 +57,7 @@ export default function VerifyCodeForm({ email }: { email: string }) { const digits = next.replace(/\D/g, "").slice(0, 6); setCode(digits); if (error) setError(null); + if (resendNotice) setResendNotice(null); if (digits.length === 6) void submitCode(digits); } @@ -93,7 +94,6 @@ export default function VerifyCodeForm({ email }: { email: string }) { id="verify-code" inputMode="numeric" autoComplete="one-time-code" - maxLength={6} value={code} onChange={(e) => handleChange(e.target.value)} placeholder="••••••" @@ -133,9 +133,6 @@ export default function VerifyCodeForm({ email }: { email: string }) { -

- Or use the one-click link from your email instead. -

); } diff --git a/src/app/email_templates/magic_link.ts b/src/app/email_templates/magic_link.ts index cae8d89..5e59b92 100644 --- a/src/app/email_templates/magic_link.ts +++ b/src/app/email_templates/magic_link.ts @@ -1,6 +1,7 @@ -// Contains the email template for user sign in. Email contains a 6-digit code -// (primary action — type at /auth/verify-code) and a sign-in link (fast-path -// for users whose corporate gateway doesn't mangle URLs). +// Email template for user sign-in. Body contains a 6-digit code only — the +// magic-link path is still wired (see /verify/[token] and the EmailProvider +// callback) but the URL is intentionally omitted from the email to reduce +// the content-scanner surface area for corporate email gateways. import { createTransport } from "nodemailer"; import { buildMailHeaders } from "./buildMailHeaders"; @@ -18,41 +19,25 @@ export async function MagicLinksEmail({ }): Promise<{ messageId: string }> { const parsed = new URL(url); const host = parsed.host; - - const baseUrl = parsed.origin; - const logoUrl = `${baseUrl}/domna-email-logo.png`; - - const token = parsed.searchParams.get("token"); - const email = parsed.searchParams.get("email"); - - if (!token || !email) { - throw new Error("Magic link token or email missing"); - } - - // Direct the link at the existing two-step verify page (defends against - // email-scanner pre-fetching), not at the NextAuth callback. - const loginUrl = `${parsed.origin}/verify/${token}`; + const logoUrl = `${parsed.origin}/domna-email-logo.png`; const transport = createTransport(provider.server); const brandColor = "#14163d"; const accentColor = "#2d348f"; - const brown = "#c4a47c"; const background = "#F9F9F9"; const result = await transport.sendMail({ to: identifier, from: provider.from, subject: "Sign in to Ara", - text: plainText({ code, url: loginUrl, host }), + text: plainText({ code, host }), html: domnaHtml({ code, - url: loginUrl, logoUrl, host, brandColor, accentColor, - brown, background, }), headers: buildMailHeaders({ @@ -77,21 +62,17 @@ function formatCodeForDisplay(code: string): string { function domnaHtml({ code, - url, logoUrl, host, brandColor, accentColor, - brown, background, }: { code: string; - url: string; logoUrl: string; host: string; brandColor: string; accentColor: string; - brown: string; background: string; }) { const escapedHost = host.replace(/\./g, "​."); @@ -113,7 +94,7 @@ function domnaHtml({ - +

Your sign-in code

Enter this code at ${escapedHost} to sign in to Ara. @@ -124,17 +105,6 @@ function domnaHtml({

This code expires in 10 minutes.

- - - - -

- Or use the one-click link instead: -

- - Sign in to Ara -

If you didn’t request this email, you can safely ignore it.

@@ -150,24 +120,13 @@ function domnaHtml({ `; } -function plainText({ - code, - url, - host, -}: { - code: string; - url: string; - host: string; -}) { +function plainText({ code, host }: { code: string; host: string }) { return `Sign in to Ara by Domna Your sign-in code: ${code} Enter this code at ${host}/auth/verify-code to sign in. -Or use the one-click link instead: -${url} - This code expires in 10 minutes. If you did not request this email, you can safely ignore it. `; }