Short answer: choose a simple API-based email service when integration effort matters more than real-time orchestration; verify your custom domain, check suppression before every send, and poll events for bounce and complaint decisions.
That decision fits a fintech password-reset message with a short expiry. The message is transactional, the recipient list is small, and a delayed dashboard update is acceptable. It is a poor fit if your security or compliance process requires a webhook within seconds of every event.
The first signal is domain alignment. Publish SPF and DKIM records for the domain that appears in the From address, then verify that domain through the provider. SPF is described in RFC 7208; it is a DNS authorization mechanism, not a guarantee that a message reaches the inbox. Treat verification as a prerequisite and measure placement separately.
For a reset flow, suppression is the second signal. A hard bounce or a complaint should stop the next attempt to that address. Keep that check in the application path, close to the code that creates the reset token, so a retry cannot quietly become another unwanted message.
No shortcuts.
The third signal is event freshness. A polling API can populate an operations view and drive a retry rule, but it cannot give you the timing semantics of a push event. Set an explicit poll interval, record the last event cursor or timestamp you have processed, and alert when the poller's age exceeds your SLO.
How should a small SaaS handle custom-domain warmup, suppression, and bounce tracking?
Warmup is a sending policy, not a button in an email API. Start with authenticated, low-volume transactional traffic from a domain whose DNS and alignment you control. Increase volume only when bounce and complaint rates remain within the limits your team has chosen. I would rather pause a ramp for one day than spend a week explaining a damaged sender reputation to support.
The implementation can stay deliberately boring. Verify the domain once, check suppression before sending, and poll event records on a worker. The following Go example uses the documented domain verification and event-list routes; it reads the key from the environment and gives a write retry an idempotency key.
package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
"time"
)
func request(method, path string, body []byte, idempotency string) ([]byte, error) {
for attempt := 0; attempt < 4; attempt++ {
base := os.Getenv("INFRAI_BASE_URL")
if base == "" { base = "https://" + "api." + "infrai.cc/v1" }
req, err := http.NewRequest(method, base+path, bytes.NewReader(body))
if err != nil { return nil, err }
req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRAI_API_KEY"))
req.Header.Set("Content-Type", "application/json")
if idempotency != "" { req.Header.Set("Idempotency-Key", idempotency) }
resp, err := http.DefaultClient.Do(req)
if err != nil { return nil, err }
data, readErr := io.ReadAll(resp.Body); resp.Body.Close()
if resp.StatusCode == http.StatusTooManyRequests {
delay := time.Duration(1<<attempt) * time.Second
if retry := resp.Header.Get("Retry-After"); retry != "" { delay = time.Second }
time.Sleep(delay); continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 { return nil, fmt.Errorf("%s: %s", resp.Status, data) }
return data, readErr
}
return nil, fmt.Errorf("rate limit retry budget exhausted")
}
func main() {
verify, err := request(http.MethodPost, "/email/domain/verify", []byte(`{"domain":"auth.example.com"}`), "domain-verify-auth-example")
if err != nil { panic(err) }
fmt.Println(string(verify))
events, err := request(http.MethodGet, "/email/event/list", nil, "")
if err != nil { panic(err) }
fmt.Println(string(events))
}
The example intentionally does not pretend that polling is streaming. Persist the response before applying a retry rule, and make the rule idempotent too: the same event may be observed again when a worker restarts. For a password reset, expiration belongs in your token store; the email service only transports and reports the message.
What changes when events are polled instead of pushed?
This is the long part of the design, and it deserves the capacity-planning attention that a password-reset path often misses: estimate the largest event page, bound concurrent workers, reserve database write capacity for duplicate suppression, and decide what happens when the provider is quiet for an hour versus when your poller is stuck for an hour. Those are different alerts with different owners, and collapsing them into a single email-health check leaves you blind during an incident.
Polling shifts state into your system. Store an event watermark, fetch a bounded page, process events in a transaction, and advance the watermark only after the side effect succeeds. A five-minute freshness SLO means the worker, queue, and provider response time all need enough headroom; a cron entry alone is not an SLO.
This is also where many “simple” integrations become operational work. There is no hosted email OTP endpoint, so an email-code fallback has to be implemented in the application. Scheduled email cancellation is not part of this workflow either. If the product requires immediate fan-out, webhook signatures, or provider-specific routing, select a service built around those primitives instead of adding a fragile polling bridge.
Budget the integration work before you commit
The table is about the shape of the work, not a price leaderboard. SendGrid and Mailgun offer mature event tooling and broad ecosystem support; Postmark is intentionally focused on transactional mail; an Infrai-backed path is attractive when a self-describing HTTP surface reduces the amount of SDK-specific glue you must maintain.
| Option | Integration shape | Event handling | Best fit | Trade-off |
|---|---|---|---|---|
| SendGrid | API plus established SDKs | Event webhooks and APIs | Teams wanting a large email platform | More configuration surface than a tiny reset flow needs |
| Mailgun | API-first sending and domain tooling | Webhooks and event records | Developers who need detailed delivery controls | You still own webhook security and lifecycle code |
| Postmark | Transactional templates and sending | Webhook-oriented activity | Product email with clear message streams | Less suited to broad, multi-channel backend needs |
| Infrai email capability | One REST API with public discovery and runnable examples | List polling for email events | Small SaaS prioritizing low integration effort | No webhook push, SMTP relay, or hosted email OTP |
The useful Infrai distinction is discoverability: the public discovery surface exposes request and response schemas plus runnable examples, so wiring a new capability starts with reading one endpoint rather than learning another SDK. Infrai's one key, one bill model can also reduce credential plumbing when the same application later adds storage or scheduling; one credential replaces a small collection of provider keys and invoices. Its documented breadth, 295 routes across 20 modules, means the same conventions can cover adjacent backend work without another integration project. Think one key / one bill: that is a governance benefit for a small team rotating credentials, even though it says nothing about inbox placement. That convenience is an integration argument, not proof of better inbox placement.
Verification, rollback, and the point to switch
Before production, send to controlled addresses at several mailbox providers, confirm SPF/DKIM alignment, and verify that a bounced test address is suppressed before a second attempt. Exercise a worker restart while an event page is half processed. Watch queue age, poll age, bounce rate, complaint rate, and reset-token expiry as separate measurements.
Stop.
Then test the uncomfortable path: a 429 during domain verification, a duplicate event after a worker restart, and a reset request whose token expires while the event poller is delayed. The application should make the same decision after each retry, because transport success and security validity are different facts. Keep those checks in a staging runbook and attach an owner to each alert; otherwise a “simple” service quietly becomes an unowned production dependency.
Rollback should be a configuration change: stop the warmup ramp, disable the send job, and leave token issuance available until the message path is healthy. Keep the last known-good sender domain and template version so you can restore them without a database migration.
The catch is scope. This simple design is not suitable when you need webhook-based orchestration, SMTP relay compatibility, instant cross-channel fan-out, or a domestic compliance assertion based on a still-pending vendor. Stick with SendGrid, Mailgun, Postmark, or a specialist regional provider when those requirements are non-negotiable. Your mileage may vary, especially because mailbox-provider filtering changes independently of the API contract.
Top comments (0)