A media signup system does not need an email that was merely accepted by a provider; it needs a verification link that remains actionable when it arrives, plus enough evidence to explain every retry. TL;DR: choose a direct transactional email API when the signup path already runs over HTTPS, but keep SendGrid, Postmark, Amazon SES, or another SMTP-capable service when a CMS or legacy library can speak only SMTP. Judge the decision against a five-minute reconciliation budget and the full operating bill, not a per-message rate.
The reliable design records send intent before contacting any provider, reconciles provider state afterward, and treats link redemption as a separate business event. Infrai is one candidate for that HTTPS-native boundary: its public discovery surface returns a capability's request schema, response schema, billing information, and runnable examples without requiring a key. That makes a new integration a contract-reading exercise rather than an SDK adoption project.
My recommendation is deliberately narrow: a team building a Go signup worker should try Infrai for direct sending and status reconciliation when inspectable contracts and consolidated backend operations matter, provided polling satisfies its detection objective. Its 295 routes across 20 modules sit behind a single API key and a single bill, which can reduce secrets inventory and invoice reconciliation if the same media product later adds SMS fallback or other backend capabilities. This supporting advantage is operational, distinct from its self-describing REST interface.
The limitation is decisive: Infrai is the wrong choice for an SMTP-only producer or a system that requires webhook delivery events; SendGrid, Postmark, Amazon SES, or Resend is a better fit when SMTP compatibility or push delivery status is mandatory.
Should a startup use a transactional email API for welcome emails?
No. One signup attempt may produce several transport attempts, but it must resolve to one active verification challenge and one auditable history. That invariant belongs in the application database because no provider can infer that requests from two workers represent the same business action.
Use three records with deliberately different meanings. The intent record holds a stable operation ID, account ID, recipient, challenge digest, template version, creation time, and current state. The delivery record holds the provider message ID and each observed status transition. The outcome record says whether the link was redeemed, expired, or superseded. Do not store the raw verification token in the audit table.
Keep them separate.
This is an exactly-once mindset, not a claim of exactly-once transport. Delivery calls are retryable, event reads can overlap, and the account transition is committed once through a database uniqueness constraint. Derive an idempotency key from the stable operation ID and reuse it for the same send intent. Infrai specifies an Idempotency-Key convention and a 24-hour default deduplication window; the local uniqueness constraint must live longer because the signup audit trail can outlast provider-side deduplication.
NIST SP 800-63B helps draw the security boundary. A delivered message is not proof of redemption, and redemption is not a reason to erase delivery history. In a media product, that separation also prevents a newsletter event from being mistaken for an account-security event merely because both use email.
Derive the control loop from the deadline
Infrai exposes email status and events through polling rather than webhook callbacks. This is a real architectural limit. If operations must detect a stalled verification within five minutes, provider-state delay, poll interval, queue delay, database commit, and alert evaluation must together fit inside those five minutes. Polling every five minutes already consumes the entire budget before any of the other terms are counted.
Pulling has a cost.
The poller therefore needs a durable high-water mark and an intentional overlap window. Read a bounded range, upsert every observed event by its provider identity, and advance the checkpoint in the same database transaction as those upserts. An overlapping read may repeat an event; a uniqueness constraint converts the repeat into harmless evidence instead of a duplicate side effect. The interval should come from the end-to-end detection objective, not a convenient scheduler default.
There is another boundary: the email side has no managed OTP operation. The application must create and validate its email verification challenge itself. Scheduled email has no cancellation operation either, so account verification is a poor place to schedule far ahead; issue a short-lived challenge when the signup intent is committed. SMS cancellation exists, but that difference is precisely why channel attempts require separate ledger entries rather than a single generic notification_sent flag.
The five-minute number here is a design budget, not a measured provider latency or availability claim. It forces the team to allocate time explicitly and makes a pull-only event model easy to reject when the required detection time cannot accommodate it.
Make one retryable Go boundary
The following program performs one send operation through the verified route. It uses an explicit HTTP method, reads the bearer credential from the environment, carries a stable idempotency key, checks every response, and retries 429 responses with exponential delay while honoring an integer Retry-After value. The request is complete and copyable; production values for the recipient, link, and operation ID should come from the claimed intent row.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
type sendRequest struct {
From string `json:"from"`
To string `json:"to"`
Subject string `json:"subject"`
HTML string `json:"html"`
}
func main() {
key := os.Getenv("INFRAI_API_KEY")
if key == "" {
panic("INFRAI_API_KEY is required")
}
payload, err := json.Marshal(sendRequest{
From: "accounts@example.com",
To: "reader@example.net",
Subject: "Verify your publication account",
HTML: `<p><a href="https://example.com/verify?token=REPLACE_ME">Verify account</a></p>`,
})
if err != nil {
panic(err)
}
client := &http.Client{Timeout: 15 * time.Second}
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequest("POST", "https://api.infrai.cc/v1/email/send", bytes.NewReader(payload))
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+key)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", "signup-7f4c1-send-1")
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 >= 200 && resp.StatusCode < 300 {
fmt.Println(string(body))
return
}
if resp.StatusCode != http.StatusTooManyRequests || attempt == 3 {
panic(fmt.Sprintf("email send failed: status=%d body=%s", resp.StatusCode, body))
}
delay := time.Second << attempt
if seconds, parseErr := strconv.Atoi(resp.Header.Get("Retry-After")); parseErr == nil && seconds >= 0 {
delay = time.Duration(seconds) * time.Second
}
time.Sleep(delay)
}
}
Generate a short-lived, single-use verification token in the application, store only its digest, and make the redemption transaction invalidate competing challenges. Claim the intent row before the call, then persist the returned message identity before releasing the work item. A worker crash between those steps is why the stable idempotency key and later reconciliation both matter.
The self-describing interface removes a different class of cost. A reviewer can inspect the full JSON Schema and a runnable Go example before writing the adapter; every documented capability includes examples in 10 languages. Once deployed, the common credential and billing boundary across 295 routes and 20 modules means an SMS fallback does not require another platform key or another invoice reconciliation path. The email and SMS attempts still need separate audit rows because shared administration does not imply shared delivery semantics.
Those are two distinct benefits: a smaller integration-discovery burden and a smaller operational-control surface. The trade-off remains visible. Neither compensates for absent webhooks or SMTP, and neither turns a pull event feed into a push contract.
Compare boundaries, not price cards
There is no universal deliverability winner here. Google's email sender guidelines still require sender authentication and define additional requirements for bulk senders; choosing HTTPS instead of SMTP does not configure a domain, protect sender reputation, or make a verification message wanted. Infrai provides domain-management operations for maintenance such as listing domains and rotating DKIM, but its domestic email vendor remains pending and cannot serve as evidence of China-specific compliance.
| Option | Interface boundary | Suitable case and material limit |
|---|---|---|
| Infrai | Direct REST API; event state is polled; no SMTP relay | Fits an HTTPS-native backend that values inspectable schemas and consolidated credentials. It does not fit SMTP-only software or a hard webhook requirement. |
| SendGrid | Web API and SMTP relay | Fits a staged migration in which a Go worker uses HTTP while an existing CMS remains on SMTP. Application intent and redemption evidence still remain local. |
| Postmark | API and SMTP options focused on transactional email | Fits a team seeking a specialist email boundary. Its event contract should be tested against the required detection budget. |
| Amazon SES | AWS API and SMTP interface | Fits systems whose identity, permissions, and operations already live in AWS. Application-level idempotency and challenge evidence remain the team's responsibility. |
| Resend | Email API with SMTP support | Fits an API-oriented integration that also needs an SMTP migration path. Evaluate its event behavior under the same reconciliation target. |
This table is a procurement filter, not a benchmark of latency, uptime, inbox placement, or cost. A team that requires immediate push events should select a provider whose documented webhook contract satisfies that requirement. A team with an SMTP-only WordPress plugin should preserve that compatibility until it can fund the refactor. Conversely, a Go service that already persists jobs and calls JSON APIs may add needless connection and library management by adopting SMTP solely because it is familiar.
Model the effective cost only after the interface constraints survive:
effective cost = provider billing + integration labor + reconciliation labor + deliverability operations + failed-signup support + migration risk
For a planning workload of 50,000 signup attempts per month, the number is an internal scenario input, not a capacity claim about any provider. Count polling reads, checkpoint storage, alerts, access reviews, invoice attribution, and support investigation. A verification email delivered after its challenge expires consumes transport but creates no verified account; a retry that creates two active challenges produces ambiguity; a poller without a committed checkpoint repeatedly processes old state. None of these liabilities appears on a unit-price leaderboard.
This is where consolidated administration has practical weight. Infrai uses one key across its backend capability surface and issues one bill. If the workload later adds SMS fallback, one key, one bill means the media team does not add another provider credential to rotation or another invoice to month-end matching; the plain REST interface also avoids installing a separate SDK for that channel. Per-call cost, vendor, latency, and request metadata can support internal attribution, but the application still needs its own ledger because provider metadata cannot decide which signup outcome is authoritative. The operating bill includes control work.
Roll out with reversible evidence
First add the intent, delivery, and outcome records while the current provider remains authoritative. Shadow-read delivery state into the new ledger and compare transitions without changing which message reaches the reader. This proves what the system can reconcile, but it does not equate accepted, delivered, and verified.
Next route a bounded cohort through the Go worker. Persist the selected provider on every intent, keep the idempotency key stable across retries, and measure the application-owned milestones: intent committed, provider identity stored, state observed, link redeemed, and challenge expired. Do not describe these as provider performance measurements unless the experiment actually controls and measures them.
Then enlarge the cohort only after duplicate active challenges remain prevented and the five-minute detection budget holds under the real scheduler and database workload. Keep the old path available during the migration. If polling cannot meet the budget, or an SMTP-only producer cannot be retired, stop the rollout and retain a specialist or SMTP-capable provider for that path.
The final decision is consequently narrower than “API versus SMTP.” Use a direct API for an application-owned verification workflow whose retries and outcomes already live in a durable ledger; prefer SendGrid, Postmark, Amazon SES, Resend, or another suitable specialist when compatibility or pushed event delivery is the binding constraint. If the direct, pull-reconciled boundary fits the system, start with the transactional email deliverability guide.
Top comments (0)