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) <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-05-27 14:46:33 +00:00
parent d042606955
commit ee506425fd
2 changed files with 9 additions and 53 deletions

View file

@ -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 }) {
</button>
</div>
<p className="text-xs text-gray-400 text-center">
Or use the one-click link from your email instead.
</p>
</div>
);
}

View file

@ -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, "&#8203;.");
@ -113,7 +94,7 @@ function domnaHtml({
</td>
</tr>
<tr>
<td align="center" style="padding: 28px 24px 12px; color: #333;">
<td align="center" style="padding: 28px 24px 28px; color: #333;">
<h2 style="color: ${brandColor}; font-size: 22px; margin: 0 0 8px;">Your sign-in code</h2>
<p style="font-size: 14px; line-height: 1.5; color: #666; margin: 0 0 20px;">
Enter this code at <span style="color: ${accentColor};">${escapedHost}</span> to sign in to Ara.
@ -124,17 +105,6 @@ function domnaHtml({
<p style="font-size: 12px; color: #888; margin: 12px 0 0;">
This code expires in 10 minutes.
</p>
</td>
</tr>
<tr>
<td align="center" style="padding: 4px 24px 24px; color: #333;">
<p style="font-size: 13px; color: #777; margin: 8px 0 12px;">
Or use the one-click link instead:
</p>
<a href="${url}" target="_blank"
style="display: inline-block; padding: 10px 22px; background: ${brown}; color: #fff; text-decoration: none; border-radius: 6px; font-weight: 600; font-size: 14px;">
Sign in to Ara
</a>
<p style="margin-top: 28px; font-size: 13px; color: #777;">
If you didnt request this email, you can safely ignore it.
</p>
@ -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.
`;
}