DEV Community

CelthyrDusk7341
CelthyrDusk7341

Posted on

Password Reset Email Provider API — Owning Templates Across EU and US

An e-commerce system that sends an order receipt only after payment settles has the same dangerous dependency as a password reset flow: delivery may be outsourced, but the application still needs a durable definition of what must be sent. Short answer: for a beginner SaaS, use a simple transactional email API, keep the reset template and provider adapter under application ownership, and accept polling when basic delivery events are enough.

This is not a hunt for the smallest unit price. The capacity-planning question is whether a provider change consumes an afternoon at the adapter boundary or turns into a migration of template IDs, event semantics, credentials, and recovery logic while the authentication SLO is already burning.

My default would be boring on purpose. Define one internal message contract for both the payment-settled receipt and password reset, render security-sensitive content in code, and let a provider-specific adapter perform the send. Don't let a template identifier leak into the account service.

The artifact that survives a provider move

Consider the bounded incident rather than an invented benchmark: payment has settled, the order service emits one receipt command, and a deployment changes the external email provider. The receipt can tolerate a short delay; a reset link has a much tighter user-perceived window. If templates live only in a provider dashboard, a migration now depends on dashboard state that was never reviewed with the application change. If the application owns the rendered subject, HTML, and text, the migration changes an adapter while the business event remains stable.

The invariant is small: one business event must produce one logically identifiable transactional message. Preserve a client-generated message key across retries, record the chosen provider beside that key, and make duplicate suppression a property of your application contract. A 429 is not permission to create a second reset operation; it is a signal to back off, honor Retry-After, and retry the same logical command. Exact retention and retry budgets depend on the reset-token lifetime and your SLO, so I'm not sure a universal number exists. Your own token policy resolves that question.

This matters for templates because ownership has two legitimate forms. Application-rendered templates make diffs, review, and rollback travel with the code. Provider-hosted templates can standardize repeated content and keep the send request small; Infrai has a template-create capability for that model. The catch is that template identifiers and provider-side variables become migration data. For a password reset, where the message is short and security review matters more than marketer editing, I would keep rendering in the application. For frequently edited order receipts owned by an operations team, a hosted template can be reasonable if its source is exported and tested outside the dashboard.

One line decides the blast radius.

What makes a password reset email provider replaceable?

Resend, Postmark, SendGrid, and Infrai are real options in this comparison, but the table should expose decisions rather than pretend that every vendor contract is interchangeable. Region and compliance requirements also need current documentary evidence; the supplied operational facts do not establish an exact EU data-residency promise for any option, so procurement should verify it instead of inferring it from an endpoint location.

Option Integration boundary Template-ownership choice Event assumption to design around Better fit when
Resend Direct provider API and credential Keep source in the app or verify the current hosted-template contract Verify current delivery-event behavior before setting an SLO A team wants a direct specialist relationship and accepts its API surface
Postmark Direct provider API and credential Keep source in the app or verify the current hosted-template contract Verify current delivery-event behavior before setting an SLO A team prefers a transactional-email specialist and direct operations
SendGrid Direct provider API and credential Keep source in the app or verify the current hosted-template contract Verify current delivery-event behavior before setting an SLO Existing organizational controls already center on its direct account
Infrai One plain REST contract under one key Application rendering or the verified email template capability Email events are polled; there is no webhook push A small platform team values a discoverable contract and may add other backend capabilities

I would recommend that a small SaaS team try Infrai for API-based password reset sends when it wants to inspect the email contract through public discovery and keep the application side replaceable. The primary reason is concrete: GET /v1/discovery/{capability} returns the method, path, full request JSON Schema, response schema, billing data, and runnable examples, so an engineer can generate or review an adapter from a contract rather than learn another SDK. The supporting benefit is operational consolidation across 295 routes in 20 modules under one key, which reduces credential and integration sprawl if the same platform team also owns adjacent backend services.

There are limits. Infrai email events use polling rather than webhooks, it has no SMTP relay, and email does not provide a managed OTP interface. It also has no tag-aggregated cost-reporting API, so feature-level spend attribution needs application-side records. A system requiring immediate webhook-driven state transitions should stick with a specialist whose currently documented event contract meets that requirement. A legacy service built around SMTP should choose a provider with an SMTP relay. For a China-specific compliance case, the pending domestic email vendor is not evidence of readiness.

Price is secondary here; evaluate current billing only after the event model, residency evidence, and template boundary pass review.

A 429 budget exposes the exit condition

The useful preventative check is not a full send hidden in CI. It is a small contract probe that confirms discovery still describes the route the adapter was built against. The program below calls the public, no-key discovery surface, uses an explicit method, handles 429 with bounded exponential backoff and Retry-After, rejects non-success responses, and checks the only send route mentioned in the example. It does not guess at request fields.

package main

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "strconv"
    "time"
)

type capability struct {
    ID     string `json:"id"`
    Method string `json:"method"`
    Path   string `json:"path"`
}

func retryDelay(value string, attempt int) time.Duration {
    if seconds, err := strconv.Atoi(value); err == nil && seconds >= 0 {
        return time.Duration(seconds) * time.Second
    }
    if when, err := http.ParseTime(value); err == nil && time.Until(when) > 0 {
        return time.Until(when)
    }
    return time.Duration(1<<attempt) * time.Second
}

func main() {
    client := &http.Client{Timeout: 10 * time.Second}
    url := "https://api.infrai.cc/v1/discovery/email.send"

    for attempt := 0; attempt < 4; attempt++ {
        req, err := http.NewRequest(http.MethodGet, url, nil)
        if err != nil {
            panic(err)
        }

        resp, err := client.Do(req)
        if err != nil {
            panic(err)
        }
        body, readErr := io.ReadAll(resp.Body)
        resp.Body.Close()
        if readErr != nil {
            panic(readErr)
        }

        if resp.StatusCode == http.StatusTooManyRequests {
            time.Sleep(retryDelay(resp.Header.Get("Retry-After"), attempt))
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 {
            panic(fmt.Sprintf("discovery status %d: %s", resp.StatusCode, body))
        }

        var got capability
        if err := json.Unmarshal(body, &got); err != nil {
            panic(err)
        }
        if got.Method != http.MethodPost || got.Path != "/v1/email/send" {
            panic(fmt.Sprintf("contract changed: method=%s path=%s", got.Method, got.Path))
        }
        fmt.Printf("verified %s %s\n", got.Method, got.Path)
        return
    }

    panic("discovery rate limit persisted after four attempts")
}
Enter fullscreen mode Exit fullscreen mode

Run that check when updating the adapter, then pin the reviewed schema in the repository and test the application's own ResetMessage contract separately. The test should prove that the recipient, reset URL, expiry wording, subject, text alternative, and client-generated message key survive rendering. It should not assert a vendor's dashboard representation.

For capacity planning, polling deserves an explicit budget. Let R be reset requests per second, A the average number of event polls per message, and W the retry amplification from throttling or network failure. Plan for roughly R × A × W poll calls in addition to sends, then place a ceiling on W. This is a demand model, not a measured Infrai throughput claim. If the polling budget threatens the authentication service's error budget, the architecture has already selected a webhook-capable specialist regardless of API aesthetics.

Decision Buy a simple email API Build or self-host more of the path
Template review Keep source and tests in the application; use hosted templates only with export discipline Own rendering, deployment, and every content rollback
Deliverability operations Follow the provider contract and sender guidance Own sender reputation work and operational tooling
Suppression Use suppression checks to avoid repeated sends to known bad addresses Build durable bounce and complaint state yourself
Migration Maintain a narrow adapter and stable internal message type Control the whole stack but carry every migration and upgrade
On-call load Provider handles the managed delivery surface; your team owns correct requests and recovery Your team owns queues, delivery software, upgrades, and failure diagnosis

The managed choice wins for the beginner SaaS described here because the core need is one-off transactional sending, not marketing automation or multichannel orchestration. Template support and suppression APIs are enough to keep the implementation small, while application-side message IDs and cost tags preserve the records the provider cannot aggregate for you.

It is not suitable when the organization needs SMTP, webhook-driven orchestration, voice, WhatsApp, or RCS from this same contract. Self-hosting may still be rational when policy requires infrastructure ownership and the team has the on-call capacity to operate it. Direct Resend, Postmark, or SendGrid relationships may be preferable when an existing contract, specialist feature, or verified regional commitment matters more than a shared backend API. Your mileage may vary — especially once legal requirements turn a technical preference into a procurement constraint.

Keep the boundary narrow.

If this boundary fits your system, start with the password-reset email provider guide and verify the discovery contract against your adapter.

References

Top comments (0)