# How to send email with a REST API: integration guide

> Integrate an email sending API securely with cURL and TypeScript, then add validation, timeouts, retries, delivery webhooks, and a durable production path.

- **Category:** Developer guide
- **Published:** August 4, 2026
- **Reading time:** 16 min read
- **Author:** Maya Chen, Email infrastructure
- **Canonical page:** [https://emailbump.com/blog/send-email-rest-api](https://emailbump.com/blog/send-email-rest-api)

To send email with a REST API, your server makes an authenticated HTTPS request containing the sender, recipient, subject, and message content. The provider validates the request, submits the message to its delivery infrastructure, and returns an identifier you can use to follow later events.

The first successful request takes minutes. A production integration also needs a safe server boundary, domain authentication, runtime validation, deadlines, retry classification, delivery-event processing, suppression handling, and a way to survive an application restart. This guide builds those pieces in order.

> **REST API email in one sentence**
>
> Keep the credential on your server, POST one validated message intent to the provider, store the returned ID, and treat signed webhooks—not the send response—as delivery evidence.

## REST email API vs SMTP

### Choosing an email integration

```text
OPTION          BEST WHEN                              TRADEOFF
REST API        New application integration             Provider-specific contract
Provider SDK    SDK adds types or useful helpers         Dependency and upgrade surface
SMTP relay      Existing framework already speaks SMTP  Less structured request/errors
Gmail API       Acting inside a user's Gmail mailbox    OAuth and mailbox quotas
Graph mail API  Acting inside Microsoft 365 mailboxes   Tenant and permission model

REST and SMTP can use the same provider. Choosing HTTP for one service
does not prevent a legacy application from using its SMTP relay.
```

A REST API is usually the clearest default for product email because HTTP exposes status codes, request IDs, structured errors, templates, scheduling, metadata, and event models directly. SMTP remains a sound submission protocol when portability, a corporate relay, or an existing library matters more than provider-specific features.

## Before the first API call

- Create separate development and production projects or credentials.
- Verify a domain you control and publish the provider’s DKIM and return-path records.
- Store the API key in a deployment secret store, not source code or frontend environment variables.
- Choose a fixed application From address for the first test.
- Send only to an inbox you control until authentication and event handling are verified.
- Decide which application action owns the email, such as account verification or payment receipt.

Do not begin by exposing POST /send-email to the browser with arbitrary to, from, subject, and html fields. That is an email relay. Begin with a named business action whose server-side handler loads the authoritative recipient and chooses an approved sender and template.

## Send email through a REST API with cURL

This example uses Email Bump’s POST /api/v1/emails endpoint. Replace the addresses with a verified sender and a test inbox. Supplying HTML and text gives mail clients a useful alternative when HTML is disabled or unavailable.

### cURL — send email through REST

```bash
curl --request POST 'https://emailbump.com/api/v1/emails' \
  --header "Authorization: Bearer $EMAIL_BUMP_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
    "from": "Acme <account@example.com>",
    "to": "jamie@example.net",
    "reply_to": "support@example.com",
    "subject": "Verify your email address",
    "html": "<h1>Verify your email</h1><p>Your code is <strong>482913</strong>.</p>",
    "text": "Verify your email\n\nYour code is 482913."
  }'
```

A successful response includes an Email Bump ID, the underlying provider message ID, and status sent. Sent means accepted by the delivery provider; it does not mean delivered to the receiving server, placed in the inbox, opened, or read.

### Accepted response

```json
{
  "object": "email",
  "id": "6dde5322-c940-43e5-84cc-97a2d8c69c08",
  "message_id": "provider-message-id",
  "to": "jamie@example.net",
  "from": "Acme <account@example.com>",
  "subject": "Verify your email address",
  "status": "sent",
  "created_at": "2026-08-04T14:20:00Z"
}
```

## Integrate the email API in TypeScript

Put provider details behind one server-only adapter. The rest of the application should pass a typed message intent and receive a normalized result; it should not know credential names, URLs, raw status codes, or provider response shapes.

### src/email/provider.ts

```typescript
export type EmailMessage = {
  from: string;
  to: string;
  replyTo?: string;
  subject: string;
  html: string;
  text: string;
};

export type EmailAccepted = {
  id: string;
  providerMessageId: string;
};

export class EmailApiError extends Error {
  constructor(
    message: string,
    readonly kind: "retryable" | "permanent" | "ambiguous",
    readonly status?: number,
    readonly retryAfterMs?: number,
  ) {
    super(message);
  }
}

function apiKey() {
  const key = process.env.EMAIL_BUMP_API_KEY;
  if (!key) throw new Error("EMAIL_BUMP_API_KEY is missing");
  return key;
}

export async function submitEmail(message: EmailMessage): Promise<EmailAccepted> {
  let response: Response;
  try {
    response = await fetch("https://emailbump.com/api/v1/emails", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${apiKey()}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        from: message.from,
        to: message.to,
        reply_to: message.replyTo,
        subject: message.subject,
        html: message.html,
        text: message.text,
      }),
      signal: AbortSignal.timeout(10_000),
    });
  } catch {
    throw new EmailApiError("Request ended without a response", "ambiguous");
  }

  if (!response.ok) {
    const retryAfter = response.headers.get("retry-after");
    const retryAfterMs = retryAfter ? Number(retryAfter) * 1_000 : undefined;
    const kind = response.status === 429 || response.status >= 500
      ? "retryable"
      : "permanent";
    throw new EmailApiError("Email API rejected the request", kind, response.status, retryAfterMs);
  }

  const body = await response.json() as { id: string; message_id: string };
  return { id: body.id, providerMessageId: body.message_id };
}
```

The adapter intentionally does not log the full response or message. Email bodies can contain reset links, addresses, invoices, health information, or other personal data. Log the internal intent ID, provider ID, HTTP status, duration, and a safe error class instead.

## Validate a named email action on the server

A browser can request resend verification. The server authenticates the session, loads the user’s current email address, checks whether verification is still needed, rotates or reuses a bounded token, renders a known template, and applies a purpose-specific rate limit. The browser never chooses the recipient or HTML.

### Server-side command boundary

```bash
UNTRUSTED REQUEST
  POST /account/resend-verification
  session cookie

SERVER DECIDES
  authenticated user
  authoritative recipient
  approved From address
  verification template + current version
  token lifetime
  per-user and per-IP rate limit
  unique message intent

PROVIDER RECEIVES
  one fully resolved email
```

- Validate all runtime input even when the codebase uses TypeScript.
- Keep public request bodies small and reject unsupported content types before parsing.
- Escape untrusted variables before placing them in HTML; template literals do not escape markup.
- Return neutral recovery responses so callers cannot enumerate accounts.
- Rate-limit by purpose, account, and network signal rather than only one global counter.
- Use a stable semantic key so repeated clicks reuse one active verification intent.

## Handle email API errors and retries

### REST email error policy

```text
RESULT                         ACTION
2xx accepted                   Store provider ID; wait for events
400 / 422 invalid request      Permanent; fix payload or template
401 / 403                      Permanent; alert on key or permission
404 endpoint/template          Permanent until configuration changes
408 / 429                      Retry with bounded backoff and jitter
5xx                            Retry within an attempt and age budget
Timeout / connection reset     Ambiguous; may already have been accepted
Delivered event                Receiving server accepted the message
Bounce / complaint event       Suppress or route to policy handling
```

A network timeout is not proof that nothing happened. The provider may accept the message and lose the response on its way back. Use a provider-supported idempotency key when available. Without one, retain an ambiguous state and reconcile provider activity before blindly resending a high-impact receipt or security alert.

Retry only while the message is still useful. A login code that expires after ten minutes should not wait in a queue for an hour. Record both an attempt limit and a maximum message age, honor Retry-After, add jitter, and stop on permanent validation or authentication errors.

## Use a transactional outbox for important email

Your database commit and the provider’s API call cannot be one atomic transaction. For receipts, invitations, security alerts, and other important messages, commit business state and a unique email intent in the same database transaction. A worker can then submit it after the request returns and recover unfinished work after a restart.

### Reliable REST email pipeline

```text
APPLICATION TRANSACTION
  update business state
  insert unique email intent
  commit
        |
        v
DURABLE WORKER
  claim with lease -> render -> POST REST API -> store provider ID
        |                                |
        | retry / ambiguous              v
        +------------------------- signed delivery webhooks
                                         |
                                         v
                              message ledger + suppression
```

## Process signed delivery webhooks

Email Bump can send email.delivered, email.bounced, and email.complained events to an HTTPS endpoint. Verify the signature against the exact raw request body before parsing JSON. Acknowledge valid events quickly, enqueue slower work, and deduplicate by event ID because webhook delivery is at least once.

### Verify an Email Bump webhook in Node.js

```javascript
import crypto from "node:crypto";

export function verifyEmailBumpWebhook(rawBody, header, secret) {
  const parts = Object.fromEntries(
    header.split(",").map((part) => part.split("=")),
  );
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${parts.t}.${rawBody}`)
    .digest("hex");

  const actualBytes = Buffer.from(parts.v1 ?? "", "hex");
  const expectedBytes = Buffer.from(expected, "hex");
  return actualBytes.length === expectedBytes.length
    && crypto.timingSafeEqual(expectedBytes, actualBytes);
}
```

Delivery means the receiving mail server accepted the message. It does not prove inbox placement or human attention. Opens can be generated by privacy proxies and security scanners; clicks can be generated by link inspection. Use delivery, bounce, complaint, and suppression events for operations, and treat engagement events as directional product signals.

## REST email API production checklist

- Server-only API key with separate development and production credentials.
- Verified domain with SPF, DKIM, and DMARC configured and monitored.
- Narrow business commands rather than a public arbitrary-send endpoint.
- Typed templates that render both HTML and plain text from validated variables.
- Request deadline plus explicit permanent, retryable, and ambiguous error classes.
- Durable outbox, worker leases, message age limit, and bounded retry budget.
- Provider idempotency where supported and semantic deduplication in your database.
- Signed, deduplicated, out-of-order-safe delivery-event processing.
- Automatic suppression for permanent bounces, complaints, and unsubscribes.
- Alerts for queue age, error rate, deferrals, bounces, complaints, and exhausted quota.

## Frequently asked questions

## Can a REST API send email?

Yes. Transactional email providers expose authenticated HTTP endpoints that accept message content and submit it to managed email delivery infrastructure. An HTTP success response normally means accepted or queued, while later webhook events report delivery, bounce, and complaint outcomes.

## Can I call an email sending API from React or browser JavaScript?

Do not call a privileged provider API directly from the browser. Anyone can extract the credential and use your account. Call your own narrow server endpoint from React, authenticate or constrain the request there, and keep the provider key and message policy in server-only code.

## Should I use REST or SMTP to send email?

Use REST for a new product integration when structured errors, metadata, scheduling, templates, and provider events are valuable. Use SMTP when an existing framework already supports it, a corporate relay is mandated, or standards-level portability matters. Both can be production-ready with the same domain authentication and operational controls.

## Send email with one authenticated REST request

Create a free Email Bump account, verify a domain, and send product email through the same workspace that owns campaigns, contacts, flows, inbound mail, and delivery activity.

[Open the API quickstart](https://emailbump.com/docs/transactional-api)

## Keep building the email API layer

- [Email API buyer's guide](https://emailbump.com/blog/email-api) — Evaluate providers before coupling your application to one contract.
- [Free email API](https://emailbump.com/blog/free-email-api) — Compare permanent free plans and the limits that affect real applications.
- [Email inbox API](https://emailbump.com/blog/email-inbox-api) — Add receiving, mailbox sync, replies, and application-owned addresses.

## Sources

- [Email Bump transactional API](https://emailbump.com/docs/transactional-api)
- [Email Bump webhook API](https://emailbump.com/docs/webhooks-api)
- [Google email sender guidelines](https://support.google.com/mail/answer/81126)
- [Yahoo sender best practices](https://senders.yahooinc.com/best-practices/)
