Skip to content

Send an email

POST https://api.spacepost.dev/emails

Send one email to up to 50 to recipients, with optional cc, bcc, attachments, tags, headers and a schedule. The call returns as soon as the email is accepted and queued; delivery happens asynchronously and is reported by webhooks.

send.ts
import { SpacePost } from "@spacepost/node";
const spacepost = new SpacePost(process.env.SPACEPOST_API_KEY);
const { data, error } = await spacepost.emails.send({
from: "Acme <hello@acme.com>",
to: ["user@example.com"],
cc: ["manager@example.com"],
bcc: ["archive@acme.com"],
replyTo: "support@acme.com",
subject: "Your invoice",
html: "<p>Invoice attached.</p>",
text: "Invoice attached.",
headers: { "X-Entity-Ref-ID": "inv_1042" },
tags: [{ name: "category", value: "invoice" }],
});
if (error) {
console.error(error.statusCode, error.name, error.message);
} else {
console.log("Email id:", data.id);
}
Response (200)
{ "id": "0192e1c4-7a3b-7c1e-9d2f-3b5a8e6f4c10" }
fromstringrequired

Sender, as email@domain.com or Name <email@domain.com>. The domain (or a parent domain) must be verified. The only exception is onboarding@test.spacepost.dev, see Testing.

tostring | string[]required

Recipients. Up to 50 addresses.

subjectstringrequired

1 to 998 characters.

htmlstringoptional

HTML body. At least one of html or text is required.

textstringoptional

Plain-text body. If you only send html, a text alternative is generated for you, which improves deliverability.

ccstring | string[]optional

Up to 50 addresses.

bccstring | string[]optional

Up to 50 addresses. Never written into message headers.

reply_tostring | string[]optional

Up to 50 addresses. The camelCase alias replyTo is also accepted.

scheduled_atstringoptional

ISO 8601 date or natural language such as in 1 hour, at most 30 days ahead. Alias scheduledAt.

headersobjectoptional

Custom headers as string keys and string values.

attachmentsobject[]optional

Up to 50 attachments, each with filename and exactly one of content or path.

tagsobject[]optional

Up to 50 { name, value } pairs. Names and values: 1 to 256 characters of A-Z a-z 0-9 _ -.

tenant_idstringoptional

Platform mode: send on behalf of a tenant (our id or your external_id). Optional when the from domain belongs to a tenant. Alias tenantId in the Node SDK. See Platform mode.

More: domain verification, Testing, Scheduling, Custom headers, Attachments, Tags, Platform mode.

Addresses in every field may use the Name <email@domain.com> form.

Limit Value
Recipients per field (to, cc, bcc, reply_to) 50
Subject length 998 characters
Attachments 50 per email, filename up to 255 characters
Tags 50 per email, name and value 1 to 256 characters of A-Z a-z 0-9 _ -
Total message size 40 MB including attachments and encoding overhead
Scheduling horizon 30 days
Emails per batch request 100 (Batch sending)

Success returns 200 with the new email id (a UUIDv7). Use it with GET /emails/{id} to read the email and its last_event.

Status name Meaning
401 missing_api_key No Authorization: Bearer header.
403 invalid_api_key Key is unknown, revoked or not a SpacePost key (sp_live_...).
401 restricted_api_key A domain-restricted key was used with a different from domain.
403 invalid_from_address The from domain is not verified for your team.
403 validation_error Using test.spacepost.dev as sender with a recipient that is not a team member or a test address.
403 sending_paused Your team is paused.
422 validation_error A field is missing or invalid. message names the field, for example `to`: Invalid email address.
429 daily_quota_exceeded Daily limit reached: the new-account sandbox, your trust-level cap or a tenant’s daily_limit.
429 monthly_quota_exceeded Free plan monthly limit reached.
429 rate_limit_exceeded More than your requests-per-second limit. See Rate limits.

See the complete list in Errors.

The email moves through statuses, exposed as last_event: queued (or scheduled), sent, then delivered, delivery_delayed, bounced, complained, opened or clicked. If every recipient is on your suppression list, nothing is sent and the status becomes suppressed.