Crisp is built for conversations, while Volanea is built to deliver transactional and campaign email. This guide shows how to send email from Crisp without exposing an API key, using Crisp’s outbound webhook capability and a server-side relay that maps a real Crisp event into a Volanea REST API request.
There is no native Volanea app, Marketplace listing, or one-click Crisp plugin to install. That is not a limitation you need to work around with browser-side code: the dependable pattern is Crisp webhook → your private endpoint → Volanea. It keeps credentials off the client, gives you a place to apply business rules, and makes duplicate prevention and failure handling possible.
What this integration does
The example in this guide sends a follow-up email after a visitor sends a message to your Crisp inbox. The concrete Crisp trigger is the message:received webhook event: Crisp emits it when a message is received in a conversation. Your endpoint receives the event, checks that it is a customer message rather than an operator or bot message, finds the visitor’s email address, and sends a message through Volanea.
That simple description hides several decisions that matter in production. A received message is not automatically a good reason to email someone. A visitor may send three messages in two minutes, may not have supplied an email address, or may be actively chatting with an operator. The relay should therefore make the policy explicit rather than treating every webhook as an unconditional email command.
A useful implementation has four parts:
- Crisp emits
message:receivedto a public HTTPS endpoint when an incoming conversation message arrives. - Your relay validates and normalizes the webhook and decides whether that event should produce an email.
- The relay calls Volanea’s REST email endpoint with a recipient, subject, HTML/text content, and an idempotency key.
- Volanea queues and delivers the email, while your application records enough information to investigate failures or suppress duplicates.
This is an event-driven integration, not a two-way inbox sync. Replies to the delivered email will go to the reply_to address you set in Volanea; they do not automatically appear in Crisp. If your aim is to bring email replies back into a Crisp conversation, treat that as a separate inbound-email workflow with its own routing and identity requirements.
Why use a webhook relay instead of a browser integration
Crisp webhooks make an HTTP request from Crisp’s infrastructure to an endpoint you control. They are suitable for telling your backend that something happened in an inbox conversation. They are not a safe place to place a Volanea secret in a page, widget configuration, mobile application, or public repository.
The correct trust boundary is straightforward:
- Crisp may know the URL of your webhook receiver.
- Your receiver holds the Volanea API key as a server environment variable or secret-manager value.
- The visitor’s browser never receives that key.
- The receiver, not Crisp’s JavaScript snippet, makes the Volanea API call.
In this architecture, the Volanea key does not live in client-visible Crisp configuration. It lives in the runtime environment of the relay, for example as VOLANEA_API_KEY in your deployment provider’s encrypted environment settings. This is important because an API key grants sending authority. A key exposed in browser code can be copied by any visitor and abused to send mail as your domain, damaging both your account and sender reputation.
A webhook relay also gives you control Crisp cannot infer from a generic event. It can fetch or consult your customer record, choose a locale, skip opted-out contacts, select a template, attach a CRM identifier, and record the provider message ID. It can also respond quickly to Crisp while completing slower work asynchronously.
For setup details on sending domains, REST authentication, and the available send parameters, use the email API reference and setup guides. Keep the integration logic in your own service even if the first version is only a small serverless function.
The Crisp event that starts the send
For this guide, register a Crisp website webhook for the message:received event. This is the event to use when the business rule is “a customer has just sent us a message.” Do not use message:send for that rule: that event represents a message being sent into the conversation and can include messages from operators or automated systems, depending on how the conversation is handled.
Crisp’s webhook event envelope identifies the event and carries event data. For message events, the useful data is the website and session context plus a message object. The message object includes properties such as the sender (from), message type, origin, content, and timestamp. The conversation/session identity is vital: it is the stable key to use when you want to look up profile data or avoid sending repeatedly for the same conversation.
A representative message:received delivery has this shape:
{
"event": "message:received",
"data": {
"website_id": "a1b2c3d4-website-id",
"session_id": "session_01HXYZ",
"message": {
"type": "text",
"from": "user",
"origin": "chat",
"content": "Can someone help me change my billing address?",
"timestamp": 1735689600
}
}
}
Treat this as an event payload, not as a complete customer profile. In particular, an email address is not guaranteed to be present in every message event. A chat visitor may be anonymous, may have provided only a nickname, or may have chosen not to share contact information. Your code must not assume that message.content is an email address or use it as a recipient.
Instead, make an explicit recipient policy. The simplest is to send only when your webhook payload or your own customer/session lookup yields a syntactically valid, consented email address. If you ask visitors for an email in a pre-chat form, ensure that field is stored in the contact or session data you can access server-side. If it is absent, acknowledge the webhook and do not send.
Registering the outgoing webhook
Create the website webhook in Crisp using the webhook management capability available through its API or administration tooling for your account. Point it at an HTTPS URL such as:
https://integrations.example.com/webhooks/crisp
Subscribe the webhook to message:received, rather than subscribing to every event and filtering later. Narrow subscriptions reduce needless traffic and make it less likely that a new Crisp workflow accidentally invokes your email sender.
Before enabling the production endpoint, use a staging Crisp website or a staging route. Send one test visitor message and log the raw request body safely, redacting email addresses and message contents in long-term logs. Real payload inspection is the fastest way to validate the fields available in your Crisp configuration and plan.
Map a Crisp message into an email deliberately
A good mapping distinguishes between event metadata, recipient data, and customer-provided content. The message text should almost never become the entire email body without escaping and formatting. It can include untrusted HTML-like characters, personal information, or content that should only be seen by your support team.
For an acknowledgment email, a sensible mapping is:
| Crisp value | Volanea value | Why |
|---|---|---|
data.session_id | metadata and idempotency key | Identifies the conversation that caused the send. |
data.message.timestamp | metadata and idempotency key | Helps identify the particular incoming message. |
data.message.content | escaped quoted excerpt | Provides context without injecting untrusted markup. |
| stored contact email | to | The recipient must come from a known contact field, not free text. |
| your verified domain address | from | Mail should be sent from an authenticated sender identity. |
| support inbox address | reply_to | Directs replies to a monitored mailbox. |
Do not automatically put the visitor’s raw message into a subject line. Subjects are frequently exposed in notifications and inbox previews, and a visitor may include an order number, address, or other sensitive text. A stable subject such as We received your support request is safer and more consistent.
The code below uses a helper named lookupCrispContactEmail. Its job is intentionally separate from the webhook parser: your implementation may obtain email from a server-side CRM record keyed by session_id, from a Crisp contact/session lookup, or from an explicitly captured form value. It returns null when the visitor cannot be safely emailed.
Working Node.js webhook-to-Volanea example
This Express route receives the Crisp event, filters it, looks up a recipient, escapes the message excerpt, and sends through Volanea. It also illustrates an idempotency key based on the Crisp session, event timestamp, and message content. Replace lookupCrispContactEmail with the lookup method used by your application.
import express from "express";
import crypto from "node:crypto";
const app = express();
app.use(express.json({ limit: "256kb" }));
const VOLANEA_API_KEY = process.env.VOLANEA_API_KEY;
const FROM_EMAIL = "Support <support@updates.example.com>";
const REPLY_TO = "support@example.com";
function escapeHtml(value = "") {
return value.replace(/[&<>'"]/g, (character) => ({
"&": "&", "<": "<", ">": ">",
"'": "'", "\"": """
}[character]));
}
async function lookupCrispContactEmail(sessionId) {
// Replace with your private CRM/Crisp-contact lookup.
// Return null for anonymous visitors or contacts without permission.
return process.env.TEST_RECIPIENT_EMAIL || null;
}
app.post("/webhooks/crisp", async (req, res) => {
const payload = req.body;
// Acknowledge only the event this endpoint was configured to handle.
if (payload?.event !== "message:received") {
return res.status(204).end();
}
const { website_id, session_id, message } = payload.data || {};
if (!website_id || !session_id || !message) {
return res.status(400).json({ error: "Missing Crisp event fields" });
}
// Avoid acknowledgments for operator/bot messages and non-text messages.
if (message.from !== "user" || message.type !== "text") {
return res.status(204).end();
}
const recipient = await lookupCrispContactEmail(session_id);
if (!recipient) {
return res.status(204).end();
}
const messageHash = crypto
.createHash("sha256")
.update(`${session_id}:${message.timestamp}:${message.content}`)
.digest("hex");
const excerpt = escapeHtml(message.content.slice(0, 1000));
const emailPayload = {
from: FROM_EMAIL,
to: [recipient],
reply_to: REPLY_TO,
subject: "We received your support request",
html: `<p>Thanks for contacting us. Our team has received your message.</p>
<p><strong>Your message</strong></p><blockquote>${excerpt}</blockquote>`,
text: `Thanks for contacting us. Our team has received your message.\n\nYour message:\n${message.content.slice(0, 1000)}`,
metadata: {
crisp_website_id: website_id,
crisp_session_id: session_id,
crisp_message_timestamp: String(message.timestamp || "")
}
};
const response = await fetch("https://api.volanea.com/v1/emails", {
method: "POST",
headers: {
"Authorization": `Bearer ${VOLANEA_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": `crisp-${messageHash}`
},
body: JSON.stringify(emailPayload)
});
if (!response.ok) {
const detail = await response.text();
console.error("Volanea send failed", response.status, detail);
// Return 5xx so the upstream delivery can be retried according to its policy.
return res.status(502).json({ error: "Email provider request failed" });
}
const result = await response.json();
console.info("Volanea email accepted", {
sessionId: session_id,
emailId: result.id
});
return res.status(202).json({ accepted: true, email_id: result.id });
});
app.listen(process.env.PORT || 3000);
The key field mapping is visible in the code: Crisp’s data.session_id and message.timestamp become metadata; a separately resolved contact email becomes to; a verified address becomes from; and the customer’s escaped text becomes a bounded excerpt. The Idempotency-Key prevents one delivery event from creating multiple email sends when the same event reaches your endpoint more than once.
Use a verified sending domain in FROM_EMAIL. A webhook integration cannot compensate for an unverified domain, weak authentication, or a from-address that does not align with your mail policy. Before moving from test recipients to customers, complete SPF, DKIM, and any required DMARC work for the domain you will use.
Secure the endpoint and API key
The Volanea API key belongs in a secret store or server environment variable, never in a Crisp chat widget, frontend build variable, browser local storage, or a static automation payload. Use a production key only in production and a separate restricted test key in staging if your account supports it. Rotate the key if it appears in a log, commit, support ticket, or client-side bundle.
Your webhook endpoint also needs protection. First, expose it only over HTTPS. Next, validate the webhook authenticity using the verification mechanism configured for your Crisp webhook, and reject requests that fail validation. Preserve the raw body when your verification method requires calculating a signature over the original bytes; parsing and reserializing JSON before verification can change the bytes and invalidate a signature.
Authentication answers “who called this endpoint,” but authorization answers “what is this event allowed to do.” Enforce both. Check the expected Crisp website ID, accept only subscribed event names, and make sure only message.from === "user" can trigger this acknowledgment. This prevents a malformed request or an internal operator message from becoming a customer email.
Avoid logging the authorization header, raw API key, full message text, or recipient address. If you need diagnostics, log a request ID, event name, session ID, hashed recipient identifier, result status, and Volanea message ID. Those fields are enough to trace a failure without turning application logs into a store of support conversations.
Design the email policy before enabling it
The technically correct event is not always the product-correct trigger. Sending an email for every incoming chat message can feel noisy, especially when the visitor is still in an active session and operators are replying quickly. Use a policy that reflects the experience you want to create.
Common policies include:
- Send a single acknowledgment for the first eligible customer message in a conversation.
- Send only after business hours, when an immediate chat response is unlikely.
- Send when a conversation moves to an internal “awaiting support” workflow, if that workflow event is available to your integration.
- Send a case-confirmation email only after your backend creates a ticket from the Crisp conversation.
- Send a follow-up after an inactivity delay, rather than on each message event.
The first-message policy is usually the safest starting point. Store a record keyed by the Crisp session ID, such as acknowledgment_sent_at, in your own database. If it already exists, return success without sending. This is more robust than relying only on the idempotency key, which handles duplicate delivery of one event but does not intentionally suppress later, distinct messages in the same conversation.
Respect marketing preferences separately from operational communication. A support acknowledgment may be transactional, but a promotional campaign triggered by a chat message is a marketing send and needs an appropriate legal basis and unsubscribe handling. Do not use a conversational support event as a shortcut around consent requirements.
When this breaks: Crisp webhook failure modes
A webhook integration is a distributed system: Crisp, your endpoint, your database, and Volanea can each fail independently. Plan for these specific failure modes before you depend on the flow.
Retries can create duplicate sends
Crisp can retry a webhook when it does not receive a successful response, and network failures can occur after Volanea accepts a request but before your relay receives the response. If your endpoint repeats the provider call on every retry, the customer may receive duplicate acknowledgments.
Use two layers of protection. First, supply a deterministic Volanea idempotency key derived from the event identity, as in the example. Second, store an application-level event or conversation record before or alongside the send. A durable table keyed by a hash of session_id, event name, timestamp, and message identifier/content hash lets you decide whether the event was already processed.
Do not mark an event permanently complete before you know the provider accepted it, unless your job queue has a separate recovery state. A practical state machine is received → sending → accepted or failed, with lease expiry for abandoned sending records.
Webhook timeouts are not email delivery failures
Your endpoint must respond promptly. A slow contact lookup, template render, database lock, or provider request can cause the webhook request to time out even if the eventual send succeeds. The result is a retry and, without idempotency, a duplicate.
For higher volume, acknowledge a validated webhook after storing a job and let a queue worker call Volanea. The endpoint’s responsibility becomes validation, deduplication, and durable enqueueing; the worker’s responsibility becomes recipient resolution, provider calls, and controlled retry. This separation makes timeouts much less likely and lets you apply exponential backoff without holding an inbound HTTP request open.
Return a 2xx response only when you have safely accepted responsibility for the event. If validation fails because the payload is malformed, return a 4xx and investigate configuration. If your database or queue is unavailable, return a 5xx so a retry remains possible.
Expected fields may be absent
Not every Crisp visitor has an email address, and event payloads can differ by message origin, channel, installed Crisp features, account configuration, and the data your own workspace collects. Attachments, file messages, bot messages, and social messages may not have the same useful text fields as a website-chat text message.
Code defensively: verify payload.event, data, session_id, and message before accessing nested fields; allow for missing timestamp; and skip non-text messages unless you have a deliberate template for them. Most importantly, treat recipient lookup failure as a normal no-send outcome, not as an opportunity to guess a recipient from chat content.
If your desired contact/profile lookup is unavailable in your Crisp plan or configuration, use an explicit fallback. Capture email in your own authenticated product account, ask for it through a supported pre-chat/contact flow, or route the event to a human workflow. Do not claim that a missing webhook field can be recovered by JavaScript running in the visitor’s browser.
Provider and sender failures need different responses
A 4xx response from the sending API often indicates a request problem: an invalid recipient, an unverified sender, malformed JSON, or an authorization error. Retrying it unchanged will not help. Record the failure, alert the integration owner where appropriate, and fix the input or configuration.
A 5xx response or transient network failure can be retried with bounded exponential backoff. Keep the same idempotency key across retries. Separately, use Volanea delivery events and suppression handling to understand messages accepted by the API but later bounced, deferred, or rejected by a recipient server. API acceptance is not the same as inbox placement.
Test the whole path safely
Test with an address you control and a non-production sender identity first. Make one visitor message, then inspect each boundary: Crisp webhook delivery, endpoint validation, database or queue record, Volanea acceptance response, and the delivered email. Save sanitized sample payloads as fixtures so future changes to the route can be tested without sending real mail.
A practical test checklist is:
- Confirm the endpoint rejects requests with the wrong event or website ID.
- Confirm an anonymous Crisp session produces no send.
- Confirm one eligible incoming text message creates exactly one provider request.
- Replay the identical webhook and confirm idempotency prevents a second email.
- Simulate a Volanea 500 response and confirm the job is retried safely.
- Send a message containing
<script>or HTML characters and confirm the quoted email excerpt is escaped. - Reply to the delivered email and confirm the
reply_tomailbox is monitored.
Measure the integration after launch. Useful metrics include webhook count, validation failures, no-recipient skips, accepted sends, API errors, deduplicated events, queue age, bounces, and complaints. A sudden increase in no-recipient skips may indicate a changed Crisp form or lookup failure; a sudden increase in duplicates often points to timeout handling or a lost idempotency record.
Alternatives when a direct relay is not right
A custom relay is the best fit when you need deterministic payload handling, secure credential storage, custom templates, or reliable deduplication. It is also a modest amount of code. If your team does not operate backend infrastructure, an automation platform can serve as the middle layer, but evaluate its trigger coverage and secret handling before adopting it.
With an automation tool, the conceptual flow remains the same: receive the Crisp event, filter for eligible customer messages, resolve a recipient, then make an authenticated HTTP request to Volanea. Store the Volanea API key in the automation platform’s encrypted connection or secret facility, not as text embedded in a public URL or frontend field. Add a persistent deduplication step if the platform can replay webhook runs.
Automation platforms are often good for prototypes and low-volume notification flows. They can become harder to govern when requirements grow: complex branching, customer data lookups, consent enforcement, retries, and audit trails are usually clearer in a small owned service. Choose based on your operational requirements, not only on the first hour of setup time.
Conclusion
To send email from Crisp reliably, use Crisp’s message:received webhook as the event source and keep Volanea behind a server-side relay. Map only validated, intentional fields into the email request, obtain the recipient from a trusted contact record, and hold the API key exclusively in server-side secrets.
The production details are what make the integration trustworthy: verify the incoming webhook, use a verified sender, suppress duplicate sends, acknowledge quickly, handle missing profile fields as normal, and distinguish API acceptance from eventual delivery. With those controls in place, a Crisp conversation can trigger useful email without creating a credential leak or a noisy customer experience.
FAQ
Does Volanea have a native Crisp integration?
No. This setup does not rely on a native Crisp app or Marketplace plugin. It uses Crisp’s outbound webhook event capability and a private endpoint that calls Volanea’s REST API.
Which Crisp event should trigger a support acknowledgment?
Use message:received when the rule is based on a visitor sending a message. Filter the payload so only message.from values representing a user and message types you support can create an email.
Where should I store the Volanea API key?
Store it in a server-side environment variable or secret manager used by your webhook relay. Never put it in the Crisp chat widget, browser JavaScript, a client-accessible config variable, or a raw webhook URL.
How do I stop duplicate emails when Crisp retries a webhook?
Use a deterministic idempotency key for the Volanea request and maintain a durable record of processed event IDs or message hashes. Return success only after the event is safely queued or sent.
What happens if a Crisp visitor has no email address?
Do not send. A missing email is a normal outcome for an anonymous chat session. Resolve recipients from a trusted contact/profile source or capture email through an explicit supported flow; never infer it from message text.