Recurly knows when a subscription starts, renews, fails to collect, or is canceled; Volanea can deliver the customer-facing message that follows. This guide explains how to send email from Recurly with a Recurly webhook, a server-side middleware endpoint, and Volanea’s REST email API.

Recurly does not need a native email-provider marketplace app for this workflow. Its webhook system can notify an HTTPS endpoint when a billing event occurs, and your endpoint can decide whether to send an email, build the message, and call Volanea. That separation is important: billing events are not email templates, and a webhook payload should not be treated as a ready-to-send email without validation and idempotency controls.

The architecture: Recurly webhook to secure email sender

The integration has three parts:

  1. Recurly emits a webhook notification for a billing or subscription event.
  2. Your middleware receives the notification, verifies and normalizes it, records an idempotency key, and returns a successful response quickly.
  3. Volanea receives a REST API request from the middleware and delivers the transactional email.

This is a server-to-server integration. Recurly posts to an HTTPS URL you control; the browser is not involved. The middleware can be a conventional Node.js service, a serverless function, a worker, or a queue consumer. The important requirement is that it has a private runtime environment where secrets can be stored and where duplicate webhook attempts can be handled safely.

A useful mental model is that Recurly is the system of record for a billing event, while Volanea is the delivery system for a communication. The middleware is the policy layer between them. It determines which Recurly events deserve an email, which account address is eligible, which template applies, what data can be included, and whether the event has already been processed.

Why not point a Recurly webhook directly at an email API?

A direct webhook-to-email-API connection is not a good fit. Recurly webhook bodies describe notification objects, while an email API expects fields such as recipient, sender, subject, and content. A direct connection also gives you no reliable place to protect an API key, deduplicate retry attempts, choose a template by event type, or prevent accidental mail when a payload is incomplete.

The middleware is therefore not unnecessary plumbing. It is where you make the billing-to-email transition explicit and safe.

Choose the Recurly event that should start the send

For a concrete example, this guide uses Recurly’s payment_succeeded notification. That is a sensible trigger for a payment receipt, a payment-confirmed message, or an “account is current” notification. In Recurly’s webhook vocabulary, the trigger is an event notification whose event_type is payment_succeeded.

Do not substitute an event name based on what sounds plausible in your UI. Configure the event types Recurly offers for your site, then use the corresponding value delivered in the webhook body. Recurly has notification types for account, subscription, invoice, and payment activity, and the available information in the payload depends on the event that occurred.

Examples of email policies you might implement include:

  • payment_succeeded: send a receipt or payment-confirmation email.
  • payment_failed: send a payment-problem notice with a link to update billing details.
  • new_subscription: send a subscription welcome or plan-confirmation email.
  • renewed_subscription: send a renewal confirmation where your customer experience calls for one.
  • canceled_subscription: send a cancellation confirmation and any required access-end information.

Avoid sending a generic email for every webhook. A single customer action can create related invoice, payment, and subscription notifications. If you subscribe to several event types without a communication policy, a successful renewal could generate multiple messages that say nearly the same thing.

Start with one event and one template

A low-risk first implementation is one payment_succeeded notification and one narrowly scoped receipt template. It gives you a recipient address from the account context, a recognizable test action in Recurly, and a clear business outcome.

Once that works, expand with deliberate rules. For example, a payment_failed email may need different suppression logic than a receipt, because several attempts can occur for the same invoice. A cancellation message may need a different sender identity and a careful distinction between cancellation requested, term ended, and access actually removed.

Configure the Recurly webhook endpoint

In Recurly, create a webhook endpoint for a public HTTPS route owned by your middleware, such as:

https://billing-mail.example.com/webhooks/recurly

Select payment_succeeded as the notification event for the first version of the integration. Recurly will POST an event notification to that URL when the selected event occurs. Keep the route dedicated to Recurly rather than mixing it with browser-facing form handlers or unrelated provider callbacks.

Your endpoint needs to be reachable from the public internet and must use HTTPS. It should return a 2xx response only after it has durably accepted the event for processing. In a production design, that usually means writing an event row or queue message before responding, not waiting for email delivery to finish.

If your Recurly webhook endpoint configuration provides a signing or authentication option, enable it and verify the incoming request according to Recurly’s current webhook documentation. Do not rely on a secret-looking URL as the only control. A path such as /webhooks/recurly-4f8a... can reduce random noise, but it is not a substitute for request verification, network controls, and idempotency.

The Volanea key does not belong in Recurly

The Volanea API key lives in the middleware’s server-side secret store, for example as VOLANEA_API_KEY in a deployment environment, secret manager, or encrypted function configuration. It does not go in a customer-facing JavaScript bundle, a Recurly hosted-page customization, a checkout page, a URL query string, or a field intended for public configuration.

Recurly is sending an event to your endpoint, not acting as a secure general-purpose secret vault for your email provider. Your code adds the Authorization header only after it has accepted and validated the Recurly event. This keeps the Volanea credential out of the webhook configuration and lets you rotate it without changing client code or exposing it to users.

Use a key with only the sending permissions the service needs. Store sender addresses, template identifiers, and production-versus-test configuration separately from the key. Keep sandbox and production environments separate as well: a test Recurly site should not unexpectedly send mail from a production domain.

Understand the Recurly webhook payload before mapping fields

Recurly webhook notifications are JSON objects. The notification identifies the event with event_type and includes resource information appropriate to the event, such as an account, invoice, payment, or subscription object. The exact set of nested objects and fields is event-specific, so code should treat optional fields as optional rather than assuming a subscription or invoice is always present.

A representative payment_succeeded notification has this general shape:

{
  "id": "0fca9e7f-5d89-4fbd-9a8b-0f5599e896cb",
  "object_type": "notification",
  "event_type": "payment_succeeded",
  "site_id": "your-recurly-site-id",
  "account": {
    "object_type": "account",
    "id": "4f61f01a-57a8-4cf7-8bae-8f2c025c9ddd",
    "code": "acct_001",
    "email": "customer@example.com"
  },
  "invoice": {
    "object_type": "invoice",
    "number": "1001",
    "currency": "USD",
    "total_in_cents": 2900
  },
  "payment": {
    "object_type": "payment",
    "id": "a-payment-id"
  }
}

Treat this as an event-oriented shape, not a contract that every event will contain every example field. For example, an account-level event may have no invoice. A subscription-oriented event may have subscription details relevant to a plan confirmation. Before enabling any email, inspect actual test notifications from the event type you selected and compare them with Recurly’s current webhook reference.

The basic mapping for a payment-success email is straightforward:

Recurly notification fieldVolanea email fieldPurpose
account.emailtoCustomer recipient address
fixed verified addressfromYour approved sending identity
derived in middlewaresubjectHuman-readable transaction subject
invoice.number, invoice.total_in_cents, invoice.currencyHTML/text contentReceipt context
ididempotency recordPrevent sends from webhook retries
event_typetemplate selection or message metadataChoose the correct email policy

The recipient is not safe merely because it appears in a billing payload. Validate it before sending. At a minimum, ensure it is a non-empty address and that it belongs to an account record you expect. If your product has separate communication-consent rules, apply them here. Transactional receipts may have a different legal basis and suppression policy than marketing email, but neither should be sent to malformed addresses or to addresses your own system has explicitly blocked.

For a preflight check in operational tooling, use the email address verification tool rather than discovering a bad address only through delivery failures. Do not call a verifier synchronously in the webhook request path, however; that adds latency and can make retries more likely.

Working Node.js middleware and Volanea REST call

The following Express example accepts a Recurly notification, handles only payment_succeeded, uses the notification id as the durable idempotency key, and posts a message to Volanea. It deliberately acknowledges Recurly after placing the event in a processing path; the in-memory Set is included only to make the code readable and must be replaced by a database table, Redis record, or queue-backed worker in production.

import express from "express";

const app = express();
app.use(express.json({ type: "application/json" }));

const VOLANEA_API_KEY = process.env.VOLANEA_API_KEY;
const VOLANEA_FROM = process.env.VOLANEA_FROM;
const processedEvents = new Set(); // Replace with durable storage in production.

function moneyFromCents(cents, currency = "USD") {
  if (!Number.isInteger(cents)) return "";
  return new Intl.NumberFormat("en-US", {
    style: "currency",
    currency
  }).format(cents / 100);
}

function escapeHtml(value = "") {
  return String(value)
    .replaceAll("&", "&")
    .replaceAll("<", "&lt;")
    .replaceAll(">", "&gt;")
    .replaceAll('"', "&quot;")
    .replaceAll("'", "&#039;");
}

async function sendWithVolanea({ to, subject, html, text }) {
  const response = await fetch("https://api.volanea.com/v1/emails", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${VOLANEA_API_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      from: VOLANEA_FROM,
      to: [to],
      subject,
      html,
      text
    })
  });

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

  return response.json();
}

app.post("/webhooks/recurly", async (req, res) => {
  // Verify Recurly's configured webhook authentication/signature here,
  // before accepting the payload. Keep the raw body if the verification
  // method requires it.
  const event = req.body;

  if (event?.object_type !== "notification" || !event?.id) {
    return res.status(400).json({ error: "Invalid Recurly notification" });
  }

  // An acknowledged but irrelevant event is not an error.
  if (event.event_type !== "payment_succeeded") {
    return res.status(204).end();
  }

  const eventId = event.id;
  if (processedEvents.has(eventId)) {
    return res.status(204).end();
  }

  const email = event.account?.email?.trim();
  if (!email || !email.includes("@")) {
    // Record this for investigation instead of repeatedly attempting a send.
    return res.status(422).json({ error: "No usable account email" });
  }

  processedEvents.add(eventId);

  try {
    const invoiceNumber = event.invoice?.number || "your recent payment";
    const amount = moneyFromCents(
      event.invoice?.total_in_cents,
      event.invoice?.currency
    );
    const accountCode = event.account?.code || "";
    const subject = `Payment received${amount ? `: ${amount}` : ""}`;
    const html = `
      <p>Hello,</p>
      <p>We received your payment${amount ? ` of <strong>${escapeHtml(amount)}</strong>` : ""}.</p>
      <p>Invoice: ${escapeHtml(invoiceNumber)}${accountCode ? `<br>Account: ${escapeHtml(accountCode)}` : ""}</p>
      <p>Thank you.</p>`;
    const text = `We received your payment${amount ? ` of ${amount}` : ""}. Invoice: ${invoiceNumber}${accountCode ? `. Account: ${accountCode}` : ""}.`;

    await sendWithVolanea({ to: email, subject, html, text });
    return res.status(204).end();
  } catch (error) {
    processedEvents.delete(eventId); // A durable implementation marks retry state instead.
    console.error({ eventId, error: error.message }, "Email send failed");
    return res.status(500).json({ error: "Email processing failed" });
  }
});

app.listen(process.env.PORT || 3000);

The from, to, subject, html, and text fields shown above are the email-message mapping. Configure VOLANEA_FROM as a sender identity on a domain you control and authenticate that domain before production traffic. Consult the Volanea API reference and setup guides for the current sending endpoint, authentication requirements, sender setup, and any optional fields such as tags or reply-to addresses.

Improve the example before production

The example’s Set disappears on a restart and is not shared across instances. In production, insert the Recurly notification ID into a database with a unique constraint before sending. A typical table has recurly_event_id, event_type, received_at, send_status, volanea_message_id, and last_error columns. The unique constraint makes duplicate delivery attempts harmless.

For higher volume, split receipt from delivery. The HTTP endpoint verifies and stores the event, places a job on a queue, then returns a 2xx response. A worker processes the job and records the Volanea response. This shortens the webhook request, isolates temporary email-provider errors, and gives you a place to retry outbound sends without asking Recurly to resend the original event.

Build message content from billing data carefully

A payment notification is often used for receipts, but it is not necessarily a full tax invoice or a substitute for the invoice view your product presents to customers. Decide what the email should confirm and link customers to the authoritative document or account portal for the complete record.

Use only the fields needed for the message. Invoice number, amount, currency, account code, and a secure account link are usually sufficient. Do not put payment instrument details, internal risk indicators, API identifiers, or full billing addresses into the email merely because a billing event may contain related data.

Escape values and keep a text version

Webhook fields originate outside your email template. Even if an account code or plan name comes from a trusted workflow today, escape it before inserting it into HTML. The example escapes invoice and account values for that reason.

Send both HTML and plain text where possible. The text version helps recipients using text-only clients and provides a readable fallback when HTML is blocked. It also forces a useful editorial check: if the essential meaning cannot be communicated in a short text message, the HTML design may be carrying too much transactional detail.

Separate transactional and campaign logic

A payment_succeeded message is transactional. It should not silently become a promotional campaign simply because it includes an opportunity to upgrade. If you add marketing material, make the transactional confirmation complete without it, honor applicable consent and suppression rules, and keep the promotional component governed by your campaign policy.

This distinction also helps operationally. Receipt-like traffic benefits from predictable, event-driven sending and a stable sender identity. Marketing traffic has different frequency, audience, unsubscribe, and engagement considerations.

When this breaks: retries, timeouts, and incomplete payloads

The difficult part of this integration is not the initial POST. It is handling the failure modes around a distributed handoff. Recurly, your middleware, and Volanea each have independent availability and retry behavior.

Recurly retries can create duplicate send attempts

A webhook delivery can be retried when Recurly does not receive a successful response. That can happen if your handler returns an error, crashes after Volanea accepts the email but before your handler responds, or takes too long to respond. The same Recurly notification may therefore reach your endpoint more than once.

Use notification.id as the primary idempotency key. Store it durably with a unique database constraint before sending. If it is already present and marked complete, return a 2xx response without sending again. If it is present but processing is incomplete, let a worker inspect the state rather than sending blindly.

There is an unavoidable edge case: Volanea can accept a message, then your process can fail before it records success. If your email API supports an idempotency mechanism, use a stable key derived from the Recurly notification ID as well. If it does not, record an outbound attempt before sending and use a recovery workflow that favors investigation over automatic duplicate customer mail.

Webhook timeouts turn slow work into retry pressure

Do not render large documents, call several third-party services, or wait for a slow email API response in the webhook request if you can avoid it. A slow endpoint risks a delivery timeout or failed webhook attempt, which increases duplicate pressure.

The preferred pattern is: validate the notification, write an idempotent event record or queue job, return success, then send from a worker. If you keep synchronous sending for a small deployment, enforce a short internal timeout, log the exact outcome, and migrate to a queue before traffic grows.

Payload fields vary by Recurly event

An account.email may be absent, blank, or unsuitable for your policy. An invoice-related property may not exist on a subscription notification. A payment field should not be assumed when processing an account event. Code should select a template only when the fields that template needs are present.

For example, require account.email for every send, require invoice.number only for an invoice receipt template, and use a generic confirmation template if a nonessential field is unavailable. If a mandatory field is missing, record the event as needs_review, return a response strategy appropriate to your queue design, and alert an operator rather than creating an email with misleading placeholders.

Delivery success is different from webhook success

A 2xx response from your endpoint means you accepted the Recurly notification. A successful Volanea API response means the provider accepted the message for processing. Neither alone guarantees that a recipient has read the email, and a provider acceptance is not the same as mailbox placement.

Log identifiers at each stage: Recurly notification ID, internal job ID, Volanea message ID, account code, recipient domain, selected template, and final processing state. These records make it possible to answer whether a customer complaint is a missing Recurly event, a blocked middleware job, a rejected send, or a downstream delivery issue.

Test the integration without emailing real customers

Use a test or sandbox workflow in Recurly and a non-customer inbox under your control. Trigger the exact event you configured, such as a successful test payment, and preserve the received JSON payload as a fixture. That fixture becomes the basis for repeatable automated tests.

A practical test sequence is:

  1. Send a known payment_succeeded test notification and confirm the handler records the notification ID.
  2. Confirm the field mapping produces the expected recipient, invoice number, currency, subject, HTML, and text.
  3. Replay the identical payload and verify that no second Volanea request is made.
  4. Remove invoice from a copied fixture and confirm the fallback or review path works as designed.
  5. Simulate a Volanea non-2xx response and confirm the event is retried by your worker without losing its idempotency state.
  6. Check the received email in at least one major mailbox and confirm sender identity, links, formatting, and reply handling.

Do not use a real customer’s address as a casual test destination. Billing messages can be confusing, and test mail from a production sender can damage trust. Test with a separate domain or a clearly labeled environment where possible.

Deliverability and sender-domain considerations

Billing messages are high-value transactional email. A receipt that lands in spam creates support work; a payment-failure notice that is missed can lead to involuntary churn. Authenticate the sending domain and use a recognizable sender name before switching the workflow on.

Keep sender identities consistent. If receipts come from one address, payment-failure notices from another, and cancellation confirmations from a third unrelated domain, recipients and mailbox providers have less continuity to work with. Consistency does not mean every message needs the same mailbox, but it should mean the domains, display names, and reply paths make sense together.

Keep copy precise. “Payment received” should only be sent after the specific Recurly event your policy defines as payment success. Do not call something a receipt if it is only a pending authorization. Similarly, do not promise restored service in an automated payment message unless the corresponding entitlement change has actually happened.

Operational rules for a reliable billing-email pipeline

A mature implementation benefits from a small set of explicit rules. Write them down before adding more event types:

  • One Recurly notification ID can result in no more than one intended transactional email of a given type.
  • The webhook endpoint accepts events quickly; slow work runs asynchronously.
  • A missing required recipient or template field creates an observable exception, not a malformed email.
  • The Volanea key exists only in server-side secret storage and is rotated through a controlled deployment process.
  • Template selection is based on an allowlist of Recurly event_type values, not arbitrary payload text.
  • Logs avoid storing more personal or payment-related data than necessary.
  • Every production send can be traced from the email provider response back to the source notification ID.

These rules have second-order benefits. They make support investigations faster, reduce the chance that a replay tool mails people twice, and make a future move from synchronous code to a queue much less risky. They also let engineering and finance teams agree on what a particular event means before an email makes that meaning visible to a customer.

Alternatives to custom middleware

A low-code automation platform can be useful for a proof of concept: Recurly posts a webhook, the automation maps fields, and an HTTP action calls an email service. That approach can be acceptable for low-volume internal notifications, provided the automation platform can keep the Volanea key secret and has a credible way to prevent duplicates.

For customer-facing payment receipts, a small owned middleware service is usually the stronger choice. It gives you durable idempotency, source-control review, reliable queues, clear audit records, and complete control over what happens when an outbound API call fails. It also avoids coupling important billing communication to opaque field transformations in a third-party workflow editor.

If you use an automation tool temporarily, retain the same design principles: trigger on a defined Recurly event type, store a unique event ID, avoid putting credentials in exposed configuration, and test duplicate delivery. The convenience of visual mapping does not remove the distributed-systems failure modes.

A practical rollout plan

Start narrowly, measure, then extend. The following rollout keeps customer impact controlled:

  1. Configure one Recurly webhook endpoint for a non-production environment and subscribe only to payment_succeeded.
  2. Deploy middleware with request verification, durable event storage, and logging before adding email content.
  3. Capture real test payloads and write fixture tests for normal, duplicate, and missing-field cases.
  4. Authenticate the Volanea sending domain and use a clearly transactional sender identity.
  5. Enable the receipt template for internal test addresses, then a small controlled production cohort if your process permits.
  6. Monitor webhook failures, queue age, Volanea API errors, bounce signals, and support tickets.
  7. Add one new event type at a time, each with its own template and idempotency policy.

The goal is not simply to make an API request succeed. The goal is to ensure that every billing-triggered email is accurate, sent once, traceable, and resilient when a service is slow or temporarily unavailable.

Conclusion

To send email from Recurly with Volanea, use Recurly’s webhook notifications as the event source and a server-side middleware endpoint as the secure integration layer. Trigger on a specific notification such as payment_succeeded, map the account and invoice data into a deliberately designed transactional message, and send it with the Volanea REST API from a private runtime environment.

The production safeguards matter as much as the mapping: store the Volanea key server-side, verify incoming webhook requests, acknowledge events quickly, use the Recurly notification ID to stop duplicate sends, and design for optional payload fields. With those pieces in place, the workflow can grow from a single payment confirmation into a dependable billing-communication pipeline.

FAQ

Can Recurly send directly to Volanea without middleware?

Recurly webhooks notify an HTTPS endpoint with a billing-event payload; they do not convert that payload into a Volanea email request for you. Use middleware to protect the API key, map fields, choose templates, and prevent duplicate sends.

Which Recurly event should I use for a payment receipt?

Start with the payment_succeeded webhook notification if your policy is to send confirmation after a successful payment. Inspect actual test payloads and make sure the invoice information your template requires is present.

Where should the Volanea API key be stored?

Store it as a server-side secret in the middleware runtime, such as an environment secret or secret manager entry. Never expose it in browser code, public configuration, hosted checkout customizations, or a webhook URL.

How do I stop duplicate emails when Recurly retries a webhook?

Use the Recurly notification id as a durable idempotency key. Insert it under a unique constraint before sending, and return a successful response without another send when the same notification arrives again.

What happens if a webhook has no customer email or invoice number?

Do not send a guessed message. Require account.email for customer mail, make event-specific fields optional only where the template supports a fallback, and route incomplete records to logs or a review queue for investigation.