If you need to migrate from Customer.io to Volanea, treat the project as more than an API-key swap. A reliable migration preserves sender authentication, recipient consent and suppression rules, message rendering, event handling, and the operational checks your team relies on when an important email does not arrive.

Customer.io is a broad customer-engagement platform built around people, events, segments, campaigns, and transactional messaging. Volanea provides a unified email infrastructure layer for transactional sends, campaigns, contacts, suppressions, templates, workflows, SMTP, REST sending, and event webhooks. That overlap makes a staged migration possible, but it does not mean every Customer.io object or behavior has a direct one-for-one replacement.

This guide focuses on the developer work: inventorying what sends today, translating transactional sends, re-verifying DNS and webhook behavior, carrying forward suppression protections, testing safely, and cutting over with a rollback plan.

Start with a migration boundary, not a rewrite

The safest way to migrate is to decide exactly what is moving in the first release. For many teams, that first boundary is transactional email produced directly by the application: password resets, verification links, receipts, billing notices, account alerts, and operational notifications.

That boundary is intentionally narrower than “move every Customer.io feature.” It lets you prove the new sending path with messages that have clear triggers, known payloads, measurable outcomes, and a straightforward fallback path. It also keeps campaign orchestration, multi-channel journeys, or complex behavioral segmentation out of the critical path until you have validated the foundational email layer.

Before changing code, classify every Customer.io dependency into one of these groups:

  1. Direct transactional sends: application code calls the Customer.io transactional API for an individual recipient.
  2. Event-triggered campaigns: your application identifies people and tracks events, while Customer.io decides when to send.
  3. Audience and profile data: attributes, identifiers, subscriptions, custom objects, segments, and consent state.
  4. Email assets: transactional message templates, layouts, snippets, images, attachments, sender identities, and unsubscribe links.
  5. Operational integrations: delivery webhooks, bounce/complaint handling, analytics pipelines, data warehouses, support tooling, and alerting.

A direct transactional send can usually move first. A campaign that depends on Customer.io’s event model, Liquid personalization, visual workflow branching, or non-email channels needs an explicit design decision: recreate it using Volanea’s contacts, segments, campaigns, and workflows where appropriate, or retain Customer.io for that use case while moving only the application email path.

Build an accurate Customer.io inventory

A migration plan is only as good as the inventory behind it. Search the codebase for Customer.io client initialization, transactional endpoints, API-key environment variables, event tracking calls, and webhook signatures. Then match that code inventory against the messages and integrations maintained in the Customer.io workspace.

Do not assume all email is sent through one implementation. A mature application often has several paths: a Node service using the SDK, a background worker using raw HTTP, a legacy app using SMTP, a billing system using a webhook-triggered message, and support tooling that triggers a campaign rather than a transactional API call.

What to record for each sending path

Create a worksheet with one row per message type or sending integration. Include the following fields:

  • Message name and business purpose.
  • Triggering service, queue, job, or webhook.
  • Customer.io transactional message ID, if used.
  • Customer identifier used by the call: id, email, or cio_id.
  • Sender address, display name, reply-to address, and sending domain.
  • Recipient fields, including whether a message can address more than one recipient.
  • Variables passed as message_data.
  • Required HTML, text, AMP, headers, attachments, and tracking behavior.
  • Retry logic, idempotency behavior, rate expectations, and timeout settings.
  • Webhook events consumed downstream.
  • Whether the message is transactional, marketing, or mixed-purpose.
  • Existing suppression, unsubscribe, and consent rules.

This inventory makes hidden dependencies visible. For example, a “welcome email” may look transactional in code but may actually depend on a Customer.io campaign delay, subscription preference, and a segment membership rule. Sending the same HTML through another API does not reproduce those controls.

Preserve a message-to-event map

Customer.io transactional sends can be tied to a transactional message template through transactional_message_id, with values passed in message_data. In contrast, a direct Volanea REST send can carry the complete message content in the request, or a Volanea template can be referenced when you have moved the template into Volanea.

Document both the message identity and the business event identity. A stable internal name such as password_reset_requested is more portable than a provider-specific template ID. It also gives your logs, dashboards, queues, and webhook consumers a provider-neutral key during the transition.

Prepare Volanea before touching production traffic

Set up the destination sending path before editing application code. At a minimum, create a Volanea secret key, add the sending domain, install the DNS records returned for that domain, and verify the domain before sending production messages.

Volanea’s REST API base URL is https://api.volanea.com, and the single-message endpoint is POST /v1/send. The API supports sending to one address or up to 50 recipients in a request, runs suppression checks before dispatch, and supports an Idempotency-Key request header for safe retry behavior. Review the current email API reference and setup guides before implementing, particularly when your messages use templates, attachments, scheduling, or custom headers.

Do not reuse a Customer.io API key

Customer.io App API keys and Volanea secret keys are separate credentials with different authentication models and permissions. Store the new Volanea key in your existing secret manager, expose it to the correct runtime only, and remove it from local logs, error reports, screenshots, and client-side code.

Use separate keys and domains for development, staging, and production when your operating model requires isolation. A staging application should not be able to send a production password reset to a real user simply because its environment variable points at production infrastructure.

Verify sender-domain alignment

You may be able to use the same visible From domain after migration, but the underlying delivery provider and authentication records can change. That is why DNS verification is an operational milestone, not a checkbox you defer until launch day.

For each production sending domain, confirm the exact records Volanea supplies after you add the domain. Volanea’s domain setup can return DKIM, bounce-domain MX, SPF, and DMARC-related records. Do not copy record names or values from an example, another provider, or an old Customer.io setup: publish the records returned for your own domain and environment.

Side-by-side code: Customer.io SDK call vs Volanea REST call

The following example shows a common transactional password-reset path. The Customer.io version uses the Node client’s transactional email method with a transactional message template. The Volanea version sends the rendered HTML and text directly through POST /v1/send.

This is deliberately a direct-content comparison. It is a dependable first migration because it avoids claiming that a Customer.io template ID or Liquid template can be used unchanged in another system. If you plan to use Volanea-hosted templates, migrate and test those templates separately before changing this call.

Before: Customer.io transactional template through the Node SDK

import { APIClient, RegionUS } from 'customerio-node';

const cio = new APIClient(process.env.CUSTOMERIO_APP_API_KEY!, {
  region: RegionUS,
});

export async function sendPasswordReset(user: {
  id: string;
  email: string;
  firstName: string;
  resetUrl: string;
}) {
  const response = await cio.sendEmail({
    to: `${user.firstName} <${user.email}>`,
    transactional_message_id: 42,
    identifiers: {
      id: user.id,
    },
    message_data: {
      first_name: user.firstName,
      passwordResetURL: user.resetUrl,
    },
  });

  return response;
}

In this model, the message content is primarily managed in Customer.io. The application supplies a recipient, a Customer.io person identifier, a transactional message ID, and data available to the template. Customer.io’s transactional API also accepts fully specified email content when no transactional template is used, but using message templates is generally the clearer operational model within Customer.io.

After: Volanea direct REST send with an idempotency key

import { randomUUID } from 'node:crypto';

export async function sendPasswordReset(user: {
  id: string;
  email: string;
  firstName: string;
  resetUrl: string;
}) {
  const idempotencyKey = `password-reset:${user.id}:${randomUUID()}`;

  const response = await fetch('https://api.volanea.com/v1/send', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.VOLANEA_API_KEY!}`,
      'Content-Type': 'application/json',
      'Idempotency-Key': idempotencyKey,
    },
    body: JSON.stringify({
      from: 'Acme <security@example.com>',
      to: `${user.firstName} <${user.email}>`,
      replyTo: 'support@example.com',
      subject: 'Reset your password',
      html: `
        <p>Hi ${escapeHtml(user.firstName)},</p>
        <p>We received a request to reset your password.</p>
        <p><a href="${escapeAttribute(user.resetUrl)}">Reset your password</a></p>
        <p>If you did not request this, you can safely ignore this email.</p>
      `,
      text: [
        `Hi ${user.firstName},`,
        '',
        'We received a request to reset your password.',
        `Reset your password: ${user.resetUrl}`,
        '',
        'If you did not request this, you can safely ignore this email.',
      ].join('\n'),
      headers: {
        'X-Message-Type': 'password-reset',
      },
    }),
  });

  if (!response.ok) {
    const detail = await response.text();
    throw new Error(`Volanea send failed: ${response.status} ${detail}`);
  }

  return response.json();
}

function escapeHtml(value: string) {
  return value.replace(/[&<>'"]/g, (character) => ({
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    "'": '&#39;',
    '"': '&quot;',
  })[character]!);
}

function escapeAttribute(value: string) {
  return escapeHtml(value);
}

The important difference is not merely syntax. The Customer.io request identifies a person and passes data into a provider-hosted template. The Volanea request above makes your application responsible for constructing the subject and bodies. That can be an advantage when email content is versioned alongside application code, but it means you need a deliberate template strategy rather than an automatic template conversion.

Also notice the idempotency key. Generate it from a durable business event ID where possible, such as an order ID, password-reset request ID, or notification job ID. Avoid generating a brand-new key every time a queue retry runs; retries for the same logical send must reuse the same key if you want deduplication protection.

Translate data and template responsibilities carefully

Customer.io uses profiles, identifiers, events, data, and Liquid-based message personalization across its engagement product. Volanea has contacts, custom data, templates, campaigns, workflows, and segments, but matching names does not guarantee matching runtime behavior.

The migration question is therefore: where should each piece of message data live after cutover? There are three practical answers.

Option 1: Render content in application code

For critical transactional messages, application-rendered content is often the lowest-risk starting point. The code receives validated business data, renders HTML and text using your existing template system or components, and sends an explicit request to Volanea.

This approach makes deployment and rollback familiar to engineering teams. It also makes a message’s required data contract visible in TypeScript types, tests, and pull requests. The tradeoff is that non-engineering teammates lose the ability to revise that message through a no-code provider editor unless you build a separate content workflow.

Option 2: Move reusable templates into Volanea

Volanea supports reusable templates addressed by templateId, allowing sends to refer to stored content instead of sending markup every time. This can suit operational templates shared across services, campaign content, or teams that want content management within the email platform.

Do not assume Customer.io Liquid syntax can be pasted into a new template unchanged. Export the source HTML, list every dynamic expression, conditional, loop, filter, fallback, layout, snippet, and link transformation, then recreate and test each behavior in the destination. Render representative payloads before production cutover, including missing optional values and unusually long strings.

Option 3: Use a hybrid model

Many teams use provider-managed templates for marketing or broadly owned lifecycle messages, while keeping security and billing messages in application code. A hybrid is valid if you document ownership, source control, approvals, and how each system receives the data needed to personalize emails.

The mistake is allowing the same message type to have two uncoordinated sources of truth. Pick one owner for the template and one owner for the send decision.

Re-verify DNS, deliverability, and sending policy

Moving providers changes the path by which your messages are authenticated and delivered. Even if recipients see the same From address, the sending infrastructure, DKIM selector, bounce handling, and event stream can be different.

DNS and authentication checklist

Use this checklist after adding each Volanea sending domain and before routing meaningful production traffic:

  • Add the sending domain in Volanea and copy the DNS record names and values supplied for that exact domain.
  • Publish the required DKIM record or records exactly as issued by Volanea.
  • Publish the bounce or MAIL FROM subdomain records, including the required MX and SPF records if Volanea provides them.
  • Confirm you do not accidentally create multiple conflicting SPF TXT records at the same hostname. SPF evaluation uses one consolidated record per hostname.
  • Review your organizational DMARC policy and make sure the visible From domain is aligned with the authentication strategy you intend to use.
  • Run domain verification in Volanea after DNS propagation, rather than assuming a record is correct because it appears in your DNS console.
  • Send controlled tests to major mailbox providers and inspect message headers for SPF, DKIM, and DMARC results.
  • Confirm that production, staging, and test sender identities cannot be confused.
  • Document DNS ownership, record TTLs, and the rollback procedure before removing any older records.

Keep the old provider’s valid DNS records in place until the Volanea path has been verified and production traffic has stabilized. Removing Customer.io-related records too early can complicate a rollback or disrupt messages still sent by campaigns you intentionally have not migrated.

Preserve transactional and marketing separation

Customer.io distinguishes transactional messaging from marketing messaging, and its documentation notes that transactional sending can use a distinct transactional IP pool when the appropriate sending-domain configuration is in place. During migration, preserve the underlying principle even if the operational setup changes: do not mix high-volume promotional traffic with password resets and receipts without a deliberate deliverability policy.

Use distinct message classifications, sender addresses, domains or subdomains where appropriate, and monitoring. More importantly, preserve consent boundaries. A customer who opted out of promotional email may still need a legally or contractually necessary receipt, but that does not make every product announcement transactional.

Move webhooks as an event contract

A sending API’s successful response generally confirms acceptance of the request, not inbox placement. Your application should continue to consume delivery lifecycle events and use them to update support views, product state, analytics, and suppression rules.

Volanea supports webhook endpoints configured with a URL and event patterns, including patterns such as email.*. Before enabling production events, write down what your Customer.io webhook consumer receives today and what it expects to do with each event.

Webhook migration checklist

  • Inventory every Customer.io webhook destination, including endpoints configured outside the main application repository.
  • Record event names, payload fields, delivery retry expectations, signature verification approach, and downstream side effects.
  • Create the Volanea webhook endpoint in a non-production environment first.
  • Update the receiver to tolerate a new provider’s event IDs, timestamps, recipient fields, and event naming.
  • Make event processing idempotent. A webhook may be delivered more than once, and your code must not create duplicate support tickets, duplicate database rows, or repeated suppression actions.
  • Return a fast successful response after durable enqueueing; process expensive enrichment or third-party calls asynchronously.
  • Validate webhook authentication according to Volanea’s current webhook documentation before trusting a request.
  • Test accepted, delivered, bounced, complained, suppressed, and unsubscribe-related flows that apply to your account.
  • Keep the old webhook path active during the canary period if old-provider messages are still being sent.

Treat webhook handling as a versioned integration. A clean implementation stores both the provider event identifier and your own internal message or business-event identifier. That mapping is what lets support answer “what happened to receipt 8942?” without depending on a particular vendor dashboard.

Handle suppressions, unsubscribes, and deletion rules conservatively

Suppression data is not a marketing preference file. It contains addresses and identities you should not casually re-enable because of a provider migration. Customer.io distinguishes provider-level email suppressions, such as hard bounces and spam complaints, from workspace-level GDPR suppressions that can prevent a person’s identifiers from being added again.

Volanea performs a suppression check in its sending pipeline. But importing historical data needs care because the source data may not be complete, exportable in a directly reusable form, or equivalent in scope.

What to migrate

At minimum, reconcile these categories:

  • Hard-bounced recipient addresses.
  • Spam-complaint recipient addresses.
  • Addresses manually blocked for abuse, fraud, or support reasons.
  • Marketing unsubscribes and subscription-category preferences.
  • Accounts or identifiers subject to deletion, do-not-contact, or legal erasure policies.
  • Internal test addresses that must never receive production communications.

Separate these categories in your own migration file and policy. A hard bounce is an email-delivery safety signal. A marketing unsubscribe is a consent instruction. A GDPR deletion or suppression record may be an identity-level privacy requirement. They should not be collapsed into a single spreadsheet column called unsubscribed.

The hard part: historical suppression exports

Large historical suppression lists are one of the harder pieces to migrate honestly. Customer.io provides different suppression concepts with different scopes, and its export of people suppressed for GDPR hashes identifiers and email addresses using SHA-256 to protect privacy. A hashed export cannot be turned into a usable destination email suppression list unless you can match it against identifiers held lawfully in your own systems.

Do not try to reverse a hash, and do not import unverified guesses. Instead, involve the privacy owner for your organization, establish what data is allowed to be matched and retained, and test a documented reconciliation process. For delivery suppressions, export or retrieve eligible address-level data through the appropriate Customer.io workflows or APIs, then import it into Volanea using the current destination suppression procedure.

For a large list, batch the import, keep immutable source snapshots, record counts by category, and validate samples. Most importantly, avoid a cutover that silently re-sends to recipients who previously hard-bounced or complained.

Decide what does not map one-to-one

A fair migration guide should acknowledge that Customer.io and Volanea may be used at different layers of a messaging stack. Customer.io is often the place where teams design multi-step customer journeys, ingest event streams, maintain profiles, send across multiple channels, and let marketers manage behavioral lifecycle messaging. Volanea combines email sending and campaign capabilities, but a Customer.io workflow should be evaluated feature by feature rather than assumed to be portable.

Customer.io-specific capabilities to assess

Re-verify these areas explicitly because they may require redesign, partial retention, or a separate replacement:

  • Liquid templates and personalization: expressions, filters, loops, conditional blocks, variable fallbacks, and formatting rules need translation and rendering tests.
  • Journeys and workflow logic: event triggers, delays, time windows, branching conditions, frequency controls, and exit rules may need to be rebuilt with a different model.
  • Profiles and identifiers: Customer.io people can be addressed through identifiers such as id, email, and cio_id; define a durable canonical identifier for Volanea contacts.
  • Event history and behavioral segmentation: decide whether to backfill historical activity, rebuild only current-state segments, or retain an existing analytics/customer-data system as the source of events.
  • Custom objects and relationship data: nested data models may need to be flattened, fetched at send time, or represented through application-owned data.
  • Mobile, in-app, push, SMS, and WhatsApp programs: these are outside a pure email API migration and need their own channel plan.
  • Subscription-center behavior: map categories, defaults, legal copy, and unsubscribe links before any campaign migration.
  • Reporting definitions: metrics may have different event names, timing, attribution rules, or bot-filtering behavior across platforms.

The correct result may be a phased architecture, not an all-or-nothing replacement. For example, a team can migrate receipts and password resets to Volanea while keeping complex cross-channel lifecycle journeys in Customer.io until the business chooses a separate migration plan.

Test message fidelity before delivery volume

A successful API response is not sufficient acceptance testing. Test what recipients actually see, what mailbox providers authenticate, how links behave, whether text alternatives are readable, and whether your downstream systems process events correctly.

A practical test matrix

For each message type, test at least the following cases:

  1. A normal recipient with all personalization fields present.
  2. A recipient with a missing optional field, such as no first name or no company name.
  3. A recipient with special characters, Unicode, apostrophes, HTML-like text, and a long name.
  4. A message with a long URL, a signed URL, or a URL that contains query parameters.
  5. A message with an attachment, if that message type uses one.
  6. A suppressed or unsubscribed test recipient, using a safe non-production test procedure.
  7. A transient network failure or worker retry that proves your idempotency strategy.
  8. A downstream webhook delivery and duplicate-delivery simulation.
  9. Rendering in the mail clients most used by your audience.
  10. Header inspection for sender, reply-to, DKIM, SPF, DMARC, List-Unsubscribe where applicable, and custom correlation headers.

Keep a before-and-after rendering archive. Save source HTML, screenshots, plain-text output, headers, and the JSON payload used for each comparison. This is especially useful for legal notices, invoices, security notifications, and messages that support teams frequently reference.

Cut over gradually and retain a rollback path

Do not switch all traffic because a single test email arrived. Start with a narrow, low-risk message or a small percentage of eligible traffic, then expand as delivery events, support tickets, bounce signals, and rendering checks remain healthy.

A common phased rollout looks like this:

  1. Deploy Volanea sending code behind a feature flag, but leave it disabled.
  2. Send internal test messages through the Volanea path and validate authentication, rendering, and webhooks.
  3. Enable Volanea for one low-volume transactional message type.
  4. Route a controlled cohort or percentage of that message type through Volanea.
  5. Compare acceptance, delivery, bounce, complaint, latency, duplicate-send, and support metrics against the existing path.
  6. Expand message by message, retaining the ability to disable the new route without redeploying.
  7. Only retire Customer.io transactional infrastructure after all dependent messages, dashboards, and webhook consumers are accounted for.

Avoid dual-sending the same production transactional email to real recipients as a comparison method. It creates duplicate receipts, duplicate password resets, and confusion. Prefer internal recipients, test environments, provider test modes where available, or carefully selected mutually exclusive cohorts.

Define rollback in advance

A rollback should be executable under pressure. Decide whether it means switching a feature flag back to Customer.io, pausing a queue, or routing through SMTP temporarily. Document who has permission to make that change, where the flag lives, how long DNS records remain in place, and how to reconcile messages queued during an outage.

Also decide what happens to an email that was accepted by one provider but whose event webhook was delayed. A well-designed system stores the provider message ID, the internal event ID, and a durable send-attempt state so that retries do not create duplicate customer communications.

Operational tradeoffs after the migration

Moving direct transactional email to Volanea can simplify an engineering-owned sending path: a REST endpoint, an SMTP option for compatible systems, contact and suppression controls, templates, campaigns, workflows, and email event webhooks in one email-focused platform. It can also let teams use explicit request bodies, provider-neutral business-event names, and idempotency-aware retry behavior.

Customer.io may remain the better fit for teams that depend heavily on its broader customer-engagement model: sophisticated event-driven journeys, customer profiles, multi-channel orchestration, and marketer-operated lifecycle programs. That is not a failure of migration planning; it is an architectural choice about where those responsibilities belong.

The practical goal is not to declare one platform universally better. It is to make your chosen boundary explicit, preserve recipient protections, and ensure each message is owned by the system best suited to its creation, decisioning, and delivery.

Final migration checklist

Before declaring the move complete, confirm all of the following:

  • Every Customer.io transactional call site has an owner and a destination plan.
  • Volanea production API keys are stored securely and scoped to the correct environment.
  • Every active sender domain is verified in Volanea.
  • DKIM, SPF, bounce-domain, and DMARC-related records were verified from actual received-message headers.
  • HTML and plain-text versions were tested for every migrated message.
  • Template logic was recreated or deliberately moved into application code.
  • Customer.io message IDs were replaced with durable internal message-type identifiers where useful.
  • Retry logic reuses an idempotency key for the same logical send.
  • Webhook consumers handle Volanea event payloads, authentication, retries, and duplicate deliveries.
  • Historical delivery suppressions, unsubscribes, manual blocks, and privacy-related restrictions have a documented destination policy.
  • Customer.io-specific journeys, segmentation, custom objects, and non-email channels were either retained, rebuilt, or formally deferred.
  • Feature flags, dashboards, on-call runbooks, and rollback controls are in place.
  • No production recipient can receive duplicate sends during the rollout.

FAQ

Can I migrate from Customer.io to Volanea without changing my sender domain?

Usually, you can continue using the same visible sender domain, but you must add and verify that domain in Volanea and publish the DNS records Volanea provides. Do not assume existing Customer.io authentication records satisfy a new provider’s requirements.

Can I reuse Customer.io transactional message IDs in Volanea?

No. A Customer.io transactional_message_id identifies a template in Customer.io. Treat it as source-system metadata, then create a Volanea template or render the message in your application. Maintain an internal message-type mapping during the transition if you need continuity in logs and reporting.

What is the hardest part of the migration?

Complex template behavior and large historical suppression datasets are commonly the hardest areas. Liquid syntax, layouts, fallbacks, and workflow-driven personalization need careful translation, while privacy-related or hashed suppression exports require a lawful, verified reconciliation process rather than a blind import.

Should I move campaigns and transactional emails at the same time?

Not necessarily. Start with direct transactional sends if they have clear ownership and predictable payloads. Keep complex Customer.io journeys in place until you have separately mapped their event, segmentation, consent, timing, and multi-channel behavior.

Do I need idempotency when sending through Volanea?

Yes, especially when sends originate from background jobs, payment webhooks, or any system that retries after timeouts. Use Volanea’s Idempotency-Key header with a key that stays stable for the same logical message attempt, so a retry is not treated as a new customer email.