SMTP relay
The SMTP relay lets software that only speaks SMTP (CMSs, auth providers, frameworks, legacy apps) send through SpacePost. Messages go through the same pipeline as the API: domain checks, suppression list, quotas, tracking, webhooks.
| Setting | Value |
|---|---|
| Host | smtp.spacepost.dev |
| Port | 25976 |
| Security | STARTTLS, required before authenticating |
| Username | spacepost (the alias resend is accepted too, for migrations) |
| Password | Your API key (sp_live_...) |
Connect without TLS and upgrade with STARTTLS before you log in; the certificate is valid for smtp.spacepost.dev. Implicit TLS (port 465) is not offered yet, so in most clients choose “STARTTLS” or “TLS”, not “SSL”.
The From address must belong to a verified domain. A sending_access key is enough; a domain-restricted key can only send from its domain.
Control headers
Section titled “Control headers”Add these headers to your message to use features SMTP has no field for. They are consumed by SpacePost and not delivered to the recipient.
| Header | Example | Effect |
|---|---|---|
X-SpacePost-Tags |
category=welcome, user_id=u_1042 |
Adds tags. Comma-separated name=value pairs; same character rules as the API. |
X-SpacePost-Scheduled-At |
2026-11-01T09:00:00Z or in 1 hour |
Schedules the email (up to 30 days). |
Other X-* headers, List-Unsubscribe, List-Unsubscribe-Post, In-Reply-To and References are forwarded as custom headers.
Examples
Section titled “Examples”import nodemailer from "nodemailer";
const transport = nodemailer.createTransport({ host: "smtp.spacepost.dev", port: 25976, secure: false, // STARTTLS: the connection is upgraded to TLS before login requireTLS: true, auth: { user: "spacepost", pass: process.env.SPACEPOST_API_KEY },});
await transport.sendMail({ from: "Acme <hello@acme.com>", to: "user@example.com", subject: "Hello over SMTP", html: "<p>It works.</p>", headers: { "X-SpacePost-Tags": "category=welcome", "X-SpacePost-Scheduled-At": "in 10 minutes" },});import os, smtplibfrom email.message import EmailMessage
msg = EmailMessage()msg["From"] = "Acme <hello@acme.com>"msg["To"] = "user@example.com"msg["Subject"] = "Hello over SMTP"msg["X-SpacePost-Tags"] = "category=welcome"msg.set_content("It works.")msg.add_alternative("<p>It works.</p>", subtype="html")
with smtplib.SMTP("smtp.spacepost.dev", 25976) as s: s.starttls() s.login("spacepost", os.environ["SPACEPOST_API_KEY"]) s.send_message(msg)swaks --server smtp.spacepost.dev --port 25976 --tls \ --auth-user spacepost --auth-password "$SPACEPOST_API_KEY" \ --from hello@acme.com --to delivered@test.spacepost.dev \ --header "Subject: Hello" --header "X-SpacePost-Tags: category=test" --body "It works."How messages are mapped
Section titled “How messages are mapped”To,CcandReply-Tocome from the headers. Envelope recipients (RCPT TO) that are not inToorCcbecome Bcc.- HTML and text parts are preserved. Attachments, including inline images (
Content-ID), are supported. - A message without a subject gets the subject
(no subject). - Each recipient counts against your quota, as with the API.
Limits and error codes
Section titled “Limits and error codes”| Limit | Value |
|---|---|
| Message size | 40 MB, reply 552 |
| Authentication failures | 10 failed attempts per IP in 10 minutes, then blocked |
SMTP replies map the API errors:
| Situation | Reply |
|---|---|
| Daily or monthly quota exceeded | 452 (temporary: your software will retry later) |
| Rate limited | 421 |
From domain not verified |
553 |
| Restricted key, paused team, validation errors, content blocked | 550 |
| Temporary problem on our side | 451 |