Let a customer resend their own verification email from My Account #110

Closed
opened 2026-08-21 18:58:59 -05:00 by bermudalamb · 0 comments
Owner

A verification email is sent once, at registration. If it is lost, filtered, or arrives after the link has expired — the token lasts 24 hours — the customer has no way to ask for another. There is no resend endpoint and no button.

What it needs

A route that mints a fresh verification token for the signed-in customer and sends the verification email, plus a button on the account page. The email itself is already editable copy after #92, so this is the trigger rather than the message.

The existing password-reset flow is the pattern to follow closely, because it has already solved most of the same problems:

  • Supersede the outstanding token. Password reset deletes any existing token before minting a new one, so a link cannot be resurrected later from an older message in the inbox. The same reasoning applies here.
  • Refuse when there is nothing to do. A customer whose address is already verified should get a clear answer rather than a pointless email.

The part that needs care: this is a mail amplification endpoint

Anything that makes the server send email on request is an abuse vector, and this one sends to an address the caller controls rather than one they name — which is better, but not a reason to leave it unbounded.

It must be rate limited. rateLimit.ts already holds the two existing limiters and the reasoning behind each, including why passwordResetRequestLimiter keys on caller and email and why reusing it on an endpoint without an email collapses everyone into one bucket. A resend limiter keys naturally on the customer id, since the caller is signed in — which is tighter than either existing limiter and sidesteps the IPv6 question #84 dealt with entirely.

Worth deciding: how many resends in what window. Something like three per hour is generous for a real person and useless to anyone hammering it.

Worth deciding

  • What the customer sees when the limit is hit. A clear "we have already sent several — check your spam folder" is more useful than a bare 429, and it is also the honest message, since the mail probably did send.
  • Whether the account page shows verification state at all. Today nothing on the page says whether the address is verified. A button to resend is odd next to no indication that anything needs verifying, so this likely wants a small status line alongside it.
  • Whether an unverified account is restricted in any way. It is not today, which is a reasonable choice for a small storefront — but it means resending is a tidiness feature rather than an unblocking one, and that changes how prominent the button should be.

Verification

Integration tests: a resend mints a new token and invalidates the previous one, an already-verified account is refused, an unauthenticated call is refused, and the limiter engages after the allowance. Note the limiter assertion needs care — its store is process-wide, and #62 and #84 both recorded that exhausting an allowance in a test leaks into every later test keyed the same way. Keying on customer id makes that easier to contain, since each test can use a fresh customer.

End to end: the button appears on the account page, produces a confirmation, and a second click within the window is refused with the friendlier message.

Severity

Low-medium. Nothing is broken, but a customer whose verification mail went astray currently has no route back except registering again with a different address.

A verification email is sent once, at registration. If it is lost, filtered, or arrives after the link has expired — the token lasts 24 hours — the customer has no way to ask for another. There is no resend endpoint and no button. ## What it needs A route that mints a fresh verification token for the signed-in customer and sends the verification email, plus a button on the account page. The email itself is already editable copy after #92, so this is the trigger rather than the message. The existing password-reset flow is the pattern to follow closely, because it has already solved most of the same problems: - **Supersede the outstanding token.** Password reset deletes any existing token before minting a new one, so a link cannot be resurrected later from an older message in the inbox. The same reasoning applies here. - **Refuse when there is nothing to do.** A customer whose address is already verified should get a clear answer rather than a pointless email. ## The part that needs care: this is a mail amplification endpoint Anything that makes the server send email on request is an abuse vector, and this one sends to an address the caller controls rather than one they name — which is better, but not a reason to leave it unbounded. It must be rate limited. `rateLimit.ts` already holds the two existing limiters and the reasoning behind each, including why `passwordResetRequestLimiter` keys on caller *and* email and why reusing it on an endpoint without an email collapses everyone into one bucket. A resend limiter keys naturally on the **customer id**, since the caller is signed in — which is tighter than either existing limiter and sidesteps the IPv6 question #84 dealt with entirely. Worth deciding: how many resends in what window. Something like three per hour is generous for a real person and useless to anyone hammering it. ## Worth deciding - **What the customer sees when the limit is hit.** A clear "we have already sent several — check your spam folder" is more useful than a bare 429, and it is also the honest message, since the mail probably did send. - **Whether the account page shows verification state at all.** Today nothing on the page says whether the address is verified. A button to resend is odd next to no indication that anything needs verifying, so this likely wants a small status line alongside it. - **Whether an unverified account is restricted in any way.** It is not today, which is a reasonable choice for a small storefront — but it means resending is a tidiness feature rather than an unblocking one, and that changes how prominent the button should be. ## Verification Integration tests: a resend mints a new token and invalidates the previous one, an already-verified account is refused, an unauthenticated call is refused, and the limiter engages after the allowance. Note the limiter assertion needs care — its store is process-wide, and #62 and #84 both recorded that exhausting an allowance in a test leaks into every later test keyed the same way. Keying on customer id makes that easier to contain, since each test can use a fresh customer. End to end: the button appears on the account page, produces a confirmation, and a second click within the window is refused with the friendlier message. ## Severity Low-medium. Nothing is broken, but a customer whose verification mail went astray currently has no route back except registering again with a different address.
bermudalamb self-assigned this 2026-08-21 19:01:37 -05:00
bermudalamb added this to the Make the password-reset email editable from Admin project 2026-08-21 19:01:52 -05:00
bermudalamb added reference feature/110-resend-verification 2026-08-22 12:56:35 -05:00
bermudalamb moved this to In Progress in Make the password-reset email editable from Admin on 2026-08-22 12:56:46 -05:00
bermudalamb moved this to Review in Make the password-reset email editable from Admin on 2026-08-22 13:06:57 -05:00
bermudalamb moved this to Ready for Release in Make the password-reset email editable from Admin on 2026-08-23 07:23:00 -05:00
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: bermudalamb/redefined-designs#110