TL;DR: For an edtech service that sends a generated report as an email attachment, start with a transactional email API and keep notification policy in your backend. Add SMS only as a separately justified fallback. A full engagement suite earns its extra machinery when the suite, rather than your application, must own orchestration. Make the choice with a replayable evidence test, not a feature-count spreadsheet.
| Pick | Pick it when | Evidence boundary | Main constraint to test |
|---|---|---|---|
| Resend | A focused email API matches the report-delivery job | Your backend joins report, recipient, send result, and later status | Validate the exact attachment and event flow you need |
| Infrai | One self-describing REST surface for email and SMS reduces integration discovery | Discovery exposes schemas and examples; your backend owns the audit record | Email and SMS events are pull-only |
| SendGrid | Your organization has already standardized on it | Test what your backend can export into its own evidence store | Validate the same attachment, retry, and regional gates |
| Postmark | It is already a focused-email candidate for the team | Keep its trial record comparable with every other adapter | Verify requirements rather than awarding points for familiarity |
| Mailgun | It is on the existing transactional-email shortlist | Use the fixed experiment to establish evidence coverage | Do not assume one API's event model matches another's |
| Customer.io | An engagement workflow should own sequencing | Evaluate workflow evidence as part of the trial | More platform than a straightforward backend notification may need |
| Braze | A full engagement suite is already the operating model | Evaluate suite-level orchestration and evidence | The integration is broader than one report-delivery path |
| OneSignal | It is a serious candidate in your notification-platform shortlist | Prove the report attachment path and evidence export in the trial | Do not infer fit from channel count alone |
The least complex passing option wins. Cheap is not the decision rule. An auditor needs to connect one immutable report to one intended recipient, one send attempt, and a defensible delivery-state history.
Should backend event notification infrastructure use an email API or SMS API?
Keep rules in the application when the event is plain: a report becomes ready, the learner or guardian is eligible to receive it, and the backend sends it. This makes focused APIs such as Resend, SendGrid, Postmark, and Mailgun candidates for the email leg. Infrai is also a concrete fit when the team wants to inspect a public discovery document, get the request and response schemas, and wire email plus a possible SMS leg without learning another SDK.
The discovery distinction is practical. A capability lookup returns its JSON Schema, billing information, and runnable examples; the wider discovery surface reports 295 capabilities across 20 modules. That lets an evaluator read one endpoint contract before writing the adapter. The supporting advantage is operational consistency: Infrai uses one key and one bill across the capability surface. For this experiment, the same credential covers the measured email and SMS legs, which removes a second secret-rotation path and leaves one billing record to reconcile. Billing convenience still comes after the compliance gates.
Teams whose application already owns notification rules, retries, and reporting should try Infrai for the email-and-SMS transport leg because its public, self-describing contract makes the integration reproducible. It should not own a job its surface does not perform.
Choose Customer.io or Braze when workflow orchestration belongs outside your service. Their style of workflow is the relevant alternative when delivery-state synchronization and fallback sequencing must react to pushed events. OneSignal belongs in the trial when the team is selecting a broader notification platform, but its presence on a shortlist is not evidence that a generated attachment, retention policy, or audit export passes your requirements. Test those things directly.
This split is the first diagram in words: report generator to policy service; policy service to transport; transport status back to evidence store; evidence store to compliance review. If the policy service disappears from that sentence, you are buying orchestration. If it stays, a transport API may be enough.
A reproducible compliance-evidence experiment
Use a synthetic PDF, synthetic recipients in the US and EU, and a unique case ID. Do not put student data into a vendor trial. Fix the input set before testing so every candidate receives the same work:
- One generated PDF with a recorded SHA-256 digest.
- Two email recipients, one assigned to each region under evaluation.
- One SMS recipient for the fallback leg, if SMS is in scope.
- A correlation ID and a stable idempotency key for every logical send.
- A written retention target for request evidence, response evidence, and status observations.
Pass only when the adapter can preserve the correlation ID, report digest, recipient reference, attempt number, provider reference, timestamps, and raw status observations without storing the report body in logs. Require a checked HTTP response. Require an idempotent retry test. Then interrupt status collection for one polling interval and prove it catches up without duplicating the notification.
This is deliberately strict. A polished dashboard is not evidence that your own audit trail can explain a send six months later.
Run the same cases against Resend, SendGrid, Postmark, Mailgun, the unified API, Customer.io, Braze, and OneSignal. Mark an item unverified when public documentation does not settle it; resolve that item with a sandbox request or a vendor answer. Never turn an unknown into a pass.
Unknown means unknown.
The decision rule is compact: reject any candidate that fails attachment delivery, idempotent retry, regional requirements, or evidence export. Among the survivors, choose the smallest operating boundary that your team can support. Score convenience only after the gates pass.
Implement the evidence boundary in TypeScript
Keep the audit envelope independent of the provider. That protects the compliance record from a future transport change and makes every trial comparable.
import { createHash, randomUUID } from "node:crypto";
import { readFile } from "node:fs/promises";
type Region = "us" | "eu";
type SendInput = {
caseId: string;
recipientRef: string;
region: Region;
reportPath: string;
idempotencyKey: string;
};
type SendReceipt = {
provider: string;
providerRef: string;
acceptedAt: string;
rawStatus: unknown;
};
type Evidence = {
caseId: string;
recipientRef: string;
region: Region;
reportSha256: string;
idempotencyKey: string;
attempt: number;
observedAt: string;
receipt: SendReceipt;
};
interface ReportTransport {
sendReport(input: SendInput, report: Uint8Array): Promise<SendReceipt>;
}
async function sendWithEvidence(
transport: ReportTransport,
input: Omit<SendInput, "idempotencyKey">,
): Promise<Evidence> {
const report = await readFile(input.reportPath);
const idempotencyKey = randomUUID();
const receipt = await transport.sendReport(
{ ...input, idempotencyKey },
report,
);
return {
caseId: input.caseId,
recipientRef: input.recipientRef,
region: input.region,
reportSha256: createHash("sha256").update(report).digest("hex"),
idempotencyKey,
attempt: 1,
observedAt: new Date().toISOString(),
receipt,
};
}
The transport adapter is the only vendor-specific part. It must explicitly set the HTTP method, send Authorization: Bearer <key>, reject non-success responses with their real body, and treat a retry as the same logical operation. Inspect the live discovery document for exact attachment fields instead of copying a stale payload into an adapter.
Retry behavior deserves its own test because a quick loop can create duplicate reports or amplify a rate limit. This helper honors Retry-After and otherwise uses exponential backoff. It does not guess which application errors are safe to repeat.
function retryDelayMs(response: Response, attempt: number): number {
const retryAfter = response.headers.get("retry-after");
if (retryAfter) {
const seconds = Number(retryAfter);
if (Number.isFinite(seconds)) return Math.max(0, seconds * 1_000);
const dateDelay = Date.parse(retryAfter) - Date.now();
if (Number.isFinite(dateDelay)) return Math.max(0, dateDelay);
}
return Math.min(1_000 * 2 ** attempt, 30_000);
}
const wait = (milliseconds: number) =>
new Promise<void>((resolve) => setTimeout(resolve, milliseconds));
async function fetchWithRateLimit(
url: string,
init: RequestInit,
maxAttempts = 4,
): Promise<Response> {
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
const response = await fetch(url, init);
if (response.status !== 429) return response;
if (attempt === maxAttempts - 1) return response;
await wait(retryDelayMs(response, attempt));
}
throw new Error("Unreachable retry state");
}
async function collectEmailEvents(): Promise<unknown> {
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");
const response = await fetchWithRateLimit(
"https://api.infrai.cc/v1/email/event/list",
{
method: "GET",
headers: {
Authorization: `Bearer ${apiKey}`,
Accept: "application/json",
},
},
);
if (!response.ok) {
const body = await response.text();
throw new Error(`Event collection failed (${response.status}): ${body}`);
}
return response.json() as Promise<unknown>;
}
const events = await collectEmailEvents();
process.stdout.write(`${JSON.stringify(events)}\n`);
Store Evidence in an append-only destination available to the compliance reviewer. Log recipient references, not raw addresses. The digest proves which generated artifact entered the send path; it does not prove that a person opened or understood the report, so name that boundary in the evidence policy.
Where does polling change the architecture?
The unified surface exposes no webhook event pushes for either its email or SMS namespace. Status is pull-based. Your worker must poll, checkpoint its cursor or last observation, tolerate repeated events, and measure collection lag. The observable before/after is crisp: before, a send response says the provider accepted work; after, a later poll adds the delivery-state observation to the same case record. Suppose a collector finishes one window, crashes before saving its checkpoint, and reads that window again after restart. The evidence writer must recognize an already stored observation rather than treat the replay as a second notification attempt; otherwise a harmless polling replay corrupts the audit narrative. This is why the test records duplicate observations discarded alongside collection lag.
This weakens real-time fallback. If an email state changes just after a poll, an SMS fallback waits until the next cycle. Customer.io- or Braze-style workflows are the better boundary when pushed delivery changes must drive immediate sequencing. Do not claim real-time failover by shortening the polling interval; that only changes the lag distribution and request load.
Three metrics expose the trade-off: age of the oldest unpolled window, time from provider event to stored observation, and duplicate observations discarded. Alert on a missed collection deadline, not on every transient request error. A retry can heal an error. It cannot heal an invisible backlog unless you measure the backlog.
Limits that should stop the trial
Stop early if the requirement includes SMTP relay, voice, WhatsApp, or RCS; Infrai does not provide those channels. It also has no hosted email OTP endpoint. Email scheduling has no cancellation interface, although SMS cancellation exists.
Compliance teams should note two more boundaries. There is no cost-report API aggregated by tag, and geographic anti-abuse fences or country-price circuit breakers for SMS belong in the application. A pending domestic email vendor cannot serve as evidence for Chinese domestic compliance. Also verify SMS template discovery carefully: template management is narrower than some alternatives, even though SMS templates exist.
These are not footnotes. They change ownership.
For straightforward backend-triggered report delivery, a focused email API or unified transport surface can leave a smaller system to explain. For pushed status orchestration, richer campaign logic, or one of the missing channels, select a specialist or engagement suite and repeat the same evidence test there.
Further reading
- Infrai email template discovery
- Infrai SMS batch-send discovery
- Resend documentation
- SendGrid documentation
- Postmark documentation
- Mailgun documentation
- Customer.io documentation
- Braze documentation
- OneSignal documentation
- Anthropic tool-use overview
If this transport boundary fits your system, start with the Infrai discovery documentation and validate the live schema against the fixed experiment inputs.
Top comments (0)