Sending email from plentymarkets does not require a marketplace plugin, but it does require an integration boundary that keeps your email credentials private. This guide shows how to send email from plentymarkets with a PlentyONE webhook subscription, a secure webhook receiver, and Volanea’s REST email API.
There is no native Volanea plentymarkets app, marketplace listing, or installable plugin. That is important: do not put a Volanea API key into storefront JavaScript, an exposed callback URL, or a client-visible configuration screen. Instead, plentymarkets notifies a server endpoint about an order event; that server validates the notification, obtains the order details it needs, and sends the message through Volanea.
The result is a maintainable path for receipts, payment reminders, shipment updates, back-in-stock notices, support acknowledgements, and other transactional messages that should be driven by commerce events.
The integration architecture
The production architecture has three distinct hops:
- plentymarkets / PlentyONE emits an outbound webhook when a subscribed event occurs.
- Your webhook receiver validates the event, deduplicates it, loads any data absent from the notification, and decides whether an email should be sent.
- Volanea receives a server-to-server
POST /v1/sendrequest and handles transactional delivery.
This separation is deliberate. A PlentyONE webhook is an event notification, not a trusted place to store a mail-provider secret or a complete email-rendering system. Your receiver is the policy boundary: it can reject malformed requests, prevent repeats, select the right sender identity, and record what happened.
PlentyONE’s webhook-subscription feature is available in the back end at Setup » Settings » Webhooks. You create a subscription, choose one or more web stores, select the subscribed events, and provide the endpoint URL that PlentyONE should call. PlentyONE generates a sign key when the subscription is saved; copy it then, because the documentation says it is not shown again unless reset. The sign key is included in webhook payloads and should be checked by the receiving service.
For an order-confirmation flow, the concrete trigger is the order-created event that you select under Subscribed events in the webhook subscription. The event occurs when the order record is created. If your business rule is “send only after payment,” do not use order creation as a proxy for payment success. Subscribe to the appropriate payment or status-related event available in your PlentyONE event picker, then enforce the exact status or payment rule in your receiver.
Why a relay is required
It may be tempting to point a plentymarkets webhook directly at https://api.volanea.com/v1/send. Do not do that.
First, Volanea expects an authenticated email-send request, while PlentyONE webhook subscriptions are designed to notify your endpoint about a PlentyONE event. The webhook body is an event payload, not Volanea’s message payload. Second, an outbound webhook configuration is not an appropriate vault for a long-lived Volanea secret key. Third, an event delivery can be repeated after a timeout or a failed response; without idempotency logic, a direct mapping can issue duplicate receipts.
A relay can be very small. A serverless function, Cloudflare Worker, Node service, Laravel route, or queue worker is sufficient. Its job is not to recreate an email platform. It simply receives the PlentyONE event, returns a fast successful response after durable acceptance, and performs the downstream API work safely.
For teams without an application runtime, a middleware route is also viable. Make has a PlentyMarkets connector and webhook-capable scenarios, but you still need to ensure the Volanea key is stored in the middleware connection or secret store—not in public client settings—and that the scenario uses a stable idempotency value. For a transaction such as an order receipt, a small owned relay is usually easier to audit and test.
What plentymarkets sends
Webhook subscriptions are event-specific, so do not treat every delivery as a complete order export. PlentyONE’s documentation describes subscriptions as notifications for predefined events and explicitly notes that the sign key is included in each payload. The safe implementation is to accept the notification as the trigger, retain its event data, and load the canonical order through the PlentyONE REST API when the email needs information that is not present in the event body.
For an order-created subscription, design the receiver around an event envelope containing the order identity and the subscription sign key. In practice, capture a test delivery from Setup » Settings » Webhooks using Test active webhooks and save that exact JSON as a fixture for your account before deploying. Event catalogs can change, and the event body may contain less data than the full order resource.
The receiver below expects an order identifier at payload.data.id and the subscription key at payload.signKey. It immediately normalizes the order identifier, checks the sign key, then fetches the canonical order. If your captured fixture uses a different event wrapper, change only the two extraction lines and retain the rest of the integration.
{
"signKey": "<plentyone-subscription-sign-key>",
"data": {
"id": 630
}
}
The complete order object returned by PlentyONE’s order API is the better source for mail content. An order resource includes fields such as id, statusId, statusName, createdAt, amounts, addressRelations, and orderItems. A sales order uses typeId: 1. Importantly, the email address is not something you should invent from an order number or assume is always embedded in a lightweight notification. Load the address/contact relations and choose the invoice or delivery recipient according to your own receipt policy.
Build the webhook receiver
The following Node.js example demonstrates the whole server-side hop. It accepts the PlentyONE order-created webhook, verifies the subscription sign key with a constant-time comparison, prevents duplicate order-confirmation sends, requests the current order resource from PlentyONE, maps that order into a Volanea email request, and sends it with an Idempotency-Key.
The code uses environment variables for secrets. The exact PlentyONE order expansion parameters and the address-selection logic can vary by account and data model, so the example isolates them in one function rather than pretending every store has identical relations.
import crypto from "node:crypto";
import express from "express";
const app = express();
app.use(express.json({ limit: "1mb" }));
const {
PLENTY_WEBHOOK_SIGN_KEY,
PLENTY_API_BASE_URL,
PLENTY_ACCESS_TOKEN,
VOLANEA_API_KEY,
VOLANEA_FROM_EMAIL,
VOLANEA_FROM_NAME = "Orders"
} = process.env;
// Replace with Redis, Postgres, Durable Objects, or another persistent store.
// A process-local Set is only suitable for illustrating the idempotency decision.
const sent = new Set();
function safeEqual(left, right) {
if (typeof left !== "string" || typeof right !== "string") return false;
const a = Buffer.from(left);
const b = Buffer.from(right);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
async function getOrder(orderId) {
const response = await fetch(
`${PLENTY_API_BASE_URL}/rest/orders/${encodeURIComponent(orderId)}`,
{
headers: {
Authorization: `Bearer ${PLENTY_ACCESS_TOKEN}`,
Accept: "application/json"
}
}
);
if (!response.ok) {
throw new Error(`PlentyONE order lookup failed: ${response.status}`);
}
return response.json();
}
function getRecipientEmail(order) {
// Implement this for your account using the address/contact relations returned
// by your PlentyONE order request. Do not send until this produces one valid email.
const email = order?.recipientEmail;
if (!email || !email.includes("@")) {
throw new Error("Order has no usable recipient email");
}
return email;
}
function orderTotal(order) {
const systemAmount = order.amounts?.find((amount) => amount.isSystemCurrency);
if (!systemAmount) return "";
return new Intl.NumberFormat("de-DE", {
style: "currency",
currency: systemAmount.currency
}).format(Number(systemAmount.invoiceTotal));
}
function renderOrderItems(order) {
return (order.orderItems || [])
.filter((item) => item.typeId === 1)
.map((item) => `<li>${escapeHtml(item.orderItemName)} × ${item.quantity}</li>`)
.join("");
}
function escapeHtml(value = "") {
return String(value)
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll('"', """)
.replaceAll("'", "'");
}
async function sendVolaneaOrderEmail(order) {
const recipient = getRecipientEmail(order);
const total = orderTotal(order);
const items = renderOrderItems(order);
const idempotencyKey = `plenty-order-${order.id}-confirmation-v1`;
const body = {
from: {
email: VOLANEA_FROM_EMAIL,
name: VOLANEA_FROM_NAME
},
to: [{ email: recipient }],
subject: `We received your order #${order.id}`,
html: `
<h1>Thanks for your order</h1>
<p>Your order number is <strong>#${order.id}</strong>.</p>
<ul>${items}</ul>
<p><strong>Total:</strong> ${escapeHtml(total)}</p>
`,
text: `Thanks for your order. Order #${order.id}. Total: ${total}`
};
const response = 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(body)
});
if (!response.ok) {
throw new Error(`Volanea send failed: ${response.status} ${await response.text()}`);
}
return response.json();
}
app.post("/webhooks/plentyone/order-created", async (req, res) => {
const payload = req.body;
if (!safeEqual(payload?.signKey, PLENTY_WEBHOOK_SIGN_KEY)) {
return res.status(401).json({ error: "Invalid webhook sign key" });
}
const orderId = payload?.data?.id;
if (!orderId) {
return res.status(400).json({ error: "Missing order ID in webhook payload" });
}
const dedupeKey = `plenty-order-${orderId}-confirmation-v1`;
if (sent.has(dedupeKey)) {
return res.status(200).json({ received: true, duplicate: true });
}
try {
const order = await getOrder(orderId);
// Optional business rule: refuse the wrong order type or status here.
if (order.typeId !== 1) {
return res.status(200).json({ received: true, skipped: "not-a-sales-order" });
}
const result = await sendVolaneaOrderEmail(order);
sent.add(dedupeKey);
return res.status(200).json({ received: true, message: result });
} catch (error) {
console.error(error);
return res.status(500).json({ error: "Webhook processing failed" });
}
});
app.listen(3000);
The field mapping in this example is explicit:
payload.data.idbecomes the lookup key forGET /rest/orders/{id}.order.idbecomes the visible order number and the stable idempotency component.order.amounts[]suppliesinvoiceTotalandcurrencyfor the receipt total.order.orderItems[]supplies item names and quantities for the HTML list.- The order’s resolved invoice/contact email becomes Volanea’s
to[0].email. - A verified sender address becomes Volanea’s
from.email.
Do not map an untrusted customer-controlled value into HTML without escaping it. Product names, custom properties, and address fields may contain characters that need encoding. The example escapes product names before inserting them into HTML.
Authenticate both sides correctly
There are two different secrets in this integration, and they solve different problems.
The PlentyONE webhook sign key
The sign key belongs to the webhook subscription. Create the subscription in Setup » Settings » Webhooks, copy the generated sign key, and store it as PLENTY_WEBHOOK_SIGN_KEY in your relay’s secret manager or deployment environment. The receiver compares that secret to the incoming payload before reading the event.
Treat the endpoint URL as sensitive too. It should use HTTPS and should not be published in source code, public documentation, browser bundles, or support screenshots.
The Volanea API key
The Volanea key lives nowhere on the plentymarkets client side. It belongs only in the server-side relay’s secret store as VOLANEA_API_KEY. That means it is readable by the function or service making the HTTPS request to Volanea, not by browser code, storefront visitors, or a public webhook configuration.
Volanea uses secret keys beginning with sk_ or sk_test_. Use a test key only in a non-production environment, restrict access to production secrets, and rotate a key if it appears in a log, ticket, repository, or front-end bundle. For endpoint options, authentication details, and response formats, see the Volanea email API reference.
The relay may also require PlentyONE REST credentials if the webhook notification does not contain the address, line items, or other data needed for your message. PlentyONE’s REST API uses bearer authentication. Keep that access token server-side as well, assign only the permissions the order lookup requires, and avoid using a human administrator’s credentials for a production integration.
Configure the webhook subscription
Set up one narrow subscription first rather than subscribing to every available event. Narrow subscriptions are easier to debug and reduce the chance that a status edit, import, or unrelated order update triggers a customer email.
- Deploy your HTTPS receiver at a route such as
https://integrations.example.com/webhooks/plentyone/order-created. - In PlentyONE, open
Setup » Settings » Webhooks. - Create a New Subscription.
- Enter the receiver URL as the endpoint and add a description that states the purpose, such as “Order-created receipt relay.”
- Select only the relevant WebStores. PlentyONE sends web-store-linked events only for the web stores attached to the subscription.
- Under Subscribed events, select the order-created event for a basic order acknowledgement.
- Save the subscription and copy the generated sign key into your relay’s secret configuration.
- Use Test active webhooks to verify that the receiver accepts the event and logs its actual payload structure.
- Place a low-value real test order in a non-production-safe environment or use a controlled production recipient to validate the full order lookup and Volanea send.
Do not enable a customer-facing confirmation until the relay can prove all of these conditions: it recognizes the webhook, identifies the intended order, finds one valid recipient, uses a verified sender identity, and records the Volanea message result.
Choose the right commerce event
An order-created webhook is useful, but it is not universally the correct moment to send mail. The right event depends on the promise you are making in the message.
| Message | Preferred trigger principle | Important safeguard |
|---|---|---|
| Order received | Order created | Explain that receipt is not necessarily payment acceptance or shipment confirmation. |
| Payment confirmation | Payment/status event that reflects your settled-payment rule | Avoid treating an authorization or pending payment as settled funds. |
| Shipment confirmation | Fulfilment or status transition after tracking is attached | Require a tracking number before rendering the message. |
| Cancellation confirmation | Cancellation status transition | Use a different idempotency version from the original receipt. |
| Support acknowledgement | Ticket-created event | Avoid replying to spam or an empty contact email. |
This distinction matters operationally. A generic “order updated” style trigger can run many times as warehouse, payment, shipping, and customer-service data change. An email that should happen once needs an event or predicate that is both meaningful and stable.
If you need more complex business rules—such as “send when the order reaches a selected status, only for a particular referrer, excluding B2B buyers”—enforce them in the relay. Plenty Flow and event procedures can automate internal actions, but a webhook receiver still gives you a clear external API boundary and a durable place for secrets, audit logs, and send deduplication.
Make the message deliverable
A working API call is only the first part of transactional email. The sender identity should be a domain you control and authenticate. Configure the DNS records supplied in Volanea for the sending domain before putting the flow into production. Authentication improves alignment and reduces the chance that a legitimate receipt is treated as suspicious mail.
Use a sender address that recipients recognize, such as orders@yourdomain.example, and keep the display name stable. The from identity should match the transactional purpose: “Example Store Orders” is clearer than a generic marketing sender for a receipt.
Content also affects support volume and trust. Include the order reference, the purchaser’s expected next step, a support path, and only the details that belong in the email. Do not expose full payment credentials, internal warehouse notes, raw customer metadata, or secrets from the webhook payload.
Before adding an address to a transactional workflow, you can also screen test data and imported customer records with the email address verification tool. Verification is useful for reducing avoidable bad-address sends, but it does not replace consent, suppression handling, or your own transactional-email policy.
When this breaks
Every event-to-email integration eventually encounters failures. The goal is not to assume they will never happen; it is to design each hop so that a retry does not create an incorrect customer experience.
PlentyONE retries after a timeout
If your receiver is slow, unavailable, or returns a non-2xx result, the delivery can be attempted again. A retry is correct behavior for an event system, but it can become a duplicate receipt if every delivery creates a fresh email.
Use two layers of protection:
- Store a durable deduplication record keyed by a business event, such as
plenty-order-630-confirmation-v1. - Send that same value in Volanea’s
Idempotency-Keyheader.
Do not generate a random UUID on every retry. A new random value tells the send API that the retry is a new operation. A stable key says that all attempts represent the same order-confirmation action.
The webhook handler times out
Avoid doing expensive work before acknowledging receipt. Canonical order lookups, template rendering, image fetches, and Volanea sends can all add latency. A stronger production design receives the webhook, verifies it, writes a job and dedupe key to durable storage, returns 200, and lets a worker perform the order lookup and send.
That queue-based approach makes failures observable and retryable without keeping PlentyONE’s webhook request open. It also lets you apply a controlled retry policy to temporary API failures while keeping a dead-letter record for permanent failures such as missing recipient data.
Fields are missing or not loaded
Do not assume every webhook event contains every order relation your email needs. A notification may identify an order without including address records, line items, payment details, or custom properties. Web-store scoping can also mean that an expected event never arrives because the order belongs to a different web store than the subscription.
Make the receiver fail safely. If the canonical order has no valid recipient email, log a structured error with the order ID and return or acknowledge according to your queue strategy; do not substitute an arbitrary address, send to an internal fallback without policy approval, or repeat the attempt forever. Build dashboards around these skipped reasons so operations can repair data rather than discover the issue from a customer complaint.
The event does not match the business condition
An order may be created before payment is confirmed, or its status may later change multiple times. Protect every message with an explicit check: correct order type, correct payment state or status, allowed web store, expected recipient, and unsent business-event key. This is more reliable than trusting the event name alone.
Volanea rejects the send
A rejected send can result from an invalid message payload, a sender domain that has not been authenticated, an invalid recipient, a suppression, or an API-key problem. Persist the response status and the order/event key. Retry only errors that are plausibly temporary; retrying a malformed address or missing sender setup only creates noise.
The Volanea send endpoint supports safe retry handling through Idempotency-Key, so keep the original business key for a retried request. After correcting a permanent configuration problem, replay the stored job rather than manually recreating the email with a different key.
Testing before production
A good test plan covers both event delivery and actual email behavior. Do not stop after seeing a 200 response from your webhook route.
Test the flow in this order:
- Use PlentyONE’s test action to capture the raw webhook body and verify the sign-key check.
- Confirm that the receiver extracts the order ID from your real subscription payload.
- Test the PlentyONE order lookup with a least-privilege API credential.
- Test a complete order with an internal recipient address and inspect the rendered subject, plain-text part, HTML, item list, currency, and order number.
- Repeat the same webhook delivery twice and confirm that only one Volanea email is created.
- Simulate a Volanea
500response and confirm that the queue or retry worker reuses the same idempotency key. - Test an order lacking an email address and confirm that it is skipped and surfaced for review rather than sent incorrectly.
- Test a second web store if you operate more than one, because webhook subscriptions can be scoped by web store.
Keep sanitized webhook fixtures in version control. A fixture makes it possible to test changes to mapping code without creating a new order or sending an email every time. Remove customer email addresses, names, street addresses, phone numbers, tokens, and sign keys from any fixture committed to a repository.
Operational visibility after launch
The best integration gives support and engineering teams a way to answer a simple question quickly: “Did order 630 trigger the message, and what happened next?”
Log a record for each stage using non-sensitive identifiers:
- PlentyONE order ID and received event name.
- Web store identifier where available.
- Your dedupe/idempotency key.
- Whether the order lookup succeeded.
- The recipient domain or a privacy-safe hash, rather than the full email in general logs.
- Volanea response status and message ID when available.
- Final state: sent, duplicate, skipped, retrying, or failed.
These records reveal second-order problems that are otherwise hard to diagnose. For example, a rise in “missing recipient” skips may indicate an order-import change; duplicates may indicate a timeout between your receiver and Volanea; a sudden increase in send failures may point to a rotated credential or sender-domain setup issue.
Conclusion
To send email from plentymarkets with Volanea, use PlentyONE’s native webhook subscriptions as the trigger—not an invented marketplace app—and route the event through a small server-side relay. For a straightforward receipt, subscribe to the order-created event, validate PlentyONE’s sign key, use the event’s order ID to retrieve authoritative order data, then call POST /v1/send with a stable idempotency key.
Keep the Volanea secret key in the relay’s secret store, not in client-visible plentymarkets configuration. Treat webhook payloads as event notifications rather than guaranteed complete customer records. Finally, build for retry behavior from the start: stable business keys, durable deduplication, fast acknowledgements, and logs that connect an order event to the resulting email.
FAQ
Does Volanea have a native plentymarkets plugin?
No. This integration uses PlentyONE webhook subscriptions and a server-side relay. There is no Volanea plentymarkets marketplace install flow to configure.
What starts the email send in plentymarkets?
For the example in this guide, the trigger is the order-created event selected in the webhook subscription’s Subscribed events list. Use a different event or an explicit server-side condition when the message should depend on payment, fulfilment, or a specific status transition.
Where should I store the Volanea API key?
Store it only as a server-side secret in the relay, middleware connection, or deployment environment. Never place it in storefront code, a public webhook URL, browser-accessible configuration, or a customer-facing page.
How do I prevent duplicate order emails?
Create a stable business key such as plenty-order-630-confirmation-v1, persist it in a durable store, and send the same value as Volanea’s Idempotency-Key header for every retry of that confirmation.
Can I send without writing a server?
You can use middleware such as Make to connect PlentyMarkets to an HTTP request, but it still needs secure secret storage, explicit field mapping, and duplicate prevention. A small owned webhook relay generally provides more control for high-value transactional mail.