Send email from Systeme.io with Volanea by connecting Systeme.io’s outbound webhook service to a small server-side relay that calls the Volanea REST API. This is not a native Systeme.io app or marketplace installation: Systeme.io sends the event, your relay makes the authenticated email API request, and Volanea sends the message.

That architecture is deliberate. A Systeme.io webhook carries event data such as an opt-in, contact, or sale to an HTTPS URL. Your relay validates the event, maps its fields to an email, assigns an idempotency key, and keeps the Volanea API key outside of browser-visible or form-visible configuration.

What this Systeme.io and Volanea integration does

Systeme.io provides outbound webhooks that can notify an HTTPS endpoint when account events occur. Its account-level webhook service supports events including Contact created, Contact tag added, Contact tag removed, Opt-In, New sale, and Sale canceled. For a funnel-specific flow, Systeme.io automation rules and workflows can also send the trigger data as JSON to a webhook destination.

Volanea provides the email-sending half of the flow. Your relay sends a JSON request to POST https://api.volanea.com/v1/send, authenticated with a Bearer API key. The send request contains the sender, recipient, subject, plain-text content, HTML content, and an Idempotency-Key header that makes retried webhook deliveries safe.

The full path looks like this:

  1. A visitor submits a Systeme.io opt-in form.
  2. Systeme.io records the subscription and emits an Opt-In webhook event.
  3. Systeme.io POSTs JSON to an HTTPS route that you control.
  4. The route checks that the event is one you expect and extracts the recipient address.
  5. The route calls Volanea’s REST API using a server-only API key.
  6. Volanea accepts the transactional message and processes delivery from your verified sending domain.

This is a useful pattern for welcome emails, lead-magnet delivery notices, purchase receipts, enrollment confirmations, onboarding instructions, account-team notifications, and internal alerts. It also keeps the responsibility boundaries clear: Systeme.io remains the funnel and event source; Volanea is the sending platform; your relay is the secure adapter between them.

Why a server-side relay is required

There is no native Volanea listing, plugin, or marketplace app to install in Systeme.io. More importantly, the direct API design would be unsafe even if a generic form action let you make arbitrary HTTP requests with custom headers.

Volanea authenticates email sends with a secret API key in the Authorization: Bearer ... header. That key can authorize mail from your account, so it must never appear in:

  • Funnel-page JavaScript or an embedded form.
  • A public page’s HTML source.
  • A redirect URL or query string.
  • A client-side tag manager variable.
  • Browser logs, screenshots, or shared automation exports.
  • A webhook destination configuration if that configuration cannot keep secrets server-side and avoid logging them.

Instead, store VOLANEA_API_KEY in the secret manager or encrypted environment-variable configuration of the service that receives the Systeme.io webhook. Common choices are a small Node service, a serverless function, a Cloudflare Worker, or an existing application backend.

The Systeme.io side should contain only the webhook URL. That URL points to your relay, not directly to Volanea. The relay is the only component that sees the Volanea API key.

This also gives you controls that a direct no-code HTTP action usually cannot provide reliably: event allowlisting, validation, idempotency, normalized field handling, observability, rate controls, recipient rules, and a durable audit trail.

For the REST endpoint, authentication, and send request details, keep the implementation aligned with the Volanea email API reference and setup guides.

Choose the right Systeme.io trigger

For this guide, the concrete trigger is Systeme.io’s Opt-In webhook event. Systeme.io describes this event as firing when a contact subscribes through an opt-in form. It is the cleanest choice for a lead-confirmation or welcome email because it represents an explicit funnel form subscription rather than every possible creation of a contact record.

Use Opt-In for funnel lead emails

Choose Opt-In when your email should be sent after someone subscribes through a funnel opt-in page. Examples include:

  • “Here is your guide” after a lead submits a download form.
  • “Thanks for registering” after webinar registration.
  • A transactional confirmation that the form was received.
  • An internal notification to a sales or support address.

At the account level, create a webhook and select the Opt-In event. Systeme.io’s configuration flow is: profile picture, Settings, Webhooks, then Create. You enter a name, the HTTPS webhook URL, a webhook secret key, activate the webhook, select the events, and save.

If your flow belongs only to one funnel step, you can alternatively add an automation rule on that funnel’s opt-in page and choose a webhook action. Systeme.io also documents a workflow route: go to Automations, choose Workflows, create a workflow, and use a webhook destination after the appropriate workflow trigger.

When to use other events instead

The account-level webhook service is broader than a simple form-submit hook. Select the event that represents the business fact your email needs to confirm:

Systeme.io eventGood use caseRecipient source to expect
Opt-InLead confirmation, delivery notice, onboarding startdata.contact.email
Contact createdInternal notification or initial record workflowContact data, depending on event payload
Contact tag addedSend a message when a qualified lead enters a segmentContact data plus tag context
New saleReceipt, purchase confirmation, access instructionsdata.customer.email
Sale canceledCancellation acknowledgement or internal follow-upCustomer/order data

Avoid treating a contact-created event as identical to an opt-in. A contact can be created by an import, an API call, another automation, or a form. If your promise to the visitor is tied to a specific funnel form, the Opt-In event is the more precise trigger.

The real Systeme.io Opt-In payload and field mapping

Systeme.io documents the funnel-step form subscription payload as JSON with the event type contact.optin.completed. The important parts for a basic welcome email are the recipient email at data.contact.email, the unique contact ID at data.contact.id, the funnel-step metadata at data.funnel_step, and the event timestamp at created_at.

A representative Opt-In payload has this shape:

{
  "type": "contact.optin.completed",
  "data": {
    "funnel_step": {
      "id": 2102831,
      "name": "Squeeze page",
      "type": "squeeze",
      "funnel": {
        "id": 614554,
        "name": "webhook data structure documentation"
      }
    },
    "contact": {
      "id": 29142804,
      "email": "person@example.com",
      "fields": [],
      "ip": "31.173.103.109",
      "referred_by_contact_id": null,
      "referred_by_contact_email": null
    }
  },
  "account": {
    "email": "account-owner@example.com"
  },
  "created_at": "2021-10-05T08:10:29+00:00"
}

Do not build your message around assumptions that are absent from this contract. In particular, the documented Opt-In example shows fields as an empty array. A first name, phone number, country, or custom field may not be present merely because you expect it on the page. Build a useful fallback email that only requires data.contact.email, then enhance the mapping only after inspecting a real webhook log from your own funnel.

Field mapping for the example email

The relay below applies this mapping:

Systeme.io fieldVolanea send fieldWhy it is used
data.contact.emailtoThe opt-in contact receives the message.
Static verified senderfromSender identity should come from your authenticated sending domain, not form data.
Static subjectsubjectThe message purpose is known from the selected event.
data.funnel_step.nametext and htmlAdds context without relying on optional custom fields.
data.funnel_step.funnel.nametext and htmlIdentifies the source funnel for operational clarity.
data.contact.id + created_atIdempotency-KeyCreates a stable identifier for this event’s email.

The sender should be an address on a domain you have verified in Volanea. Never set from to the contact’s submitted email address. If you need replies to reach a support inbox, use a sender address that you control and configure a suitable reply workflow in your sending setup.

Build the secure webhook relay

The following implementation uses Node.js’s built-in HTTP server and fetch, so it does not require an Express or Fastify dependency. It receives Systeme.io’s JSON, accepts only the documented Opt-In event used in this guide, validates the recipient shape, and calls Volanea.

Create three server-side secrets in your host’s environment configuration:

VOLANEA_API_KEY=sk_live_replace_with_your_key
EMAIL_FROM=Support <support@your-verified-domain.example>
SYSTEME_WEBHOOK_TOKEN=replace-with-a-long-random-value

Use a long random value for SYSTEME_WEBHOOK_TOKEN. Put it in the webhook URL path, not in browser code. For example:

https://relay.your-domain.example/webhooks/systeme/replace-with-a-long-random-value

Then add that HTTPS URL to Systeme.io under Settings → Webhooks → Create, select Opt-In, and activate the webhook. Systeme.io requires an HTTPS endpoint for the webhook destination.

Working Node.js relay and Volanea API call

// server.mjs
import http from "node:http";
import { URL } from "node:url";

const {
  VOLANEA_API_KEY,
  EMAIL_FROM,
  SYSTEME_WEBHOOK_TOKEN,
  PORT = "3000"
} = process.env;

if (!VOLANEA_API_KEY || !EMAIL_FROM || !SYSTEME_WEBHOOK_TOKEN) {
  throw new Error("Missing VOLANEA_API_KEY, EMAIL_FROM, or SYSTEME_WEBHOOK_TOKEN");
}

function readJson(request) {
  return new Promise((resolve, reject) => {
    let body = "";

    request.on("data", (chunk) => {
      body += chunk;
      if (body.length > 1_000_000) {
        reject(new Error("Webhook body too large"));
        request.destroy();
      }
    });

    request.on("end", () => {
      try {
        resolve(JSON.parse(body));
      } catch {
        reject(new Error("Invalid JSON"));
      }
    });

    request.on("error", reject);
  });
}

function sendJson(response, status, value) {
  response.writeHead(status, { "content-type": "application/json" });
  response.end(JSON.stringify(value));
}

const server = http.createServer(async (request, response) => {
  const url = new URL(request.url, `http://${request.headers.host}`);
  const expectedPath = `/webhooks/systeme/${SYSTEME_WEBHOOK_TOKEN}`;

  if (request.method !== "POST" || url.pathname !== expectedPath) {
    return sendJson(response, 404, { error: "Not found" });
  }

  try {
    const event = await readJson(request);

    // Concrete Systeme.io trigger handled by this relay.
    if (event.type !== "contact.optin.completed") {
      return sendJson(response, 202, { ignored: true, type: event.type });
    }

    const email = event?.data?.contact?.email;
    const contactId = event?.data?.contact?.id;
    const createdAt = event?.created_at;
    const stepName = event?.data?.funnel_step?.name ?? "your form";
    const funnelName = event?.data?.funnel_step?.funnel?.name ?? "your funnel";

    if (typeof email !== "string" || !email.includes("@")) {
      return sendJson(response, 400, { error: "Missing valid data.contact.email" });
    }

    if (!contactId || !createdAt) {
      return sendJson(response, 400, {
        error: "Missing data.contact.id or created_at for idempotency"
      });
    }

    // Stable across Systeme.io retries for this specific opt-in event.
    const idempotencyKey = `systeme-optin:${contactId}:${createdAt}`;

    const text = [
      "Thanks for signing up.",
      "",
      `We received your subscription through ${stepName} in ${funnelName}.`,
      "You can safely keep this message as your confirmation."
    ].join("\n");

    const html = `
      <p>Thanks for signing up.</p>
      <p>We received your subscription through <strong>${escapeHtml(stepName)}</strong>
      in <strong>${escapeHtml(funnelName)}</strong>.</p>
      <p>You can safely keep this message as your confirmation.</p>
    `;

    const volaneaResponse = await fetch("https://api.volanea.com/v1/send", {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${VOLANEA_API_KEY}`,
        "Content-Type": "application/json",
        "Idempotency-Key": idempotencyKey
      },
      body: JSON.stringify({
        from: EMAIL_FROM,
        to: email,
        subject: "Thanks for signing up",
        text,
        html
      })
    });

    const result = await volaneaResponse.json().catch(() => ({}));

    if (!volaneaResponse.ok) {
      console.error("Volanea send failed", {
        status: volaneaResponse.status,
        result,
        idempotencyKey
      });
      return sendJson(response, 502, { error: "Email send failed" });
    }

    console.info("Volanea send accepted", {
      contactId,
      email,
      idempotencyKey,
      result
    });

    return sendJson(response, 200, { ok: true });
  } catch (error) {
    console.error("Systeme.io webhook error", error);
    return sendJson(response, 400, { error: "Invalid webhook request" });
  }
});

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

server.listen(Number(PORT), () => {
  console.log(`Listening on port ${PORT}`);
});

The code intentionally returns 200 only after Volanea has accepted the API request. If Volanea returns an error, the relay returns a non-success response. That gives Systeme.io a chance to retry its webhook delivery rather than silently treating an unsent email as successful.

Keep API keys and webhook credentials separate

This integration has two different secret categories, and confusing them creates security problems.

The Volanea API key

VOLANEA_API_KEY is a sending credential. It belongs only in your relay’s environment or secret store. The relay uses it to set:

Authorization: Bearer sk_...

It does not live in Systeme.io. It does not belong in a funnel, custom JavaScript block, webhook URL query parameter, CRM custom field, or exported workflow description.

A leak can allow an attacker to send email under your account. If you suspect exposure, rotate the credential, update the relay secret, redeploy, and check your Volanea sending activity.

The Systeme.io webhook secret and destination URL

Systeme.io’s webhook configuration includes a webhook secret key. Store that value as confidential operational configuration too. The exact verification method should follow Systeme.io’s current webhook-delivery contract and the headers visible in an actual delivery log; do not guess a signature-header name or copy a verifier intended for another provider.

The example relay also uses a long random route token. It is an additional access-control layer, not a substitute for proper webhook verification where your account’s delivery contract supports it. Put your relay behind HTTPS, restrict access where your infrastructure supports it, and consider allowing only Systeme.io’s documented webhook source addresses after confirming that your proxy preserves the real client IP.

Systeme.io documents these webhook source IP addresses: 185.236.142.1, 185.236.142.2, and 185.236.142.3. IP allowlisting can reduce unsolicited traffic, but it should be carefully tested with your hosting provider and reverse proxy configuration.

Prevent duplicate emails with idempotency

Webhook delivery is not the same as exactly-once business processing. A webhook provider may retry after a connection failure, a slow response, or an error response. Your receiver can also complete the Volanea send but lose its response before returning success to Systeme.io. In that situation, Systeme.io may retry even though the email request already reached Volanea.

That is why the relay sends this header:

Idempotency-Key: systeme-optin:<contact-id>:<created-at>

The key is derived from the event identity, not a random value generated per HTTP request. If the same Systeme.io Opt-In event is delivered again, the key remains the same. Volanea can recognize that it refers to the same send intent rather than a new welcome email.

What not to use as an idempotency key

Avoid keys based only on:

  • The contact email address, because the same person may legitimately opt in again later.
  • The current server timestamp, because each retry would create a different key.
  • A random UUID generated by the relay, because it cannot connect repeated deliveries to the original event.
  • The funnel ID alone, because many contacts can opt in to the same funnel.

For the documented Opt-In payload, combining data.contact.id and created_at gives the integration a stable event-specific key. For a sale flow, use the durable order identifier exposed by the sale event, not merely the customer email.

Test the integration before publishing it

Treat the first setup as an integration test, not as production automation. Use a controlled inbox you can inspect and a dedicated test funnel step where possible.

Test checklist

  1. Verify the EMAIL_FROM address and domain in Volanea before invoking the relay.
  2. Deploy the relay with VOLANEA_API_KEY, EMAIL_FROM, and SYSTEME_WEBHOOK_TOKEN configured as server-side secrets.
  3. Create the Systeme.io webhook in Settings → Webhooks with the relay’s HTTPS URL and select Opt-In.
  4. Submit the specific opt-in page using a test email address.
  5. Open the Systeme.io webhook logs and inspect the event type, HTTP result, and payload details.
  6. Confirm the relay logged an accepted Volanea send without writing secret values to logs.
  7. Check the message headers and content in the test mailbox.
  8. Submit the same test carefully again only when you intend to create a new opt-in event; verify that an actual retry of the original event does not create another message.

Systeme.io provides webhook logs from the three-dot menu next to the webhook through View logs. Use those logs to diagnose the source event before changing the relay blindly.

When testing field mapping, do not assume your custom contact information arrives in the same structure as a sample payload. Inspect your real delivery. If your workflow needs a first name but the Opt-In payload does not contain one, either revise the email copy so it does not require it or use a controlled server-side lookup pattern that fetches the approved contact data from the appropriate source.

When this breaks: failures specific to this hop

The Systeme.io-to-relay-to-Volanea hop has predictable failure modes. Designing for them up front is much easier than explaining a duplicate welcome email or missing receipt after launch.

Systeme.io retries create duplicate sends

Systeme.io retries failed webhook delivery attempts over several days. That is useful when your relay is temporarily unavailable, but it means your endpoint must be idempotent.

Symptom: A subscriber receives two or more copies of the same confirmation.

Likely cause: The relay sent the Volanea request, but returned a timeout, disconnect, or non-2xx status before Systeme.io recorded success. The next delivery attempted the send again with a new random key or no idempotency key.

Fix: Use a stable Idempotency-Key derived from the Systeme.io event, as shown in the code. Log the key, contact ID, event type, and Volanea response status. Never generate a fresh idempotency key for each retry.

Webhook timeout or slow server response

Symptom: Systeme.io logs show failures or repeated delivery attempts while Volanea shows intermittent accepted sends.

Likely cause: Cold starts, a slow database lookup, DNS trouble, or a network stall caused the receiver to respond too slowly. Even a successful downstream call is not useful if the webhook sender does not receive the response.

Fix: Keep the initial handler narrow. Parse, validate, derive the idempotency key, call Volanea with a deliberate timeout policy at your hosting layer, and return a small response. For more complex tasks—PDF generation, CRM enrichment, multi-step personalization—store a durable job record keyed by the event identity and acknowledge the webhook after the record is safely persisted.

Do not respond with 200 before you have a durable plan for the send. A fast success response followed by an in-memory background task can lose emails if the process exits immediately afterward.

Payload fields are missing or shaped differently

Symptom: The email renders “Hello undefined,” includes an empty offer name, or the relay returns a validation error.

Likely cause: The payload differs by event. The documented Opt-In example includes data.contact.email and an empty fields array, while a new-sale payload uses data.customer.email and includes customer fields. Optional form or contact data may also be absent depending on the event, the form, the account configuration, or the plan and feature limits available to that Systeme.io account.

Fix: Require only fields guaranteed by the event you selected. For this Opt-In integration, the hard requirement is data.contact.email; funnel metadata is optional and has fallback text. Inspect webhook logs for your account and test each relevant funnel. If a message truly requires a custom field, reject missing data into a monitored queue rather than sending malformed content.

Also review Systeme.io plan limits before designing a high-volume or highly segmented workflow. Its plans limit items such as automation rules, workflows, tags, funnels, and contacts at different tiers. A missing automation path can look like a payload problem when the trigger never ran in the first place.

The Volanea API key is exposed

Symptom: Unrecognized sends, a key found in a repository, or a support ticket includes a full secret.

Fix: Rotate the Volanea key immediately, update only the relay’s server-side secret, redeploy, and remove the leaked value from repositories, build logs, screenshots, and shared documents. The Systeme.io webhook should continue to call your relay; it should never need the Volanea credential.

The wrong Systeme.io event triggers the message

Symptom: An imported lead receives a funnel welcome message, or a cancelled sale sends a receipt.

Fix: Restrict the relay to the exact event type it is designed to process. The example ignores anything except contact.optin.completed. Build a separate handler and a separate message contract for sale events rather than loosely accepting every incoming payload.

Extending the pattern to sales and tagged contacts

Once the Opt-In flow works, the same relay can support other Systeme.io business events, but each should be treated as its own contract.

For a New sale event, Systeme.io documents the event type as customer.sale.completed. The recipient is under data.customer.email, rather than the Opt-In event’s data.contact.email. The sale payload can include funnel-step information, price-plan information, order data, and customer fields. A receipt email should use a sale-specific subject, template, sender policy, and idempotency key based on the order identifier.

For a Contact tag added event, be conservative. A tag is often an internal classification rather than user-facing consent. Decide whether the message is transactional, operational, or marketing before sending it. If it is marketing content, ensure your process honors the recipient’s consent status, unsubscribe requirements, and local obligations. Do not turn every CRM tag into a reason to send mail.

For reusable production content, move the inline html and text strings into tested templates once the mapping stabilizes. Keep the event validation and idempotency logic in the relay; a template does not replace the need to decide whether an event should produce a message.

Operational practices that keep the flow reliable

The integration is small, but it sits between lead capture and a customer-facing email. A few operational rules have outsized impact.

  • Log identifiers, not secrets. Record the event type, contact or order ID, idempotency key, response status, and request duration. Do not log the Volanea API key, webhook token, or unnecessary personal data.
  • Use one purpose per endpoint or handler. An Opt-In welcome handler is easier to reason about than a single route with dozens of loosely defined branches.
  • Validate email before sending. At minimum, reject empty or clearly malformed values. For list hygiene before campaigns or sensitive high-volume sends, use an address verification process such as the email verification tool.
  • Use a verified sender. The sender identity should be controlled by you and aligned with the domain authenticated for Volanea sending.
  • Monitor both sides. Systeme.io webhook logs show whether the event left Systeme.io and what your endpoint returned. Relay logs show mapping and API outcomes. Volanea provides the sending-side view.
  • Separate test and live configuration. Keep test funnel events, test inboxes, and test API keys distinct from production wherever possible.

The practical lesson is that the webhook itself is only a delivery attempt. Your relay establishes the reliable business operation: it identifies the event, prevents duplicates, protects credentials, and makes the requested Volanea send.

Conclusion

To send email from Systeme.io with Volanea, use Systeme.io’s outbound webhook capability rather than looking for a native app. Configure the concrete Opt-In event, send it to an HTTPS endpoint you control, extract data.contact.email, and have that server call Volanea’s POST /v1/send endpoint with a verified sender and a stable idempotency key.

Keep the Volanea API key exclusively in server-side secrets. Keep event handling narrow. Test the exact JSON emitted by your funnel. Finally, assume retries will happen and design the send so that a retry represents the same email intent—not another message in the subscriber’s inbox.

FAQ

Does Systeme.io have a native Volanea integration?

No. This setup uses Systeme.io outbound webhooks plus your own server-side relay. There is no Volanea marketplace install or native Systeme.io app required.

What Systeme.io event should trigger a welcome email?

Use the Opt-In webhook event for a funnel form subscription. Systeme.io’s documented funnel subscription event type is contact.optin.completed, and the recipient email is at data.contact.email.

Can I call Volanea directly from a Systeme.io funnel page?

No. Do not put a Volanea API key in page code, form configuration, or browser-visible JavaScript. Send the Systeme.io event to a secure server endpoint, and have that endpoint call Volanea.

How do I stop duplicate emails when Systeme.io retries a webhook?

Send a stable Idempotency-Key to Volanea. For an Opt-In event, derive it from immutable event data such as data.contact.id and created_at, rather than generating a random value for each HTTP attempt.

Why is my custom first-name field missing from the email?

The Opt-In payload example includes contact email and may show fields as an empty array. Inspect the actual webhook log for your form and use fallbacks for optional data. Do not require personalization fields unless your production payload proves they are available.