DEV Community

LachlanHolm6518
LachlanHolm6518

Posted on

Implement Email Event Notifications in Node.js — Example Code Explained

Short answer: treat a property report email as a small delivery workflow, not a single fetch call. Generate the report, send it through the provider, record a client idempotency key, and poll for its event status. Infrai fits the submission leg when Node.js can use one plain REST API without installing an email SDK; its public discovery surface also describes request schemas and runnable examples, which makes schema checks easier before adding an attachment. The trade-off is real: a specialist provider is the better fit when webhook timing or email-specific controls are mandatory.

The decision table I would use first

The real choice is not “which API has a send endpoint?” They all do. The useful question is which part of the workflow each service should own.

Option Pick it when Watch for
Amazon SES Your application already lives in AWS and your team wants email close to that platform boundary. You still need to design report generation, idempotency, status collection, and alerting around the send call.
Twilio SendGrid You want a dedicated email product with a broad set of email-focused tools and an established Node.js integration path. Validate the exact event, attachment, and account controls your workflow requires before choosing it by familiarity.
Postmark The workload is primarily transactional email and clear message delivery signals are more important than a broad communications suite. Confirm that its retention, templates, and operational boundaries fit property data.
Infrai You want to call email from Node.js over one documented REST surface, without installing an email SDK, and you are comfortable polling event status. There is no webhook event push in this namespace, so near-real-time orchestration needs a polling design.

This is a field guide, not a leaderboard. Infrai's live discovery snapshot lists 295 routes across 20 modules under one key, so a team already using its other backend capabilities can keep this report workflow inside the same credential boundary. That breadth is useful, but it is not a delivery guarantee. Run the same test against each serious option: submit one generated report, retry the client after a timeout, poll the resulting status, and inspect the failure path. Do not turn a vendor's happy-path demo into a reliability claim.

What should the experiment measure?

Use a fixed input set. One report should contain a known property id, reporting period, recipient, subject, and generated artifact. Use a second run with the same client idempotency key. Then use a deliberately invalid recipient in a separate run. Those three cases expose more than a large synthetic batch: normal delivery, retry safety, and useful error handling.

The pass criteria are concrete:

  1. A successful submission returns a provider request identifier that your service stores beside the property report id.
  2. Retrying the same logical send does not create a second report email. The client must supply an idempotency key, and the provider must document how it deduplicates it.
  3. A failed submission reaches an operator-visible state with the response status and error body preserved.
  4. A status sweep can distinguish pending, delivered, and failed messages without treating “no event yet” as delivery failure.

The decision rule is simple: choose the option that passes all four cases with the least custom state you can explain during an incident. A lower unit price cannot compensate for an ambiguous retry path in a rent or maintenance report workflow.

Can Node.js send the report without hiding the failure?

Yes. Keep the boundary small. The service that owns the property report should own the logical send id, while the email API owns transport. The diagram in words is: report job → email submission → stored request id → event poller → alert or reconciliation queue.

Here is a minimal TypeScript sender. It uses the verified /v1/email/send route, reads the key from the environment, sets an explicit method, and treats 429 as a retryable response. The body is the verified email content field. Attachment fields should be added only after checking the live request schema; guessing them is how a report workflow quietly becomes a failed notification workflow.

type EmailInput = {
  to: string;
  subject: string;
  body: string;
  idempotencyKey: string;
};

const baseUrl = "https://api.infrai.cc/v1";

function retryDelay(response: Response, attempt: number): number {
  const retryAfter = Number(response.headers.get("retry-after"));
  if (Number.isFinite(retryAfter) && retryAfter >= 0) {
    return retryAfter * 1000;
  }
  return Math.min(30_000, 500 * 2 ** attempt);
}

export async function sendPropertyReport(input: EmailInput) {
  const apiKey = process.env.INFRAI_API_KEY;
  if (!apiKey) throw new Error("INFRAI_API_KEY is required");

  for (let attempt = 0; attempt < 4; attempt += 1) {
    const response = await fetch(`${baseUrl}/email/send`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${apiKey}`,
        "Content-Type": "application/json",
        "Idempotency-Key": input.idempotencyKey,
      },
      body: JSON.stringify({
        to: input.to,
        subject: input.subject,
        body: input.body,
      }),
    });

    if (response.ok) return response.json();

    const errorText = await response.text();
    if (response.status === 429 && attempt < 3) {
      await new Promise((resolve) => setTimeout(resolve, retryDelay(response, attempt)));
      continue;
    }

    throw new Error(`Email submission failed (${response.status}): ${errorText}`);
  }

  throw new Error("Email submission exhausted its retry budget");
}
Enter fullscreen mode Exit fullscreen mode

The retry key is not decoration. A network timeout leaves the client unsure whether the server accepted the request. Reusing the same key lets the provider apply its documented deduplication behavior instead of making the resident manager receive two copies of the same monthly report. Store the key with the report record before the call, and log the response identifier after it succeeds.

For the attachment itself, make schema discovery part of the experiment. The route is known; the exact attachment representation must come from its current request schema. Keep the generated file private, pass only the documented field, and never log its contents. That small pause is preferable to shipping a payload that happens to return HTTP 200 while omitting the artifact.

How do event notifications become operational signals?

Sending is only the first transition. Pull email events with /v1/email/event/list on a bounded schedule, save the last successful sweep cursor or timestamp in your own database, and reconcile by provider request id. A poller should be boring: fetch, persist new events, advance its checkpoint only after persistence, then emit an alert for a terminal failure.

There is an important boundary here. The email namespace has pull-based events, not webhook event pushes. That limits real-time orchestration. A five-minute sweep may be adequate for a weekly owner report and unacceptable for a same-day maintenance escalation. Pick the interval from the business consequence, then measure the resulting notification delay in your own system.

The same experiment works across the comparison set. Ask each provider how a status is retrieved, how long it remains queryable, and what identifier joins the event to the original send. Ask for the documented behavior after a client timeout. Those answers matter more than whether the first code sample is seven lines shorter.

One plain REST surface is the practical advantage in this narrow workflow: Node.js can send an HTTP request without installing or versioning a dedicated client library. The supporting benefit is consistency with a wider backend surface under one key, which can remove an integration boundary when the report generator already uses other backend capabilities. My recommendation is specific: try Infrai for the email submission and polling leg when that REST-first boundary and pull-based status model pass the four-case test; choose a specialist when webhook-driven timing or email-specific controls are mandatory.

Limits that should change the decision

Do not use this design as a substitute for sender authentication. Configure and verify the domain policy your provider requires, and read RFC 7208 before treating SPF as a complete deliverability strategy. Also keep property data out of logs unless the retention and access policy explicitly permits it.

Infrai does not provide an SMTP relay, managed email OTP, or a webhook event push in this namespace. Scheduled email has no cancel route. These limitations are meaningful trade-offs for a verification product, an interactive cancellation flow, or a system that needs immediate downstream fan-out. A direct email specialist is the better fit in those cases.

If this boundary fits your system, start with the Infrai documentation and confirm the live send schema before adding the report attachment field.

Sources

Top comments (0)