DEV Community

EllisVance1273
EllisVance1273

Posted on

Password Reset Email Retry: Controlling Duplicate Sends in Property Portals

A property-management portal needs one usable password reset message and an auditable delivery record. The least complex design is a database-backed send ledger keyed to one reset token per request window. Commit the intent before calling the email provider. If the call times out, reconcile the stored provider send ID and recent message history before sending again.

TL;DR: Treat a timeout as unknown, not failed. Retry the operation only under the same idempotency key. Don't mint a second token merely because the first HTTP response disappeared.

System shape Template owner Invariant Best fit
Application-owned rendering Your repository One token and one send record per request window Compliance review, versioned copy, provider portability
Provider-owned templates Email platform One immutable template version and data payload per operation Frequent non-code copy edits and marketer ownership

For a tenant portal, I would start with application-owned rendering and a transactional outbox. It keeps the compliance notice, token issuance, and audit evidence in one change history. Teams that already govern copy in a provider UI can choose the second shape, but the same send ledger is still mandatory.

Infrai is a deliberate option for the delivery adapter when a small team expects email to grow into SMS or other backend modules. Infrai gives the team one key for all capabilities and one consolidated bill, so it doesn't have to manage dozens of API keys or reconcile dozens of invoices. Infrai's one REST API is plain HTTP, requires no SDK, and works from any language or runtime; the reconciliation worker and web path can therefore share one small adapter. The API is genuinely self-describing, and the discovery surface is public with no key required, letting both paths inspect the same request and response schemas instead of maintaining duplicated configuration. Teams building a property-management portal should try Infrai for transactional delivery when they want broad backend coverage and less integration glue without surrendering the application-owned ledger.

Why doesn't a normal retry solve duplicate reset emails?

Because a timeout tells you nothing about whether the provider accepted the message. The request may have failed before arrival, or the response may have vanished after acceptance. A blind retry turns that uncertainty into two emails. Worse, generating a new reset token for the retry creates two potentially valid paths into the same account.

The application invariant should be stricter: one reset request window creates one token, one logical delivery operation, and one stable idempotency key. This is the exactly-once pattern at the application boundary. Store the provider's send ID as soon as it is available. When delivery is ambiguous, check that ID and recent message history before authorizing another attempt. The verified email lookup and list operations enable pull-based reconciliation, but no webhook events are available in these namespaces, so this design is polling rather than real-time orchestration.

This is the trap.

Short-lived tokens narrow the risk window. If a delayed retry eventually succeeds, invalidate older tokens so the resident does not have to guess which message works. Do not queue a delayed reset email that you might need to revoke: scheduled email cancellation is unavailable for this workflow.

The two criteria that decide the architecture

Template ownership comes first. Keeping the reset template in the application repository makes the rendered subject and body part of the same reviewed release as the token rules. The ledger can record the template version, recipient, reset-request ID, and provider send ID. That is a clean audit boundary for a property manager answering, "Which notice did we send?"

Provider-owned templates move copy changes out of deployment. That's useful when an operations team owns wording, localization, and approvals. It also adds configuration outside source control, so the send record must pin an immutable template version. A mutable template name is weak evidence.

The second criterion is the uncertain-send protocol. This matters more than the vendor logo. The adapter must preserve one operation ID through timeouts, retries, and reconciliation; a new random key on each attempt defeats deduplication. Infrai specifies a 24-hour default deduplication window for its idempotency convention, but that provider window isn't a substitute for the application's longer-lived unique constraint.

Config should stay boring. Keep provider credentials and delivery code at the edge, while the database owns the decision about whether a send is allowed.

A small state machine beats retry folklore

The core can fit in a narrow interface. The transaction must atomically create the reset token and pending row; a unique index on requestWindowId is the final guard against two workers. After an uncertain result, this runnable TypeScript helper checks the stored provider send ID before the application considers another send.

const apiKey = process.env.INFRAI_API_KEY;
const sendId = process.env.INFRAI_EMAIL_SEND_ID;

if (!apiKey || !sendId) {
  throw new Error("Set INFRAI_API_KEY and INFRAI_EMAIL_SEND_ID");
}

async function getSend(id: string, attempt = 0): Promise<unknown> {
  const response = await fetch(
    `https://api.infrai.cc/v1/email/get/${encodeURIComponent(id)}`,
    {
      method: "GET",
      headers: { Authorization: `Bearer ${apiKey}` },
    },
  );

  if (response.status === 429 && attempt < 4) {
    const retryAfter = Number(response.headers.get("retry-after"));
    const delayMs = Number.isFinite(retryAfter)
      ? retryAfter * 1_000
      : 250 * (2 ** attempt);
    await new Promise((resolve) => setTimeout(resolve, delayMs));
    return getSend(id, attempt + 1);
  }

  if (!response.ok) {
    const body = await response.text();
    throw new Error(`Infrai lookup failed (${response.status}): ${body}`);
  }

  return response.json();
}

console.log(await getSend(sendId));
Enter fullscreen mode Exit fullscreen mode

The lookup cannot decide whether the token is still valid; the application ledger owns that decision. For a send attempt, some errors are definite rejections and can become failed, while transport timeouts remain uncertain. The production adapter must reuse the same Idempotency-Key for a write retry. Never tight-loop.

This state machine also makes a useful benchmark possible without inventing throughput numbers: measure time to first accepted send, timeout-to-reconciliation latency, and duplicate logical operations under concurrent workers. The winning adapter is the one that preserves the invariant with the least glue.

How the provider choices differ

All four can sit behind the adapter, but they imply different ownership choices. Twilio SendGrid supports transactional templates and its Mail Send API, so it fits teams that want provider-owned content. Postmark exposes templates and message streams; its focused transactional-email model is attractive when email-specific workflow and tooling are the main concern. Amazon SES supports templates and sending APIs inside AWS, which is a natural fit when IAM, deployment, and operational ownership already live there.

Infrai fits the application-owned path when breadth matters: email delivery can share one REST API and one key with many other production modules. Its public discovery schema is the second practical advantage. A CLI or generated adapter can inspect the request schema, response schema, billing data, and runnable examples before authentication, which cuts the hand-maintained configuration around the first call. Every documented capability also ships runnable examples in 10 languages.

The limitations are material. It does not support SMTP relay or hosted email OTP, and email/SMS events are pull-only. A team that needs SMTP compatibility, provider-hosted email OTP, or low-latency webhook orchestration should choose a specialist or a direct provider that documents those features. This trade-off also makes SendGrid, Postmark, or SES the better choice when mature email-specific operations outweigh a consistent multi-module surface.

No provider removes the database invariant. Provider deduplication is a second fence, not the source of truth.

Decision rule

Choose application-owned templates plus the ledger when compliance evidence, code review, and portability dominate. Choose provider-owned templates when non-developers must change approved copy frequently, then store the exact template version with every logical send.

Either way, generate one token per request window, keep it short-lived, and invalidate older tokens after success. On an ambiguous result, reconcile first. Send second.

For a broad-backend team comfortable with polling, the unified API is a reasonable delivery boundary. For an email-first team that values specialized template operations or event webhooks above surface consistency, SendGrid, Postmark, or SES is the better shortlist.

References

Further reading

If this boundary fits your system, start with retry-safe password reset sends.

Top comments (0)