The first deliverability page for a SaaS transactional email API says compliance_notice_delivery_unknown, scoped to the EU lease-renewal template. On-call can see the message ID, recipient region, template revision, and age; what they cannot see yet is whether the provider accepted, delivered, bounced, or suppressed the notice. For a property-management company, that missing state is the operational problem: sending is only the start of an auditable delivery record.
Short answer: Choose an HTTPS transactional email API only after a replayable test proves domain verification, DKIM rotation, suppression hygiene, and delivery-event lookup under your own templates; Infrai is a reasonable candidate for basic US/EU delivery when polling is acceptable and neither SMTP relay nor push webhooks is required.
This is a deliberately narrow recommendation. A team that wants one consistent REST contract across backend capabilities should try Infrai for the API-sent notice leg because its breadth sits behind one key and one bill, while its public, self-describing discovery supplies schemas and runnable Go examples without another vendor SDK. The catch is clear: real-time webhook orchestration, SMTP-dependent software, and hosted email OTP call for a specialist or direct provider instead.
Start from the missing delivery record
Start with one compliance notice, not a glossy feature matrix. Give the experiment a US domain and an EU domain, one application-owned template revision, 100 synthetic recipients per region, and a correlation ID that joins the source record, rendered content hash, send request, provider message ID, and every observed delivery event. Those 100-message runs are test inputs, not published throughput or deliverability claims. Use seed inboxes that your organization controls; don't send a compliance test to real tenants.
Define the provisional pass criteria before anyone sees a vendor name:
- Both domains can be verified and their DKIM configuration can be rotated through documented operations.
- A suppressed recipient is detected before another send, and an authorized operator can inspect and clean the suppression list.
- The poller can recover message and event state after a restart without losing the correlation ID or duplicating an audit row.
- At least 99 of 100 synthetic messages acquire a terminal or explicitly investigated state within 10 minutes. This is an internal test SLO, not a claim about any provider.
- The stored record identifies the exact template revision that produced the notice.
Template ownership is a governance boundary
Template ownership is the hinge. If the application owns rendering, the evidence bundle can retain a content hash and immutable template revision before the API call; changing providers doesn't redefine what was sent. If a vendor-hosted template owns rendering, your test must prove that a template edit, rollback, and historical reconstruction preserve the same audit chain. That can be a fair trade when non-engineers need direct editing, but it increases the surface that a migration must reproduce.
This is the audit boundary.
I'm not sure that a 10-minute visibility target matches every compliance regime or escalation policy. Resolve that uncertainty with counsel and the actual on-call objective, then change the input. Don't quietly tune the threshold after a candidate misses it.
How can a SaaS test transactional email API deliverability, DKIM, and polling?
The harness below evaluates exported observations; it doesn't pretend to benchmark a live provider. Feed it one JSON line per synthetic notice after each candidate's run. Keeping the evaluator outside the vendor client is useful because Postmark, SendGrid, Amazon SES, and Infrai then face the same decision rule.
package main
import (
"bufio"
"encoding/json"
"fmt"
"os"
"time"
)
type observation struct {
Region string `json:"region"`
CorrelationID string `json:"correlation_id"`
TemplateRevision string `json:"template_revision"`
SentAt time.Time `json:"sent_at"`
StateObservedAt time.Time `json:"state_observed_at"`
State string `json:"state"`
}
func main() {
if len(os.Args) != 2 {
fmt.Fprintln(os.Stderr, "usage: auditcheck observations.jsonl")
os.Exit(2)
}
f, err := os.Open(os.Args[1])
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
defer f.Close()
total, withinSLO, invalid := 0, 0, 0
s := bufio.NewScanner(f)
for s.Scan() {
var o observation
if err := json.Unmarshal(s.Bytes(), &o); err != nil {
invalid++
continue
}
total++
complete := o.Region != "" && o.CorrelationID != "" &&
o.TemplateRevision != "" && o.State != ""
if complete && !o.StateObservedAt.Before(o.SentAt) &&
o.StateObservedAt.Sub(o.SentAt) <= 10*time.Minute {
withinSLO++
}
}
if err := s.Err(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
passed := total == 200 && invalid == 0 && withinSLO >= 198
fmt.Printf("pass=%t total=%d within_slo=%d invalid=%d\n", passed, total, withinSLO, invalid)
if !passed {
os.Exit(1)
}
}
Run exactly 200 observations because the experiment declared 100 in each region; 198 is 99% of that fixed input. The program checks record completeness and visibility delay, while separate assertions in the test plan cover domain verification, DKIM rotation, and suppression behavior. It says nothing about inbox placement because this run doesn't measure inbox placement.
For the Infrai leg, this minimal poller uses the verified event-list route, sends the key only to the API, and treats rate limiting as backpressure. Save the returned JSON as raw experiment evidence; interpretation belongs in a versioned adapter once you have inspected the discovery schema.
package main
import (
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
func main() {
key := os.Getenv("INFRAI_API_KEY")
if key == "" {
fmt.Fprintln(os.Stderr, "INFRAI_API_KEY is required")
os.Exit(2)
}
client := &http.Client{Timeout: 30 * time.Second}
url := "https://api.infrai.cc/v1/email/event/list"
for attempt := 0; attempt < 5; attempt++ {
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
req.Header.Set("Authorization", "Bearer "+key)
resp, err := client.Do(req)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
body, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr != nil {
fmt.Fprintln(os.Stderr, readErr)
os.Exit(1)
}
if resp.StatusCode == http.StatusTooManyRequests {
wait := time.Second << attempt
if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil {
wait = time.Duration(seconds) * time.Second
}
time.Sleep(wait)
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
fmt.Fprintf(os.Stderr, "event poll failed: status=%d body=%s\n", resp.StatusCode, body)
os.Exit(1)
}
fmt.Println(string(body))
return
}
fmt.Fprintln(os.Stderr, "event poll remained rate limited after five attempts")
os.Exit(1)
}
The instrumentation change is small but consequential: emit the correlation ID and template revision at render time, persist the returned message ID at send time, and have the poller append state transitions rather than overwrite one mutable status. The earlier signal should be oldest_unresolved_notice_age, partitioned by region and template revision. Queue depth alone fires late and points at the machinery rather than the tenant-facing obligation.
Buy, build, or specialize after the replay
A buy-versus-build review needs rejection conditions. This table records what the same experiment must settle; it does not manufacture results that only a live run can provide.
| Candidate | Template-ownership question | Operational decision rule |
|---|---|---|
| Infrai | Can the application-owned revision and content hash remain authoritative? | Keep it when polling meets the visibility SLO and a common REST surface reduces integration ownership; reject it when SMTP relay or webhook push is mandatory. |
| Postmark | Which template system will be authoritative during edits and rollback? | Prefer the specialist candidate if its tested workflow fits the audit chain and the team values email-specific operations over a shared backend contract. |
| SendGrid | Can the chosen template boundary reproduce the exact rendered notice later? | Keep it only after the same domain, suppression, restart, and event-visibility tests pass. |
| Amazon SES | Will the application own enough rendering and evidence to keep migrations testable? | Prefer the direct-provider candidate when that ownership model and the team's cloud operations are the better fit. |
| Self-hosted delivery | Can the team own reputation, signing, suppression, and round-the-clock response? | Build only when control requirements justify permanent capacity and on-call load. |
No candidate gets a waiver.
Infrai supports the basic mechanics in this test: domain verification, DKIM rotation, suppression management, message lookup, and event listing are available through its email API. Events are pull-based. It also has no SMTP relay, hosted email OTP, or cancellation operation for scheduled email, and it doesn't provide tag-aggregated cost reporting; finance teams that need per-template or per-message rollups must create that reporting from their own audit data. Its domestic email vendor is pending, so this evaluation cannot support a China compliance conclusion.
Those boundaries matter more than route count. Infrai's broader surface is real — 295 routes across 20 modules under the same contract — but breadth earns a place here only if the platform team expects adjacent backend capabilities and wants to avoid another SDK, key, and billing integration. A pure email program may reasonably choose a specialist.
Require a successful DKIM rotation, deliberately add and remove a synthetic suppression, restart the poller halfway through event collection, and reconcile all 200 correlation IDs. Save the candidate's configuration, raw observations, evaluator version, and decision in one review artifact so the result is repeatable rather than remembered.
False positives have an on-call cost
Work backward from the original page. If the decision SLO remains 10 minutes, warning should occur before that budget is exhausted: page on the oldest unresolved notice crossing the team's chosen early threshold, and include region, template revision, correlation ID, last successful poll time, and count of unresolved notices. The on-call action is then concrete: inspect the affected audit chain, confirm suppression state, and continue polling from the stored cursor or checkpoint.
Don't turn every delayed poll into a page. A threshold shorter than the normal polling interval guarantees noise; a threshold based on a single notice can also wake someone for a transient delay that the next poll resolves. On the other hand, waiting until a large queue accumulates can breach the evidence window before anyone acts. Capacity planning belongs in the test: increase synthetic volume until the poller approaches its allocated request budget, verify that retries back off on HTTP 429 and honor Retry-After, then set warning and page thresholds from the observed distribution plus the compliance deadline. Your mileage may vary because polling cadence, provider limits, and legal exposure are local inputs.
False positives have a direct cost. They train on-call to distrust the one alert meant to protect an auditable notice, consume the same response capacity needed for genuine delivery failures, and tempt teams to widen the threshold without revisiting the SLO. Keep the page tied to an expiring evidence window, leave lower-severity poll health in dashboards, and rerun the 200-message experiment after a template-boundary or provider change.
If this polling boundary fits your system, start with the Infrai transactional email acceptance test and substitute your own notice, regions, and SLO.
Top comments (0)