Raised in a comment on #40.
IAccountService.RegisterPending commits the inactive account and its activation token, then publishes AccountRegistrationRequestedEvent and answers 202. Delivery of the verification is best-effort by design: NotificationService schedules it on the job system, retries MaxAttempts times and logs Gave up delivering if every attempt fails.
That combination has no recovery path. When delivery fails — the SMTP host is down, the channel is misconfigured, the process restarts between the account commit and the send, the mail is silently dropped by the recipient side — the outcome is:
- an account that exists but cannot log in, because it is inactive;
- its username permanently taken, so the same person cannot simply register again;
- an activation token that is never shown to anyone.
The account is unreachable through any existing endpoint. Only an operator with database access can resolve it.
Proposed fix
A rate-limited, idempotent resend endpoint — POST /api/v1/register/resend taking the email address:
- Answers the same way whether or not a pending account exists, so it cannot be used to enumerate registered addresses.
- Re-sends the notification for the existing account rather than creating anything, so calling it twice is harmless.
- Reuses the existing activation token, or mints a fresh one and invalidates the previous, but does not touch
IsActive.
- Uses the same per-IP fixed-window limiter as
POST /api/v1/register, since it triggers outbound mail.
- Does nothing for an account that is already active.
A persisted outbox was considered and rejected in the notifications spec, and this issue does not reopen that: an outbox makes delivery more reliable, while the real problem here is that there is no way to ask again. The resend path fixes the stuck account at a fraction of the cost and is useful even with a perfectly reliable transport, because mail is lost outside our process too.
Acceptance
- A pending account whose verification was never delivered can be recovered without operator intervention.
- The endpoint is anonymous, rate-limited, idempotent, and does not reveal whether an address is registered.
- Tests cover: resend for a pending account, resend for an unknown address, resend for an already-active account, and the rate limit.
- The registration guide documents the endpoint and says plainly that a lost verification is recoverable this way.
Raised in a comment on #40.
IAccountService.RegisterPendingcommits the inactive account and its activation token, then publishesAccountRegistrationRequestedEventand answers202. Delivery of the verification is best-effort by design:NotificationServiceschedules it on the job system, retriesMaxAttemptstimes and logsGave up deliveringif every attempt fails.That combination has no recovery path. When delivery fails — the SMTP host is down, the channel is misconfigured, the process restarts between the account commit and the send, the mail is silently dropped by the recipient side — the outcome is:
The account is unreachable through any existing endpoint. Only an operator with database access can resolve it.
Proposed fix
A rate-limited, idempotent resend endpoint —
POST /api/v1/register/resendtaking the email address:IsActive.POST /api/v1/register, since it triggers outbound mail.A persisted outbox was considered and rejected in the notifications spec, and this issue does not reopen that: an outbox makes delivery more reliable, while the real problem here is that there is no way to ask again. The resend path fixes the stuck account at a fraction of the cost and is useful even with a perfectly reliable transport, because mail is lost outside our process too.
Acceptance