Skip to content

Transactional Email

Use the transactional endpoint for messages caused by a specific application action, such as a verification code, password reset, receipt or security notification.

POST https://app.forgecloud.cc/api/v1/email/send
Authorization: Bearer ef_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000

Idempotency-Key is required. Generate one UUID for each logical email and reuse it only when retrying that same request. Reusing a key with different request data is rejected.

{
"from": "noreply@example.com",
"to": ["user@example.net"],
"subject": "Your verification code",
"text_body": "Your verification code is 482193.",
"html_body": "<p>Your verification code is <strong>482193</strong>.</p>",
"purpose": "transactional"
}
Field Required Description
from Sometimes Required for a project key when the project has multiple verified senders. It must match a verified sender in that project.
to Yes Recipient addresses. The current per-request maximum is 50.
subject Yes Subject line, up to 300 characters.
text_body Yes Plain-text body.
html_body No Optional HTML alternative.
purpose No When present, must be transactional.

A sender-scoped key always uses its selected identity. If from is present, it must match that identity.

A request can contain more than one recipient, so the response reports each delivery separately:

{
"status": "queued",
"traffic_class": "tenant_transactional",
"accepted": 1,
"sent": 1,
"failed": 0,
"deliveries": [
{
"id": "send_a1b2c3d4e5f6",
"to": "user@example.net",
"status": "queued",
"smtp_message_id": "<forge-send_a1b2c3d4e5f6@example.com>",
"error": null,
"duration_ms": 342
}
]
}

The top-level status can be queued, partial, suppressed or failed. A 201 response means at least one delivery was accepted for submission. It does not prove inbox placement.

const response = await fetch("https://app.forgecloud.cc/api/v1/email/send", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PROJECT_EMAIL_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
from: "noreply@example.com",
to: ["user@example.net"],
subject: "Welcome",
text_body: "Thanks for signing up.",
html_body: "<p>Thanks for signing up.</p>",
purpose: "transactional",
}),
});
const result = await response.json();
if (!response.ok) throw new Error(result.error ?? "Email request failed");

Use your own service variable name rather than committing the API key. The example variable name is not required by Forge.

  • 400: malformed or unsupported request data.
  • 401: invalid, revoked or insufficient API key.
  • 409: recipient/provider suppression, unverified identity or conflicting idempotency use.
  • 429: request or quota limit. Respect Retry-After when present.
  • 502: no recipient could be submitted to the configured delivery route.

Retry network errors and retryable server responses with the same idempotency key. Do not generate a new key for the same logical email because that can create a duplicate delivery.

Open Email → Send History to review each recipient. queued is submission acceptance; later delivery evidence may advance the record to delivered, deferred or bounced. A recipient can also be suppressed after a permanent failure, complaint or active provider incident.

For higher-volume or marketing email, use a provider and workflow designed for consent, unsubscribe processing, complaint handling and separate reputation. Forge’s current public API is transactional-only.