If you need to send email from bexio after a customer record is created, the dependable approach is a server-side webhook integration. bexio can notify your endpoint about supported events; your endpoint then fetches the contact data it needs and sends the message through Volanea’s REST API.
This distinction matters: Volanea does not provide a native bexio marketplace app or a browser-side plug-in. The connection is an integration you control, with bexio as the event source, your server or automation scenario as the decision layer, and Volanea as the delivery service. That architecture keeps API credentials private, gives you a useful audit trail, and prevents accounting data from being copied into a client-visible configuration.
What this integration does
The example in this guide sends a welcome or onboarding email when bexio emits the contact_created event. A new contact is a practical starting point because it is an explicit, durable business event: a person or company has been added to the account, and the contact record has an ID that can be retrieved from the bexio API.
The flow is intentionally split into two steps:
- bexio posts a webhook notification to an HTTPS endpoint you operate.
- That endpoint validates and records the event, looks up the contact through the bexio API, maps the contact’s email and name, then calls Volanea.
Do not treat a webhook as a complete, permanent copy of a customer profile. A webhook tells your service that something happened. Fetching the contact by ID lets your sender use the current canonical record and makes it possible to reject incomplete or unsuitable records before an email is sent.
For the walkthrough, the trigger is specifically a bexio contact being created, represented by the contact_created webhook event. You can apply the same pattern to other event types that your bexio account and API configuration expose, but do not substitute an event name from an example without checking the webhook events available in your own bexio developer configuration.
Choose the right architecture before writing code
There are two sensible ways to connect the systems. The best choice depends on whether you need custom rules, high assurance around duplicate handling, or a no-code operational workflow.
Direct webhook receiver
With a direct receiver, bexio delivers the event to an endpoint such as https://automation.example.com/webhooks/bexio. Your application owns all of the important decisions: which event qualifies, whether a contact has an email address, which template is appropriate, and whether that event has already resulted in a send.
This is generally the strongest option for customer-facing mail. It gives you a durable place to store idempotency records, delivery requests, webhook bodies, and business-level suppression decisions. It also means the Volanea API key exists only in server-side environment variables or a managed secret store.
A direct webhook route requires a public HTTPS endpoint and access to bexio’s webhook capability through its API/developer setup. It is not an “install a Volanea app in bexio” workflow.
Automation middleware
If you do not operate an application server, use an automation service that supports both bexio and outbound HTTP requests, such as Make. The scenario watches or receives the relevant bexio event, filters it, and uses an HTTP module to call Volanea.
This is useful for modest-volume, operational automations: notify a new lead, send a request for missing information, or route a newly created contact into an approved lifecycle. Store the Volanea key in the automation platform’s encrypted connection or credential facility, not in a field that is exposed to users or returned by a webhook.
The trade-off is control. Polling-based triggers can have a delay, run history may be retained for a limited period depending on the automation plan, and robust deduplication can be harder than in a database-backed service. If a send has contractual, financial, or compliance implications, a direct receiver with persistent event storage is usually safer.
The bexio trigger and payload to expect
Configure a webhook subscription for the bexio contact_created event with your receiver’s HTTPS URL as its target. bexio webhook notifications identify the account and provide event data including the affected resource ID; your receiver should use that ID as the lookup key rather than assuming every optional contact field is present in the notification.
A contact-created notification has this relevant shape:
{
"event": "contact_created",
"account_id": 123456,
"data": {
"id": 987654
}
}
The values are examples. account_id identifies the bexio account context, while data.id is the contact ID to retrieve. Your service should log both values, because together with the event name they form a helpful basis for troubleshooting and deduplication.
Do not write a handler that assumes data contains mail, name_1, or name_2. Instead, request the contact from the bexio API using the event’s ID. Contact records can be incomplete by design: a contact can be created without an email address, a business contact can have a different naming convention from an individual, and records may be enriched only after the initial create event.
A representative contact lookup response contains fields such as these:
{
"id": 987654,
"contact_type_id": 1,
"name_1": "Muster",
"name_2": "Ada",
"mail": "ada.muster@example.ch",
"language_id": 1,
"updated_at": "2026-09-30 09:42:18"
}
In bexio contact data, mail is the primary email address. The meanings of name_1 and name_2 depend on the contact type and how the record was entered, so avoid relying on a perfect first-name/last-name presentation for every contact. For an initial message, a neutral greeting or a fallback such as “Hello” is more robust than trying to infer a salutation from sparse accounting data.
Build a secure webhook endpoint
A webhook endpoint must do very little synchronously: accept the request, validate it as far as your configuration permits, persist enough information to process it safely, and return a successful response promptly. Sending an email, querying remote APIs, and generating complex content inside the request path all increase the odds of a timeout and a retry.
For a production implementation, place the endpoint behind HTTPS, restrict access where feasible, and retain a structured log of the event name, account ID, contact ID, receipt time, and processing outcome. Never log raw API keys. Be thoughtful about logs containing email addresses, too: they are personal data and should follow your retention policy.
Keep the Volanea key off bexio and out of the browser
The Volanea API key does not belong in a bexio contact field, custom field, JavaScript snippet, front-end application configuration, or webhook URL query string. A webhook subscription only needs to know where to send the event; your receiver is the security boundary that owns the Volanea credential.
Put the key in a server-side secret, for example VOLANEA_API_KEY in your deployment environment or a cloud secrets manager. The Bexio access token used to retrieve contact details should be stored the same way as BEXIO_ACCESS_TOKEN. Give each credential only the permissions it needs, rotate it when staff or systems change, and use separate secrets for test and production environments.
This is more than a security preference. Client-visible configuration can be inspected in browser developer tools, copied from a mobile app, committed accidentally to a repository, or exposed in an automation export. Once a sending key leaks, an attacker may be able to send mail under your domain and damage its reputation.
Respond quickly, process durably
The receiver should acknowledge a valid webhook quickly and pass the actual processing work to a queue or worker. For low volume, a small application can process immediately after first recording a unique event key, but the same principle applies: do not make successful delivery depend on a single long-lived inbound HTTP request.
A useful event key for this trigger is contact_created:{account_id}:{contact_id}. Create it atomically in your database before sending. If it already exists in a completed or in-progress state, return success without sending another email. This is your protection against retried webhook deliveries and accidental duplicate subscriptions.
Working field mapping and Volanea REST call
The following Node.js example demonstrates the entire mapping. It accepts the real webhook fields used by the contact_created notification, fetches the bexio contact by data.id, requires mail, and posts the resulting email request to Volanea.
The example uses a simple in-memory alreadyProcessed placeholder to make the idempotency decision visible. Replace it with a database transaction, Redis key with suitable persistence semantics, or a queue-backed event table in production. The important property is that two concurrent deliveries cannot both decide that an event is new.
import express from "express";
const app = express();
app.use(express.json());
// Replace with a durable, atomic database or queue implementation.
const processedEvents = new Set();
app.post("/webhooks/bexio", async (req, res) => {
const payload = req.body;
// Real bexio webhook fields used for this event:
// payload.event, payload.account_id, payload.data.id
if (payload?.event !== "contact_created" || !payload?.data?.id) {
return res.status(204).end();
}
const contactId = payload.data.id;
const eventKey = `contact_created:${payload.account_id}:${contactId}`;
if (processedEvents.has(eventKey)) {
return res.status(200).json({ status: "already_processed" });
}
// Claim before remote calls. In production, claim atomically and record status.
processedEvents.add(eventKey);
try {
const bexioResponse = await fetch(
`https://api.bexio.com/2.0/contact/${contactId}`,
{
headers: {
"Authorization": `Bearer ${process.env.BEXIO_ACCESS_TOKEN}`,
"Accept": "application/json"
}
}
);
if (!bexioResponse.ok) {
throw new Error(`bexio contact lookup failed: ${bexioResponse.status}`);
}
const contact = await bexioResponse.json();
const recipient = contact.mail?.trim();
if (!recipient) {
console.info("Contact has no primary email; no message sent", { contactId });
return res.status(200).json({ status: "skipped_no_email" });
}
// Contact mapping: name_2 is used only as an optional friendly display value.
const displayName = [contact.name_2, contact.name_1]
.filter(Boolean)
.join(" ") || "there";
const volaneaResponse = await fetch("https://api.volanea.com/v1/emails", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.VOLANEA_API_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
from: "Acme Team <hello@updates.example.com>",
to: [recipient],
subject: "Welcome to Acme",
html: `<p>Hello ${escapeHtml(displayName)},</p><p>Thanks for getting in touch. We will be in touch shortly.</p>`,
text: `Hello ${displayName},\n\nThanks for getting in touch. We will be in touch shortly.`
})
});
if (!volaneaResponse.ok) {
const responseBody = await volaneaResponse.text();
throw new Error(`Volanea send failed: ${volaneaResponse.status} ${responseBody}`);
}
return res.status(200).json({ status: "sent", contactId });
} catch (error) {
// In production: mark the event failed and let a worker retry safely.
// Do not blindly delete the event claim without recording an attempt.
console.error("bexio webhook processing failed", { contactId, error: error.message });
return res.status(500).json({ status: "failed" });
}
});
function escapeHtml(value) {
return String(value)
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll('"', """)
.replaceAll("'", "'");
}
The field mapping is deliberately small:
payload.data.idbecomes the identifier inGET /2.0/contact/{id}.contact.mailbecomes the Volaneatorecipient.contact.name_2andcontact.name_1become an optional display name in the message body.- A domain you control becomes the Volanea
fromsender. - Your application, rather than bexio, supplies the subject, text body, and HTML body.
Check the current request schema and sender-domain setup in the Volanea API reference and setup guides before deploying. In particular, use a verified sending domain, supply both text and HTML content where appropriate, and avoid trusting unescaped contact data in HTML.
Design the message around business consent
A contact-created event is not automatically permission to send marketing email. The fact that a person or company appears in an accounting or CRM record establishes neither marketing consent nor the type of communication they expect.
Use this automation for a transactional or operational purpose that is connected to why the contact was created: confirming an inquiry, acknowledging account setup, sending requested onboarding information, or notifying the contact about a process they initiated. Keep promotional sequences separate and apply your consent, suppression, and preference-management rules before calling the sending API.
A practical decision table looks like this:
| Contact condition | Recommended action |
|---|---|
mail is empty | Skip and log skipped_no_email |
| Email is syntactically invalid | Skip or route for correction before sending |
| Contact is suppressed or opted out | Do not send |
| Event was already processed | Acknowledge without another send |
| Contact is a valid operational recipient | Send the relevant transactional message |
Consider verifying addresses before a high-value workflow or a first message to imported contacts. For one-off checks during support or data cleanup, the email address verification tool can help identify obviously unsuitable recipients. Verification is not a substitute for consent, nor does it guarantee mailbox acceptance, but it can reduce avoidable bounces caused by malformed data.
Test the integration without emailing real customers
Set up a staging endpoint and a non-production sender before connecting a production bexio account. Create a test contact with an address you control, inspect the inbound webhook body, confirm the contact lookup response, and verify that the mapped request reaches Volanea exactly once.
Test more than the happy path. The most useful cases expose assumptions that otherwise become production incidents:
- Create a contact with a valid
mailvalue and confirm one send. - Create a contact without
mailand confirm the event is recorded but no send occurs. - Replay the same webhook payload and confirm deduplication prevents another send.
- Temporarily make the Volanea request fail and confirm the event moves to a retryable failure state.
- Create both individual and company contacts and inspect the greeting generated from their name fields.
- Update a contact after creation and confirm that your event filter does not accidentally send a second welcome message.
Record the Volanea message identifier returned by a successful API response alongside your event key. That record creates a clean support trail: you can answer whether bexio emitted the event, whether the contact had an email, whether the API accepted the message, and which send corresponds to the accounting record.
When this breaks: failure modes in this specific hop
Every integration has failure modes, but webhook-to-email systems have a few especially important ones. Treating them as expected cases rather than exceptional surprises will make the workflow much easier to operate.
bexio retries can create duplicate sends
A sender may retry a webhook delivery when your endpoint returns an error, takes too long, or has a network interruption after receiving the request. From bexio’s perspective, it cannot know whether your application sent an email just before the connection failed.
That is why the unique event key must be created before the Volanea call, and why it must be durable. Do not use only a process-local JavaScript Set as the production solution shown above. Use a table with a uniqueness constraint or an equivalent atomic store, retain the send status, and make retries operate on that status rather than sending blindly.
Webhook timeouts can look like lost events
If your receiver fetches a contact, renders a complex template, waits on another service, and sends an email before responding, it can exceed the webhook delivery timeout. The upstream system may retry even though the work eventually succeeded.
Acknowledge quickly after safely persisting the event, then process asynchronously. If you cannot introduce a queue immediately, minimize synchronous work and set conservative timeouts for outbound calls. Monitor endpoint latency and alert on rising 5xx responses before they become a backlog.
Payload fields may be missing or differ by configuration
Do not assume optional data will always be present in a webhook or that every account configuration exposes the same event options. A contact can have no primary email; names can be empty or structured differently; access permissions may prevent a lookup token from reading the record; and available API/webhook capabilities can depend on the bexio plan, developer access, and authorization scopes.
Defend against this explicitly. Validate event and data.id, fetch the authoritative contact, require mail before sending, and classify a missing field as a normal skip rather than an application crash. Keep a dashboard or report for skipped events so the operations team can fix the data if the message was important.
API authentication and sender setup can fail separately
A successful bexio contact lookup does not prove Volanea is ready to send. An expired Bexio token causes lookup failures; an invalid Volanea key causes send failures; and an unverified or misaligned sender domain can prevent or impair delivery.
Log these as distinct stages: received, contact_loaded, skipped, send_requested, sent, and failed. This lets you rotate the right credential or fix the right domain setting instead of diagnosing every problem as “the webhook is broken.”
Use a queue and an audit model as volume grows
At small volume, a webhook receiver with a database can be enough. As the number of contacts grows, move to a queue-based pattern: the HTTP endpoint validates and stores the event, a worker fetches contact data and sends the message, and a scheduler retries only failures that are safe to retry.
A minimal event table should include the event key, raw event metadata, contact ID, received timestamp, current state, attempt count, last error, and Volanea message ID when available. Keep the raw body only as long as necessary for debugging and compliance. You do not need to retain more customer data than the integration needs.
This design also makes business rules easier to evolve. You might later send different content based on language_id, distinguish leads from existing customers, or wait until a human has completed a review step. Those changes belong in the worker’s decision layer, not in the webhook endpoint and not in a hard-coded email address field in bexio.
Alternatives when a contact-created email is not the right trigger
A newly created contact is a useful trigger for onboarding, but it is not ideal for every message. If the email is tied to money, fulfillment, or a status transition, use an event that reflects that business state rather than treating contact creation as a proxy.
For example, an inquiry acknowledgment may logically follow a lead creation process; an invoice reminder should follow an invoice or payment workflow; and a shipment update should follow fulfillment data. First confirm that the relevant webhook event is available to your bexio account, then follow the same receiver pattern: capture the resource ID, retrieve the source record, evaluate eligibility, and send a narrowly relevant message.
If the needed outbound event is unavailable in your bexio setup, use middleware that can watch the relevant record or status and invoke an HTTPS request. Be transparent about the consequence of polling: it may detect changes later than a webhook, and the scenario must store a last-processed ID or timestamp to avoid reprocessing old records.
Operational checklist before going live
Before enabling the production webhook, review the whole path rather than only the API call:
- Your receiver URL is public HTTPS and monitored.
- The subscription is limited to the precise bexio event you need, such as
contact_created. - Bexio and Volanea credentials are server-side secrets with appropriate scopes.
- The sending domain and
fromaddress are configured for your Volanea account. - A durable, atomic idempotency key prevents a retried event from sending twice.
- Contacts without
mail, consent, or eligibility are skipped with a useful reason. - HTML output escapes dynamic contact values.
- Failures are retried by a worker with bounded attempts and visible alerts.
- Successful sends are associated with the bexio contact ID and the provider message ID.
- The team knows how to disable the subscription or worker if a faulty rule begins sending unexpected mail.
The last point is often overlooked. A safe integration has a fast operational stop mechanism: disable the webhook subscription, pause the worker queue, or turn off the automation scenario. This is far safer than trying to revoke a credential while events continue arriving.
Conclusion
To send email from bexio with Volanea, use bexio’s contact_created webhook as a notification, not as a complete mailing list export. Receive the event on your own secure endpoint, use data.id to retrieve the contact, map contact.mail to a recipient only after eligibility checks, and call Volanea from server-side code where the API key is protected.
This approach is slightly more deliberate than a one-click connector, but it gives you the controls reliable email automation needs: explicit triggers, private credentials, deduplication, resilient retries, clear records, and a clean separation between accounting data and delivery infrastructure.
FAQ
Does Volanea have a native bexio app?
No. This integration uses bexio webhooks or automation middleware plus a server-side call to Volanea’s REST API. There is no native Volanea marketplace installation flow for bexio.
What starts the email in this example?
The trigger is the bexio contact_created webhook event. The webhook includes the created contact ID in data.id, which the receiver uses to retrieve the contact record before sending.
Where should the Volanea API key be stored?
Store it as a server-side environment variable or managed secret in the webhook receiver or automation platform’s encrypted credential store. Never place it in browser code, a bexio contact field, or a public webhook URL.
How do I stop duplicate emails after webhook retries?
Create a durable, atomic event key such as contact_created:{account_id}:{contact_id} before the Volanea call. If the same event arrives again, acknowledge it without sending a second message.
What happens when a bexio contact has no email address?
Treat it as a normal skipped event. Record the contact ID and a skipped_no_email status, but do not call the sending API until the contact has a usable, eligible email address.