Migrating from Resend
SpacePost implements the Resend API: same paths, same field names, same response shapes and the same error format. Most apps migrate by changing a base URL and an API key.
-
Create a SpacePost API key in the dashboard. SpacePost keys start with
sp_live_(Resend keys start withre_). -
Add and verify your domain in SpacePost. DNS records differ from Resend’s, so add the new DKIM CNAMEs and the
sendMX/TXT records. You can keep Resend’s records in place while you cut over, because DKIM selectors do not collide. See Adding and verifying. Move your SPF/DMARC policy only if you changed thesendsubdomain. -
Switch the client. Pick one of the options below.
Keep the official
resendpackage. Set the base URL with an environment variable and use your SpacePost key:.env RESEND_API_KEY=sp_live_YOUR_API_KEYRESEND_BASE_URL=https://api.spacepost.devimport { Resend } from "resend";const resend = new Resend(process.env.RESEND_API_KEY);await resend.emails.send({ from: "Acme <hello@acme.com>", to: "you@example.com", subject: "Hi", html: "<p>Hi</p>" });Or pass it in code:
const resend = new Resend("sp_live_YOUR_API_KEY", { baseUrl: "https://api.spacepost.dev" });Swap the import. The method names and
{ data, error }return shape are the same.import { Resend } from "resend";const resend = new Resend(process.env.RESEND_API_KEY);import { SpacePost } from "@spacepost/node";const spacepost = new SpacePost(process.env.SPACEPOST_API_KEY);await resend.emails.send({ ... });await spacepost.emails.send({ ... });Change the host and the key. Paths and bodies are identical.
curl https://api.resend.com/emails -H "Authorization: Bearer re_..."curl https://api.spacepost.dev/emails -H "Authorization: Bearer sp_live_..."Change host, port and password, and use STARTTLS. The username
resendis accepted as an alias forspacepost.Host: smtp.spacepost.dev Port: 25976 (STARTTLS)User: spacepost Password: sp_live_YOUR_API_KEY -
Recreate your webhooks. Create each endpoint again in SpacePost (
POST /webhooks) and store the newsigning_secret. Verification code written for Resend or Svix keeps working because SpacePost sends the same Standard Webhooks headers (plussvix-*duplicates). See Verifying signatures. -
Move audiences and contacts. Create audiences and import contacts through
POST /audiences/{id}/contacts. Placeholders such as{{{FIRST_NAME|fallback}}}and{{{RESEND_UNSUBSCRIBE_URL}}}work unchanged in broadcasts. -
Send a test, then cut over. Send to
delivered@test.spacepost.dev, check the webhook arrives, then deploy.
Parity table
Section titled “Parity table”| Resend feature | SpacePost | Notes |
|---|---|---|
POST /emails |
Supported | to/cc/bcc/reply_to up to 50 each; camelCase aliases replyTo, scheduledAt accepted |
POST /emails/batch |
Supported | Up to 100 emails; attachments and scheduled_at are allowed per email; all-or-nothing |
GET /emails/{id}, GET /emails |
Supported | List omits bodies |
PATCH /emails/{id} |
Supported | Reschedule scheduled emails |
POST /emails/{id}/cancel |
Supported | Scheduled emails only |
Attachments (content, path) |
Supported | path must be https |
| Tags, custom headers | Supported | Same constraints on tag characters |
Idempotency-Key |
Supported | 24 hours, POST /emails and /emails/batch |
| Domains (create, get, list, verify, update, delete) | Supported | open_tracking, click_tracking |
| API keys | Supported | full_access, sending_access, optional domain_id |
| Webhooks, Standard Webhooks signing | Supported | Also svix-* headers |
| Audiences, contacts, broadcasts | Supported | Contacts live under /audiences/{id}/contacts |
| Suppressions API | Supported | Extra: list, add and remove entries |
| Tenants (platform mode) | SpacePost only | Isolated reputation per customer; see Platform mode. The Resend SDK drops tenant_id; use resend.post or tenant-owned domains |
| SMTP relay | Supported | smtp.spacepost.dev:25976 with STARTTLS (no implicit TLS on 465 yet) |
| Inbound email | Not yet | |
| Templates editor | Not yet | Use React Email or your own HTML |
Differences to know about
Section titled “Differences to know about”last_eventvalues. Resend’s values (queued,scheduled,sent,delivered,delivery_delayed,bounced,complained,opened,clicked,canceled,failed) are all present. SpacePost addssuppressed, used when every recipient was on your suppression list. Status never moves backwards: anopenedevent arriving beforedeliveredkeeps the later state.- Extra webhook event.
email.suppressedfires when a recipient is skipped because it is suppressed. Subscribe only to the events you handle. Payloads are otherwise identical to Resend’s;data.tenant_idis added for tenant mail. - Rate limit. 10 requests per second per team (25 on Scale) versus 2 on Resend, with the same
ratelimit-*headers. - Pricing. Pro is €9 for 50,000 emails with €0.25 per extra 1,000, billed in EUR excluding VAT (Resend Pro: USD 20 with USD 0.90 per extra 1,000, Resend’s USD prices as of 23 October 2026). After the first 7 days every plan, including Free, has a daily cap that grows with your sending: the larger of 1,000 and twice your best day in the last 14 days, up to 50,000 per day. See Plans and quotas.
- What counts as an email. Every recipient counts: one email to 3 recipients (to, cc and bcc together) counts 3 against your quota.
- Test sender.
onboarding@test.spacepost.devreplacesonboarding@resend.dev. Test inboxes live attest.spacepost.devinstead ofresend.dev:delivered@,bounced@,complained@,delayed@, plussoftbounced@(SpacePost only). - New-account sandbox. The first 7 days are limited to 100 emails per day. See Verification and limits.
- Automatic pausing. Teams with abnormal bounce or complaint rates are paused automatically. See Automatic pausing.
- Base URL.
https://api.spacepost.dev, with paths at the root. A/v1prefix is also accepted (https://api.spacepost.dev/v1/emails).