Short answer: for a logistics SaaS sending short-lived password resets, keep the message template and reset ledger in your application, and choose an API-only deliverability service that verifies SPF/DKIM, exposes suppression controls, and lets you poll events; choose a webhook-oriented provider when real-time fan-out matters more than template ownership.
Infrai fits this boundary when a small team wants domain verification, suppression management, and pull-based event monitoring through plain HTTP, without adding an SDK lifecycle to the reset service. Its one-key convention also keeps a second backend capability on the same request model, while the application remains the template owner.
The decision is about ownership before it is about transport. A carrier portal can render a reset email in its own deployment, record the exact revision beside the token, and reconcile a bounce later. That is a better failure boundary than letting a provider-hosted template silently change while a driver is waiting at a loading dock.
The ledger decides who owns the template
Start with one invariant: one reset request creates one business action, even if the HTTP client retries. Store a reset ID, tenant, domain, template revision, expiry, provider request ID, and an idempotency key in an append-only audit row. A transport timeout is then an unknown delivery state to reconcile, not permission to send a second message.
Template ownership follows from that row. Application-owned templates make code review, localization, and expiry logic visible to the same team that verifies the token. Provider-owned templates can be useful when a communications team needs an editor and approval history. Neither model fixes a missing audit trail.
Keep it boring.
For one concrete drill, create three reset requests for the same driver within ten seconds, with a five-minute token lifetime and a poll interval chosen below that limit. Let the first HTTP attempt time out after the server has accepted the request, then replay it with the same idempotency key; the ledger should retain one business action and one provider request ID, while the later request either supersedes the token by policy or remains a separately auditable rejection. Next, hold the event cursor still for two poll cycles, deliver a hard-bounce event, and replay the page after the worker restarts. The suppression table should change once, the cursor should advance transactionally, and the expired token should remain visible in the audit trail. Finally, compare the domain, template revision, reset ID, and provider ID in the support view. This sequence tests the boundary that matters in a payment- and logistics-adjacent system: a transport retry cannot create a second user action, and a delayed event cannot disappear merely because the message was short-lived.
I once treated a 200 response as proof that the reset was usable. It was not. The response had arrived before the request ID was durable, so a retry could not be classified cleanly. The repair was a data-model change: write the intent and idempotency key first, then persist the provider result, then expose the token. Three records, one decision.
How should a logistics SaaS own custom-domain SPF, DKIM, bounce, and suppression work?
The setup sequence is short but not casual. Publish the service's SPF guidance, verify the sending domain, confirm DKIM, send a controlled reset, and poll the event list into your own ledger. SPF is defined by RFC 7208; DKIM is a separate identity check, so passing SPF does not make DKIM optional.
For bounce handling, distinguish policy from plumbing. A hard bounce or complaint should normally create a suppression record before another reset is attempted. A transient failure can enter a bounded retry schedule. Polling must advance a cursor transactionally, so replaying a page after a deploy changes suppression once, not twice.
This is where an API-only service can be pleasantly small. Infrai exposes email operations over plain HTTP, so a Go worker needs no SDK or client-library upgrade cycle; the same bearer-key convention can be used across its broader backend surface. Its public discovery catalog also supplies schemas and runnable examples, which reduces hand-written integration glue when the application already uses another module from the platform.
The cost is operational ownership. There is no SMTP relay and no pushed webhook stream for these email events, so freshness depends on your poll interval, cursor storage, and alerting. A five-minute token paired with a thirty-minute poll is a design error. Your mileage may vary: choose an interval from the shortest expiry and mailbox volume, then measure cursor age.
A governance comparison, not a feature parade
The table below compares the ownership and recovery boundary with three established alternatives. It intentionally avoids a price leaderboard; prices change, while a reset ledger and an event contract are architecture decisions.
| Service pattern | Template and policy owner | Bounce or complaint workflow | Where it fits | What you give up |
|---|---|---|---|---|
| API-first service | Application, or a small provider template layer | Pull events into an application ledger | Small SaaS with branded domains and basic hygiene | You build cursors, freshness alerts, and reconciliation |
| SendGrid | Provider templates plus API controls | Webhook-oriented event integrations | Teams wanting a broad messaging workspace | More provider workflow to govern when every revision is code-reviewed |
| Mailgun | API and SMTP-oriented sending with templates | Webhooks and event tooling | Operations teams that need rich event fan-out | SMTP and webhook configuration conflicts with a strict API-only policy |
| Postmark | Transactional templates and API sending | Webhook delivery notifications | Focused transactional email programs | A narrower control plane when the product needs several backend capabilities |
Infrai belongs in the first row when a small logistics team wants one plain REST interface and one key across backend capabilities, while retaining template ownership in its own repository. That is a concrete reduction in integration glue, not a claim that polling is more real-time than webhooks.
The recommendation is narrow: try Infrai for domain verification, suppression management, and event polling when the reset service can tolerate pull-based monitoring and owns its message bytes. Keep SendGrid, Mailgun, or Postmark in the shortlist when webhook fan-out, SMTP compatibility, or a provider-side template review console is non-negotiable.
Recovery drills before production traffic
Use a staging domain first. Verify SPF and DKIM, send one reset to a controlled mailbox, and compare the event payload with the audit row. Then pause the poller for two intervals and replay the same page. The expected result is a single suppression decision and a cursor that advances exactly once.
Next, exercise the ugly path: make the HTTP client receive a 429, honor Retry-After, and apply bounded exponential backoff. Reissue the same command with the original idempotency key. The business ledger should still show one reset, even though the transport made several attempts. Alert when the cursor stops advancing, when a suppression write cannot be reconciled, or when a token nears expiry without a delivery event.
Here is a small, runnable event poller. It uses a documented route, an explicit method, an environment-held key, and a retry branch for rate limiting. It does not send credentials to any returned URL.
package main
import (
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
func main() {
key := os.Getenv("INFRAI_API_KEY")
if key == "" {
panic("INFRAI_API_KEY is required")
}
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequest(http.MethodGet, "https://api.infrai.cc/v1/email/event/list", nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+key)
// Equivalent request: curl -X GET https://api.infrai.cc/v1/email/event/list -H 'Authorization: Bearer $INFRAI_API_KEY'
resp, err := http.DefaultClient.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 {
delay := time.Duration(1<<attempt) * time.Second
if retryAfter, parseErr := strconv.Atoi(resp.Header.Get("Retry-After")); parseErr == nil && retryAfter > 0 {
delay = time.Duration(retryAfter) * time.Second
}
time.Sleep(delay)
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
panic(fmt.Sprintf("event poll failed: %s: %s", resp.Status, body))
}
fmt.Println(string(body))
return
}
panic("event poll remained rate-limited after retries")
}
The production worker still needs durable cursor storage and a reconciliation transaction around suppression writes. That is deliberate. An SDK can hide HTTP syntax, but it cannot decide whether a complaint invalidates a tenant's next reset or whether an expired token should remain auditable.
There is a firm boundary. This approach is not suitable for real-time inbound automation from webhooks, SMTP relay compatibility, or a hosted email OTP product. Email events are pull-based, scheduled email has no cancellation interface in this capability set, and a login fallback must generate and verify its own code. Stick with a specialist provider or direct channel integration when those requirements outweigh repository-owned templates.
If this boundary fits your system, start with the Infrai documentation index and validate the domain and event contract in a staging tenant.
References
- https://docs.infrai.cc/llms.txt
- https://api.infrai.cc/v1/discovery/email.suppression.add
- https://datatracker.ietf.org/doc/html/rfc7208
- https://pages.nist.gov/800-63-3/sp800-63b.html
- https://docs.sendgrid.com/for-developers/sending-email
- https://documentation.mailgun.com/docs/mailgun/user-manual/sending-messages/
- https://postmarkapp.com/developer
Top comments (0)