A failed confirmation resend is invisible to us and leaves the user locked out #61

Open
opened 2026-11-03 18:52:32 +00:00 by skippy-bot · 0 comments

Problem

The resend path starts a task, throws the result away, and returns :ok whatever happened. When the mailer rejects the message (a rotated key returns 401, or 429, or a 5xx) the page still promises instructions, and because login is refused until the address is confirmed that user cannot use the site at all. The resend has already replaced the outstanding token, so the previously delivered link stops working too. Registration and password reset both log this same failure, so only this path is blind.

Evidence

At 2f150f66:

  • lib/aprsme/accounts.ex:518 is _task = Task.start(fn -> UserNotifier.deliver_confirmation_instructions(user, url) end), and :519 returns :ok unconditionally.
  • lib/aprsme/accounts.ex:517 mints the URL first, which deletes the previous token.
  • lib/aprsme/accounts.ex:571 logs confirmation mail delivery failed user_id=... on the registration path and :682 does the same for password reset.
  • lib/aprsme_web/live/user_confirmation_instructions_live.ex:102 still renders the promise that instructions are on the way.

Expected

The failure is logged keyed on the user id the way the sibling paths do, and the previous token is only replaced once the new mail was accepted, or the failure is surfaced.

How to verify

Configure the mailer to return {:error, :rejected}, submit the resend form for a known unconfirmed address, and assert with ExUnit.CaptureLog that an error naming the user id is emitted. Today no line appears.

Acceptance criteria

  • A rejected resend logs the failure with the user id.
  • The previously delivered link is not invalidated by a resend that was never delivered.
  • A test covers the rejected adapter path.

Effort: S
Lens: product

## Problem The resend path starts a task, throws the result away, and returns `:ok` whatever happened. When the mailer rejects the message (a rotated key returns 401, or 429, or a 5xx) the page still promises instructions, and because login is refused until the address is confirmed that user cannot use the site at all. The resend has already replaced the outstanding token, so the previously delivered link stops working too. Registration and password reset both log this same failure, so only this path is blind. ## Evidence At `2f150f66`: - `lib/aprsme/accounts.ex:518` is `_task = Task.start(fn -> UserNotifier.deliver_confirmation_instructions(user, url) end)`, and `:519` returns `:ok` unconditionally. - `lib/aprsme/accounts.ex:517` mints the URL first, which deletes the previous token. - `lib/aprsme/accounts.ex:571` logs `confirmation mail delivery failed user_id=...` on the registration path and `:682` does the same for password reset. - `lib/aprsme_web/live/user_confirmation_instructions_live.ex:102` still renders the promise that instructions are on the way. ## Expected The failure is logged keyed on the user id the way the sibling paths do, and the previous token is only replaced once the new mail was accepted, or the failure is surfaced. ## How to verify Configure the mailer to return `{:error, :rejected}`, submit the resend form for a known unconfirmed address, and assert with `ExUnit.CaptureLog` that an error naming the user id is emitted. Today no line appears. ## Acceptance criteria - [ ] A rejected resend logs the failure with the user id. - [ ] The previously delivered link is not invalidated by a resend that was never delivered. - [ ] A test covers the rejected adapter path. **Effort:** S **Lens:** product
Sign in to join this conversation.
No description provided.