Mollie can tell your application when a payment changes status, and Volanea can deliver the resulting customer email. This guide shows how to send email from Mollie using Mollie’s real webhook flow—not a nonexistent native Mollie app or marketplace plugin—and a small server-side endpoint that calls the Volanea REST API.

The result is a dependable payment-confirmation path: Mollie reports a payment update, your server verifies the payment with Mollie, your code finds the recipient and order data, then Volanea accepts one transactional email. It is a modest amount of code, but it prevents the security and duplicate-send problems that appear when payment status, customer data, and email delivery are treated as one shortcut.

The integration model: Mollie webhook to your server to Volanea

There is no native Volanea app to install in Mollie, and you should not look for a direct “send email” action in the Mollie Dashboard. Mollie’s native outbound HTTP mechanism is a webhook: Mollie makes an HTTP POST request to a URL that you provide when a payment status changes.

That makes the production architecture straightforward:

  1. Your checkout creates a Mollie payment and supplies a webhookUrl.
  2. A customer completes—or otherwise changes—the payment.
  3. Mollie POSTs the payment identifier to your webhook URL.
  4. Your server retrieves the current payment object from Mollie’s Payments API.
  5. Your server confirms that the payment status is paid.
  6. Your server gets the recipient email from its own order record or trusted payment metadata.
  7. Your server calls POST /v1/send at Volanea with a stable idempotency key.

This server-side hop is essential. A Mollie webhook is a notification, not an email renderer. It should trigger business logic only after your application has checked the current payment state from Mollie’s API.

Mollie also documents a Zapier integration with triggers such as New Payment and New Refund. That can be useful for lightweight internal notifications. For customer-facing receipts and payment confirmations, though, a direct webhook relay gives you a clearer source of truth, proper payment verification, a safe location for secrets, and deterministic duplicate protection.

What actually triggers the email in Mollie

For a payment-confirmation email, the concrete trigger is a payment status update delivered to the webhookUrl attached to a Mollie payment. The business rule in your relay is not simply “a webhook arrived.” It is:

Send the email only after the retrieved Mollie payment has status: "paid".

That distinction matters because a payment may be open, pending, authorized, canceled, expired, failed, or paid. A browser redirect to your redirectUrl is not proof that a payment succeeded. The shopper can close the browser, return early, or manipulate a browser URL. The webhook plus a server-side payment lookup is the reliable payment signal.

The legacy per-payment webhook payload

Mollie’s established payment webhook format is intentionally small. It sends an application/x-www-form-urlencoded POST body with one parameter: id.

POST /webhooks/mollie/payment HTTP/1.1
Host: app.example.com
Content-Type: application/x-www-form-urlencoded

id=tr_5B8cwPMGnU6qLbRvo7qEZo

That tr_... value is the Mollie payment ID. It is not the customer email, order total, paid status, or proof that the request should produce an email. Your endpoint must use it to retrieve the payment from Mollie before taking any action.

Next-gen webhooks are not a reason to skip verification

Mollie also has next-generation webhooks with event subscriptions and configurable payload delivery. Depending on the configuration, an event can include a compact event object or a fuller snapshot of the resource. Those events are useful when you need account-level subscriptions rather than a webhookUrl per payment.

Even where the webhook contains a full entity snapshot, retrieve or otherwise verify the current state before sending a customer-facing confirmation. A payment confirmation is a financial communication, so your code should make its decision from an authenticated Mollie API response and the records your application owns.

Prerequisites before you send a payment email

Set up the payment and email sides before wiring the endpoint. The goal is to make every data dependency explicit rather than hoping it appears in a webhook.

You need:

  • A verified sending domain in Volanea and a sender address on that domain, such as receipts@example.com.
  • A Volanea secret API key, such as an sk_... or sk_test_... key.
  • A Mollie API key or access token with permission to retrieve the relevant payments.
  • A public HTTPS URL for the Mollie payment webhook. Mollie cannot send production webhooks to localhost.
  • A database or durable store that links your order, recipient, and Mollie payment ID.
  • A way to deploy server-side environment variables or secrets.

Volanea’s API reference and setup guides are available in the email API documentation. Complete domain verification before testing live payment emails; a properly formed API request still cannot send from an unverified sender domain.

Store recipient data in your application first

The safest source for the recipient address is an order or customer record in your own database. Your application creates the order before it creates the Mollie payment, records the buyer email, then saves the returned Mollie payment ID against that order.

A payment webhook then follows this lookup chain:

Mollie payment ID → your order record → approved receipt recipient → Volanea send request

This approach gives you a durable audit trail and lets you distinguish a payment email from a marketing consent decision. It also lets you resend a receipt from your own support tooling without pretending that a new payment took place.

Metadata can help, but do not trust browser-supplied values

Mollie supports payment metadata, and the metadata is returned when you retrieve the payment. You can use it to store an internal order reference and, if appropriate, a recipient email. Put metadata on the payment from your server while creating the payment—not from unvalidated client-side JavaScript.

For example, your payment-creation code can attach data such as:

{
  "orderId": "ord_8421",
  "receiptEmail": "buyer@example.com"
}

Metadata is convenient for the webhook handler, but it should not replace your order database for a mature checkout. A payment amount, shipping status, tax treatment, invoice URL, and the actual recipient address often belong in your own order system. Treat metadata as a correlation aid, not your only record of a sale.

Create the Mollie payment with a webhook URL

The webhook URL belongs on the payment creation request. The exact payment-creation implementation depends on your backend and checkout, but the important fields are the public webhookUrl, a redirect URL for the customer experience, and server-generated metadata that links the payment back to your order.

Here is a representative Node.js server-side request using the Mollie Payments API:

const paymentResponse = await fetch("https://api.mollie.com/v2/payments", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.MOLLIE_API_KEY}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    amount: {
      currency: "EUR",
      value: "49.00"
    },
    description: "Order ord_8421",
    redirectUrl: "https://app.example.com/checkout/complete?order=ord_8421",
    webhookUrl: "https://app.example.com/webhooks/mollie/payment",
    metadata: {
      orderId: "ord_8421",
      receiptEmail: "buyer@example.com"
    }
  })
});

const payment = await paymentResponse.json();

// Save payment.id (for example, tr_...) against ord_8421 in your database.

Use the returned Mollie payment ID as a durable correlation key. Save it immediately, before redirecting the buyer to Mollie checkout. Do not derive order identity from the payment description, because descriptions are primarily human-readable and may be truncated or changed by payment-method constraints.

Test mode and live mode must remain separate

Mollie test payments and live payments are distinct environments. Keep the receiving endpoint capable of handling both only if your deployment setup makes the separation obvious. Many teams use separate test and production domains, separate Mollie credentials, separate Volanea test and live keys, and distinct sender addresses.

Never let a test webhook generate a live receipt to a real customer by accident. A practical rule is to allow payment.mode === "test" only in a non-production environment, or route it to an internal testing address.

Working webhook relay: Mollie payload to Volanea email

The following Express example handles Mollie’s real form-encoded payment webhook payload, fetches the payment from Mollie, verifies that it is paid, maps trusted fields into a Volanea message, and uses Idempotency-Key to make webhook retries safe.

It uses the payment metadata for readability. In production, replace the receiptEmail metadata lookup with a database lookup by payment.id or by metadata.orderId if that is your canonical order design.

import express from "express";

const app = express();

// Mollie sends the classic payment webhook as
// application/x-www-form-urlencoded: id=tr_...
app.use(express.urlencoded({ extended: false }));

const {
  MOLLIE_API_KEY,
  VOLANEA_API_KEY,
  VOLANEA_FROM
} = process.env;

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

function isEmail(value) {
  return typeof value === "string" && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
}

app.post("/webhooks/mollie/payment", async (req, res) => {
  const paymentId = req.body.id;

  // Real Mollie legacy webhook body field: id=tr_...
  if (typeof paymentId !== "string" || !paymentId.startsWith("tr_")) {
    return res.status(400).send("Missing or invalid Mollie payment ID");
  }

  try {
    // Never trust the incoming webhook body as proof of payment.
    // Retrieve the current payment object from Mollie instead.
    const mollieResponse = await fetch(
      `https://api.mollie.com/v2/payments/${encodeURIComponent(paymentId)}`,
      {
        headers: {
          "Authorization": `Bearer ${MOLLIE_API_KEY}`,
          "Accept": "application/json"
        }
      }
    );

    if (!mollieResponse.ok) {
      throw new Error(`Mollie payment lookup failed: ${mollieResponse.status}`);
    }

    const payment = await mollieResponse.json();

    // Field mapping from the fetched Mollie payment object.
    const orderId = payment.metadata?.orderId ?? payment.id;
    const recipient = payment.metadata?.receiptEmail;
    const amount = payment.amount?.value;
    const currency = payment.amount?.currency;
    const description = payment.description ?? "Your order";

    // A webhook can be sent for statuses other than paid.
    // Acknowledging them avoids retries for events that require no email.
    if (payment.status !== "paid") {
      return res.status(200).send("No receipt required for this status");
    }

    if (!isEmail(recipient)) {
      // Log payment.id/orderId internally, but do not expose payment data to callers.
      console.error("Paid Mollie payment has no valid receipt email", {
        paymentId: payment.id,
        orderId
      });
      return res.status(200).send("Payment recorded; no recipient available");
    }

    const safeDescription = escapeHtml(description);
    const safeOrderId = escapeHtml(orderId);
    const safeAmount = escapeHtml(amount);
    const safeCurrency = escapeHtml(currency);

    // One stable key for one business event. Reuse it when this webhook retries.
    const idempotencyKey = `mollie-payment-paid:${payment.id}`;

    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: VOLANEA_FROM,
        to: recipient,
        subject: `Payment received for order ${orderId}`,
        html: `
          <h1>Payment received</h1>
          <p>Thanks for your payment for <strong>${safeDescription}</strong>.</p>
          <p><strong>Order:</strong> ${safeOrderId}<br>
          <strong>Amount:</strong> ${safeAmount} ${safeCurrency}</p>
          <p>Keep this email for your records.</p>
        `,
        text: [
          "Payment received",
          "",
          `Thanks for your payment for ${description}.`,
          `Order: ${orderId}`,
          `Amount: ${amount} ${currency}`,
          "",
          "Keep this email for your records."
        ].join("\n")
      })
    });

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

    return res.status(200).send("Payment email accepted");
  } catch (error) {
    console.error("Mollie-to-Volanea webhook processing failed", error);

    // A non-2xx response tells Mollie the webhook was not processed.
    // Mollie can retry; the stable Volanea idempotency key prevents duplicates.
    return res.status(500).send("Temporary processing failure");
  }
});

app.listen(3000, () => {
  console.log("Listening for Mollie webhooks on port 3000");
});

Field mapping in the example

The code deliberately maps each value instead of passing an opaque payment object into an email template:

Email valueSourceWhy it is handled this way
Webhook correlation IDreq.body.idThis is Mollie’s form-encoded payment identifier.
Current payment statuspayment.statusRetrieved from Mollie before any email is sent.
Recipientpayment.metadata.receiptEmailReplace with a database value when possible.
Order referencepayment.metadata.orderIdFalls back to payment.id for traceability.
Charged amountpayment.amount.valueMollie supplies monetary values as strings, preserving decimal precision.
Currencypayment.amount.currencyIncluded with the amount rather than assumed from your store locale.
Email senderVOLANEA_FROMKept in server configuration and tied to a verified domain.
Duplicate guardmollie-payment-paid:${payment.id}Stable through retries for the same paid payment.

Do not build the recipient list from a free-form description, an untrusted URL parameter, or a client-provided request body. Those values are not a safe substitute for your order record.

Where the Volanea API key belongs

The Volanea API key does not live in Mollie, in a checkout page, in a mobile app, or in browser-visible configuration. Mollie calls your webhook URL; it does not need, receive, or store your Volanea credential.

Store VOLANEA_API_KEY in the secret manager or encrypted environment-variable facility provided by the platform that hosts your webhook endpoint. Examples include your hosting provider’s secret settings, a cloud secret manager, or a deployment-time encrypted environment configuration.

Your runtime configuration should look conceptually like this:

MOLLIE_API_KEY=live_or_test_mollie_credential
VOLANEA_API_KEY=sk_live_or_sk_test_volanea_secret
VOLANEA_FROM="Example Store <receipts@example.com>"

The browser receives neither API key. The buyer should see only a redirect to Mollie checkout or client-safe payment configuration. If a Volanea key is embedded in JavaScript, exposed through a public endpoint, placed in a no-code client field, or committed to a repository, anyone who discovers it may be able to send mail using your account.

Restrict access around the webhook endpoint

A secret environment variable is necessary but not sufficient. Also make the endpoint narrow:

  • Accept only POST requests at the webhook route.
  • Validate that a classic webhook includes a plausible tr_ payment ID.
  • Retrieve the payment using your own Mollie credential before making an email decision.
  • Send only for the exact status and message type you intend to support.
  • Avoid logging API keys, full customer data, or complete API error bodies.
  • Keep a durable record of processed payment IDs and resulting email IDs where your operational requirements call for it.

For next-generation signed webhooks, verify the X-Mollie-Signature against the raw request body and shared secret before processing. The payment retrieval step is still valuable as a current-state check and a way to keep business decisions independent of a transient event payload.

Prevent duplicate emails when Mollie retries

Webhooks are delivered over networks, and networks produce ambiguous outcomes. Your server may successfully send an email but lose its response connection before it returns HTTP 200 to Mollie. Mollie then sees a failed delivery attempt and retries. Without duplicate handling, the customer receives two receipts.

Mollie expects a successful 200 OK response once the webhook has been processed; responses outside the successful range can be retried. That is why the email call needs an idempotency key that represents the business event, not the current HTTP attempt.

For a payment confirmation, this is a good key:

mollie-payment-paid:tr_5B8cwPMGnU6qLbRvo7qEZo

It says, “the paid-email operation for this unique Mollie payment.” It must remain the same if the webhook comes again tomorrow, after a timeout, or concurrently from two workers.

Idempotency is not the same as a random request ID

Do not generate a fresh UUID every time the webhook handler runs. A fresh key makes every retry look like a new send operation. Use the Mollie payment ID plus the message purpose, such as paid, refund, or failed-payment-help.

For higher-volume stores, combine Volanea’s idempotency support with your own database constraint. Create a table or unique record keyed by something like:

payment_id + message_type

Claim that record before sending. Store the Volanea response identifier and final processing status. This gives your team an audit log, lets support see whether a receipt was accepted, and protects you if you ever change email providers or add a second downstream action.

When this breaks: Mollie-to-Volanea troubleshooting

Every integration has two network hops: Mollie to your webhook, then your webhook to Volanea. Diagnose which hop failed before changing code or resending anything.

Mollie retries cause duplicate sends

Symptom: One payment results in multiple customer emails.

Likely cause: Your server sent the message but returned a timeout or error to Mollie, and a retry used a new email-send request without a stable idempotency key.

Fix: Use a deterministic Volanea Idempotency-Key based on payment.id and message type. Add a database uniqueness rule for the same pair. Do not generate an idempotency key with the current timestamp.

Your webhook times out

Symptom: Mollie continues retrying, or your webhook logs show long-running requests.

Likely cause: The request is doing too much synchronously: multiple database queries, invoice generation, third-party API calls, or email rendering work before responding.

Fix: Keep the webhook’s synchronous path focused: validate the ID, retrieve the payment, claim the deduplication record, submit the email, and return a successful response. If your receipt requires PDF generation or several downstream jobs, enqueue a durable job after recording the payment event. Make the job idempotent too.

Do not return 200 OK before persisting enough information to recover the task. If your process crashes immediately after the 200 response, Mollie has no reason to retry and you may lose the email. A durable queue or transactionally stored job is the better pattern for complex workflows.

The webhook has an ID but no email address

Symptom: req.body.id exists, the payment is paid, but there is no recipient field available to map into to.

Likely cause: This is expected with Mollie’s classic payment webhook. Its payload contains only the payment ID. The retrieved payment also should not be assumed to include a customer email for every payment method or checkout design.

Fix: Save the order email in your database before creating the payment, keyed to your order and Mollie payment ID. Alternatively, attach server-generated receiptEmail metadata at payment creation, then validate it before sending. Never assume an email collected by one payment method will be available across every method, plan, or integration path.

The endpoint receives a webhook for an unpaid payment

Symptom: The handler runs for open, pending, failed, expired, canceled, or authorized states.

Likely cause: Payment status changes are part of normal processing. A webhook is an update notification, not exclusively a paid event.

Fix: Make payment.status === "paid" an explicit gate for a receipt. For other statuses, either acknowledge the webhook with 200 OK and do nothing, or map those states to deliberately designed messages such as a payment-failure support email. Do not send a success receipt for authorized unless your specific fulfilment policy treats authorization as payment completion.

Volanea rejects the send request

Symptom: Your logs show a non-success HTTP response from POST /v1/send.

Likely cause: Common causes include an invalid or missing API key, a sender domain that is not verified, malformed recipient data, or a request body that does not match the send API requirements.

Fix: Check the response status and body in protected server logs, confirm the key belongs to the intended Volanea environment, and verify that VOLANEA_FROM uses an authenticated domain. Use a real test payment and a test recipient to validate the full path before enabling live traffic. Review transactional email pricing and sending limits when planning for order volume, retries, and seasonal spikes.

A test payment sends a production-looking email

Symptom: A developer receives a normal receipt during testing, or test traffic mixes with production reporting.

Likely cause: Test and live credentials, sender identities, or webhook deployments share the same configuration.

Fix: Separate test and live configuration. Check payment.mode, use a test Volanea key for test environments, and optionally replace external recipients with an allowlisted internal inbox in non-production deployments.

A practical deployment and testing checklist

Before using the integration for real transactions, run through the complete path rather than testing the Volanea request alone.

  1. Verify the Volanea sender domain and choose a receipt sender address.
  2. Create a non-production Volanea key and a Mollie test credential.
  3. Deploy the webhook endpoint on a public HTTPS URL.
  4. Create a Mollie test payment with webhookUrl pointing at that endpoint.
  5. Store the generated Mollie payment ID against a test order and recipient email.
  6. Complete the payment using Mollie’s test flow.
  7. Confirm your endpoint receives form data containing id=tr_....
  8. Confirm the server retrieves the corresponding Mollie payment and sees status: "paid".
  9. Confirm exactly one Volanea request is made with the expected Idempotency-Key.
  10. Repeat the webhook request manually with the same payment ID and verify that no duplicate email is created.
  11. Test missing recipient data, a non-paid payment status, a bad Volanea key, and a temporary network failure.
  12. Review your logs to ensure secrets and full payment details are not printed.

A successful API response means Volanea accepted the message for processing; it is not the same thing as a recipient opening the message. Keep your operational monitoring separate: payment state belongs to Mollie and your order system, while send acceptance and downstream delivery events belong to your email infrastructure.

Choosing direct webhooks versus Zapier

Use the direct webhook relay when the email is an important transactional message: payment confirmation, receipt availability, subscription payment confirmation, service activation, or a failure notification that affects a customer’s next action.

A direct relay is usually the better choice when you need:

  • A verified Mollie payment status before sending.
  • A reliable link to your internal order record.
  • Server-side secret handling.
  • Idempotency tied to a payment ID.
  • Control over errors, retries, logs, and customer-facing content.
  • A path to attach invoices, add localized templates, or write email outcomes back to your database.

Zapier can be reasonable for internal alerts, spreadsheet updates, CRM tasks, or early prototypes. Mollie’s Zapier integration exposes triggers including New Payment and New Refund. But if you route Volanea through an automation platform, do not put a long-lived Volanea secret into browser-visible fields or an unreviewed client-side webhook action. Use a secure server-side relay or the automation platform’s encrypted connection mechanism, and make duplicate prevention part of the workflow design.

Conclusion

To send email from Mollie reliably, treat the payment webhook as the beginning of a verified server-side workflow—not as an email event with all the data you need. Mollie sends the payment ID, your endpoint retrieves and verifies the payment, your application finds the correct recipient and order context, and Volanea sends the transactional message with a payment-based idempotency key.

This pattern is intentionally conservative. It keeps your Volanea key out of client-visible configuration, avoids trusting redirect URLs or webhook body claims as proof of payment, works with Mollie’s actual form-encoded webhook payload, and remains safe when retries happen. Once the receipt path is reliable, you can reuse the same relay structure for refunds, recurring-payment notices, fulfillment updates, and account communications.

FAQ

Does Mollie have a native Volanea integration?

No. Mollie does not provide a native Volanea marketplace app or plugin. Use a Mollie webhook that calls your server, then have your server call the Volanea REST API.

What is the Mollie trigger for a payment confirmation email?

The trigger is a payment status webhook delivered to the webhookUrl configured for the payment. Retrieve the payment from Mollie, then send the confirmation only when its current status is paid.

Does Mollie send the customer email in its classic payment webhook?

No. The classic webhook is form-encoded and sends a single id parameter containing the Mollie payment ID. Store the email in your order system before payment creation, or attach trusted server-generated metadata and retrieve it with the payment.

Where should I store the Volanea API key?

Store it as a server-side secret or environment variable on the system hosting your webhook relay. Never expose it in checkout JavaScript, a mobile app, a public configuration endpoint, or a Mollie redirect URL.

How do I prevent Mollie webhook retries from emailing customers twice?

Use a stable Volanea Idempotency-Key, such as mollie-payment-paid:<payment-id>, for every retry of the same payment confirmation. For stronger auditing, also keep a unique processed-event record in your own database.