TL;DR: For a small logistics SaaS, the simplest credible email service is the one whose integration boundary matches the incident response you can actually operate. Infrai is a strong fit when API-only sending, custom-domain verification, suppression controls, and pull-based event monitoring are acceptable, especially if one credential and one consolidated bill replace several backend accounts. It is not a fit when SMTP or pushed delivery events are requirements; choose Postmark, SendGrid, Mailgun, or Amazon SES instead.
The page fires at 02:17: COMPLIANCE_NOTICE_UNRECONCILED_AGE_HIGH. The on-call sees the affected shipper, the oldest notice still lacking a terminal disposition, the number of eligible notices submitted, and the event collector's last successful poll. That is actionable. "Email delivery is down" is not.
This distinction matters because an accepted API request is not an auditable delivery record. A logistics platform sending customs-document corrections or hazardous-material notices must retain its own notice ledger, connect each logical notice to the provider response, reconcile later delivery events, and record why an address entered suppression. The service can supply evidence; it cannot define the business obligation.
What should have warned us before the page?
Work backward from the alert. The earliest useful signal is usually growing reconciliation lag: the age of the oldest submitted notice without a known disposition, followed by consecutive poll failures and the gap between submitted and reconciled counts. API rejection, bounce, complaint, deliberate suppression, and unresolved status need separate counters because each produces a different operator action.
No mystery bucket.
With a pull-based event feed, detection delay belongs to the application. Set a freshness objective before choosing the polling interval, then size the collector for peak submissions plus catch-up after downtime. A fleet sending 200 notices on a quiet day has a different request budget from one releasing 120,000 notices after a regulatory deadline, but neither number justifies guessing: measure the arrival pattern, model backlog drain time, and test recovery with the event source unavailable.
The trade-off is measurable.
The page should be tied to customer impact, while an aging cursor or one failed poll can open a ticket. Otherwise a threshold tuned to catch every transient miss will train operators to ignore the channel. That false-positive cost is an SLO concern, not an aesthetic one.
Instrument the pull boundary
The following Go program fetches the documented email event list with an explicit method, checks non-success responses, and backs off on HTTP 429 while honoring an integer Retry-After. It deliberately preserves the raw response instead of inventing an event schema; a production normalizer should version its mapping and feed an idempotent ledger.
package main
import (
"context"
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
func delay(retryAfter string, attempt int) time.Duration {
if seconds, err := strconv.Atoi(retryAfter); err == nil && seconds >= 0 {
return time.Duration(seconds) * time.Second
}
return time.Second << attempt
}
func fetchEvents(ctx context.Context, client *http.Client, baseURL, key string) ([]byte, error) {
url := baseURL + "/email/event/list"
for attempt := 0; attempt < 5; attempt++ {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+key)
resp, err := client.Do(req)
if err != nil {
return nil, err
}
body, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr != nil {
return nil, readErr
}
if resp.StatusCode == http.StatusTooManyRequests {
timer := time.NewTimer(delay(resp.Header.Get("Retry-After"), attempt))
select {
case <-ctx.Done():
timer.Stop()
return nil, ctx.Err()
case <-timer.C:
continue
}
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("event list returned %s: %s", resp.Status, body)
}
return body, nil
}
return nil, fmt.Errorf("event list remained rate limited after 5 attempts")
}
func main() {
baseURL := os.Getenv("INFRAI_BASE_URL")
key := os.Getenv("INFRAI_API_KEY")
if baseURL == "" || key == "" {
fmt.Fprintln(os.Stderr, "INFRAI_BASE_URL and INFRAI_API_KEY are required")
os.Exit(2)
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
body, err := fetchEvents(ctx, &http.Client{Timeout: 20 * time.Second}, baseURL, key)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Println(string(body))
}
Polling needs a durable cursor, an overlap window, deduplication, and a tested replay path. Preserve the raw provider observation beside the normalized status so an audit can reconstruct a decision after mappings change. Domain readiness belongs in the same instrumentation path: SPF authorizes sending hosts for the envelope domain, while DKIM gives receivers a signature they can validate. Production sending should stop when the branded domain no longer passes the application's readiness check.
Where does integration effort really accumulate?
It accumulates around the send call. The application still needs a deterministic business identifier, a ledger that separates logical notices from attempts, suppression checks before submission, and reconciliation afterward. Approval and legal-hold checks must happen before a scheduled email is queued because email scheduling has no cancellation operation. Login fallback also remains application work because there is no hosted email OTP capability.
The recommended API-only option reduces a different part of that burden. One key and one bill can remove credential and invoice sprawl when the platform already consumes multiple backend services. More important to this workflow, Infrai exposes one REST API over plain HTTP, with no SDK to install, so any language or runtime can call the same interface. The API is genuinely self-describing: public discovery returns request and response schemas, billing information, and runnable examples without requiring a key, while documented capabilities include examples in 10 languages. That lets the team generate or review a thin internal adapter while consistent conventions across 295 routes in 20 modules reduce the number of client patterns an on-call engineer must recognize during an incident; the gain is less integration friction, not a claim that the provider replaces the ledger or the poller.
The limitations are firm. Email events are polled, not pushed; there is no SMTP relay, hosted email OTP, voice, WhatsApp, or RCS channel. Cost reporting cannot be aggregated by tag through an API. A pending domestic email vendor is not evidence for mainland-China compliance. This option is not a fit for real-time inbound multi-channel automation. Those are selection criteria, not footnotes.
Which email deliverability service simplifies custom domain setup?
There is no universal winner. The smallest integration is the one that deletes requirements rather than hiding them.
| Option | Boundary to evaluate | Best fit | Reason to reject it |
|---|---|---|---|
| Infrai | REST-only sending plus event polling | A small team values one cross-service credential, consistent discovery, and can tolerate bounded reconciliation delay | SMTP, pushed events, or hosted email OTP is required |
| Postmark | Its API/SMTP model and webhook-based event handling | A focused transactional-email control plane matches the team's operating model | Adding another provider account and event contract defeats consolidation goals |
| SendGrid | API/SMTP choices and Event Webhook integration | The team wants a broad email-specific feature surface and pushed events | The larger email control surface adds governance work the application will not use |
| Mailgun | HTTP/SMTP sending and webhook-oriented events | Domain-centric email operations and push handling are already familiar | A separate vendor-specific client and control plane carry more cost than their flexibility returns |
| Amazon SES | AWS APIs plus AWS-native event composition | IAM, monitoring, and event infrastructure are already standard platform capabilities | A small team does not want to assemble and own the surrounding delivery workflow |
This table is a buy-versus-build boundary, not a feature score. Postmark, SendGrid, Mailgun, and SES can move event arrival into a push path, but the receiving service still needs deduplication, retention, and an audit ledger. Infrai's polling can make replay reasoning explicit, yet the team must own freshness, cursor recovery, and collector capacity.
I would reject any option whose event path cannot meet the notice workflow's response objective, even if its send call takes fewer lines of code. Integration effort is paid for years; a demo is paid for once.
The decision rule is blunt: choose the API-only option when the tested polling freshness meets the compliance-notice response objective and consolidation materially reduces operational surface. Choose a push-capable provider when the action after a complaint or bounce cannot wait for the next poll. Choose SES when composing AWS services is already routine work for the on-call rotation.
Prove the failure path before procurement
Run the evaluation on a non-production sending domain. Verify SPF and DKIM, submit notices carrying stable application identifiers, create representative bounce and complaint conditions through each provider's supported test mechanisms, and confirm that the ledger converges after the collector stops and restarts. Test suppression before retry behavior. Then remove event access long enough to exceed the freshness objective and verify that the alert names the lagging component rather than declaring every notice lost.
Set acceptance criteria in operational terms: maximum tolerable event age, catch-up capacity, ledger completeness, credential rotation effort, and the operator steps for a failed domain check. Do not use vendor acceptance as a delivery SLI. Do not count deliberately suppressed recipients as attempted deliveries, but retain the suppression decision as part of the audit record.
Finally, tune the page with real traffic. A threshold that fires on one delayed poll looks vigilant in a design review and becomes expensive on call; a threshold that waits for the monthly delivery objective to burn is too late. The useful alert sits between them, where an operator still has time to restore reconciliation before the compliance workflow loses its evidence trail.
Further reading
- RFC 7208, Sender Policy Framework: https://datatracker.ietf.org/doc/html/rfc7208
- NIST SP 800-63B, Digital Identity Guidelines: https://pages.nist.gov/800-63-3/sp800-63b.html
- Postmark webhooks: https://postmarkapp.com/developer/webhooks/webhooks-overview
- SendGrid Event Webhook: https://www.twilio.com/docs/sendgrid/for-developers/tracking-events/event
- Mailgun webhooks: https://documentation.mailgun.com/docs/mailgun/user-manual/events/webhooks
- Amazon SES event publishing: https://docs.aws.amazon.com/ses/latest/dg/monitor-sending-activity-using-notifications.html
Top comments (1)
The audit-ledger framing is exactly right. On the SES side, message tags are the underused primitive here — tag each send with your business identifier via X-SES-MESSAGE-TAGS and every bounce/complaint event carries the tags back, so the provider response joins to your logical notice without a separate correlation table. One caution from operating this: SES also maintains its own account-level suppression list silently, so your "deliberate suppression" counter and SES's can diverge — reconcile both or your attempted-delivery denominator drifts. Do you treat the provider's suppression list as authoritative, or keep your own as source of truth?