New from emailrHealthCheck Email iconHealthCheck Email
emailr_

Migrate from SparkPost to emailr_

Move from SparkPost to emailr_: replace transmissions, map substitution data, campaigns, metadata, suppressions and webhook events step by step.

SparkPost iconSparkPostemailr_

SparkPost sends everything as a transmission: content plus a recipient list plus substitution data. emailr_ splits that into single sends through the API and audience sends through Broadcasts, which keeps each call easy to read and retry.

This guide covers the sparkpost Node client, stored templates, substitution data, the suppression list and webhooks.

The code change

A typical SparkPost send call next to its emailr_ equivalent.

Before (SparkPost)ts
import SparkPost from 'sparkpost'

const client = new SparkPost(process.env.SPARKPOST_API_KEY)

await client.transmissions.send({
  campaign_id: 'receipts',
  content: {
    from: '[email protected]',
    subject: 'Your receipt',
    html: '<p>Thanks for your order.</p>',
  },
  recipients: [{ address: '[email protected]' }],
  metadata: { order_id: '123' },
})
After (emailr_)ts
import { Emailr } from 'emailr'

const emailr = new Emailr({ apiKey: process.env.EMAILR_API_KEY! })

await emailr.emails.send({
  from: '[email protected]',
  to: '[email protected]',
  subject: 'Your receipt',
  html: '<p>Thanks for your order.</p>',
  tags: { campaign: 'receipts', order_id: '123' },
})

SparkPost concepts in emailr_

Use this table to find where each feature you rely on lives in emailr_.

SparkPostemailr_
API keyAPI key (et_live_ prefix)
Sending domain + bounce domainDomain with send. MAIL FROM subdomain
Transmissions (single recipient)emails.send()
Transmissions to a recipient listBroadcasts to a segment
Stored templates + substitution_dataTemplates (template_id + template_data)
campaign_id + metadatatags object
Suppression listContacts with subscribed: false
SubaccountsDomain workspaces
Relay webhooks (inbound)Inboxes and email.received webhooks

How to migrate from SparkPost

  1. 01

    Create an API key

    Create a key under API keys in the dashboard. Keys start with et_live_ and are scoped to one domain workspace. Store it as EMAILR_API_KEY in your secret manager or environment, never in client-side code.

  2. 02

    Add and verify your sending domain

    In the dashboard, open Domains and add the domain you send from. A subdomain such as mail.yourcompany.com keeps transactional reputation separate from your corporate mailbox.

    Emailr generates the DNS records: a DKIM TXT record on the emailr._domainkey selector, an SPF TXT record, and an MX + TXT pair on the send subdomain used as the MAIL FROM / Return-Path. A DMARC record is optional but recommended.

    SparkPost uses a scph…._domainkey selector and a separate bounce domain CNAME. Keep them until you finish the cutover.

    Emailr's DKIM selector and send subdomain don't collide with other providers' records, so you can add them before cutting over and send from both providers during the migration. A domain can only have one SPF record: if one already exists, merge Emailr's include: into it instead of adding a second record.

  3. 03

    Replace transmissions

    Install the SDK. For one-to-one messages, replace transmissions.send() with emails.send(): the content object's from, subject, html and text move to the top level and recipients[].address becomes to. campaign_id and metadata move into tags.

    For transmissions sent to a stored recipient list, import the list as contacts, group them in a segment and send a Broadcast instead.

    Terminalbash
    npm install emailr
  4. 04

    Move stored templates

    Recreate templates in emailr_ and pass values in template_data instead of substitution_data.

    Emailr templates substitute flat {{variable}} tokens from template_data. There are no loops or conditionals, so pre-render lists and optional blocks in your code and pass the result as a variable. SparkPost's {{if}} and {{each}} blocks need the same treatment.

  5. 05

    Move the suppression list

    Export the SparkPost suppression list and import the addresses as contacts with subscribed: false so broadcasts skip them.

  6. 06

    Recreate your event webhooks

    SparkPost batches events into arrays of msys objects. emailr_ sends one event per request with a flat { event, data } body.

    Create a webhook of type transactional (delivery and engagement events), domain (verification status) or receiving (inbound email). Each webhook has its own signing secret. Every request carries an X-Emailr-Signature header: a hex-encoded HMAC-SHA256 of the raw request body. Verify it before trusting the payload.

    verify-webhook.tsts
    import { createHmac, timingSafeEqual } from 'node:crypto'
    
    // rawBody must be the exact request body string, before JSON.parse
    export function isValidEmailrWebhook(rawBody: string, signature: string | null) {
      if (!signature) return false
      const expected = createHmac('sha256', process.env.EMAILR_WEBHOOK_SECRET!)
        .update(rawBody)
        .digest('hex')
      const a = Buffer.from(expected)
      const b = Buffer.from(signature)
      return a.length === b.length && timingSafeEqual(a, b)
    }
    
    // const ok = isValidEmailrWebhook(body, req.headers.get('x-emailr-signature'))
    // const { event, data } = JSON.parse(body)  // e.g. event === 'email.bounced'

Webhook event mapping

emailr_ webhooks post one JSON event per request with an event name and a data object. Translate event names in your handler with this table.

SparkPost eventemailr_ event
injectionemail.sent
deliveryemail.delivered
bounceemail.bounced
spam_complaintemail.complained
policy_rejectionemail.failed
initial_open / openemail.opened
clickemail.clicked

Things to watch for

  • Per-recipient substitution_data inside one transmission isn't supported; send per recipient or use Broadcasts with contact properties.
  • SparkPost's EU and US hosts are separate; emailr_ uses a single API base URL.
  • Remove SparkPost's DNS records only after your last transmission has been sent.

Frequently asked questions

What replaces SparkPost transmissions?

Single messages use the emails API. Sends to a recipient list become Broadcasts to a segment of contacts, which also handle unsubscribe links.

Does emailr_ support SparkPost template logic?

No. emailr_ templates substitute flat {{variable}} values. Conditional and loop blocks need to be rendered in your code and passed in as variables.

How do campaign_id and metadata map?

Put them in the tags object on the send request, for example tags: { campaign: 'receipts', order_id: '123' }.

Can I keep my SparkPost bounce domain?

emailr_ creates its own MAIL FROM on the send subdomain of your sending domain. Keep SparkPost's bounce domain until you've finished migrating, then remove it.

Ready to leave SparkPost?

Create a free account, verify your domain and send your first email through emailr_ in minutes. No credit card required.