An Aircall email integration can send a useful follow-up the moment a call ends—but only if it treats Aircall’s call event as an operational signal rather than an invitation to expose an email key in a browser. This guide shows a server-side webhook pattern that receives Aircall’s call.ended event, selects a valid contact email address, and sends through Volanea’s REST email API.
Aircall does not need a native Volanea marketplace app for this to work. The durable implementation is an Aircall webhook pointed at an endpoint you control, with that endpoint performing validation, deduplication, and the outbound API request. That extra hop is important: it keeps credentials private, gives you a place to handle retries, and prevents every completed phone call from becoming an accidental duplicate email.
What this Aircall email integration does
The trigger in this implementation is Aircall’s call.ended webhook event. Aircall emits that event when a call reaches its end, making it a better automation point than call.created for follow-up messaging: duration, ending time, call direction, agent, and contact context are more likely to be settled.
The webhook receiver then applies a business rule. A straightforward example is: send a recap only after an outbound call, only when the associated contact has a usable email address, and only once per Aircall call ID. The receiver creates the actual email by calling Volanea from its server environment.
That yields this flow:
- An agent completes a call in Aircall.
- Aircall posts a
call.endedevent to your HTTPS webhook URL. - Your endpoint verifies the event, checks whether it has already processed the call, and extracts approved fields.
- Your endpoint calls Volanea’s email API using a server-held API key.
- Volanea accepts the message for delivery, while your application records the result and event ID.
This is deliberately not a browser automation. A customer, agent, or page visitor should never be able to inspect the Volanea credential, replace the recipient, or send arbitrary mail through your account.
Why use call.ended rather than a new-call event
Aircall exposes webhook events for changes in its calling environment. For a post-call email, call.ended is the practical event to subscribe to because the workflow depends on a call having actually concluded.
A call.created event can arrive before an agent has reached the person, before the call has been answered, or before a final call outcome is known. Sending at creation can produce an awkward “thanks for speaking with us” email after a call that was never connected. A call-ended workflow can inspect direction, duration, and any fields your account makes available before deciding whether to send.
Define the business rule before writing code
“Send an email after every call” is usually too broad. Start with a narrow, explainable rule that your sales or support team can test. For example:
- Send only for
outboundcalls that lasted at least 30 seconds. - Do not send when the contact has no email address in the Aircall data.
- Do not send for calls associated with a shared queue unless an assigned user is present.
- Suppress the follow-up when the contact has opted out in your CRM or customer database.
- Send a transactional recap, not an unsolicited marketing campaign, unless you have the appropriate consent and sending basis.
The key distinction is that a phone call is not automatically email permission. An event-triggered message can be highly relevant, but relevance does not eliminate consent, unsubscribe, privacy, or regional compliance obligations.
Know what the event is and is not
A webhook is an HTTP notification. It is not a guaranteed, ordered event stream and should not be treated as one. A delivery can be delayed, retried, or arrive more than once. Your receiver must therefore be idempotent: processing the same call event twice must not produce two messages.
Also, a webhook should not be used as the sole system of record for call history. Preserve the Aircall call ID and your processing result, but retrieve or reconcile call data through your established Aircall reporting or API process when an audit requires it.
Configure Aircall to deliver a webhook
Aircall supports webhooks through its developer integration capabilities. Create a webhook subscription for the call.ended event and set its destination to a publicly reachable HTTPS endpoint that you operate, such as:
https://integrations.example.com/webhooks/aircall/call-ended
Use a dedicated endpoint rather than sending every Aircall event to a general-purpose route. A narrowly scoped URL makes logs easier to interpret, lets you apply rate limits appropriate to call traffic, and reduces the chance that another integration processes call data accidentally.
The exact process for creating and managing a webhook depends on how your Aircall account and developer integration are set up. Follow Aircall’s current webhook documentation for the subscription configuration and available event list rather than relying on old screenshots or third-party setup instructions. API availability and connected-app capabilities can vary by Aircall plan and account configuration.
Keep the endpoint public but not open to abuse
Aircall must be able to reach the endpoint over the public internet, but that does not mean the endpoint should accept any arbitrary request as a real call event. At minimum:
- Require HTTPS and redirect neither the webhook request nor its POST body.
- Validate the expected event name before doing work.
- Put a high-entropy, unguessable value in the path or use the authentication/verification mechanism supported by your Aircall webhook configuration.
- Limit request body size and reject unexpected content types.
- Return a quick 2xx response after durable acceptance, not after slow downstream work has already timed out.
- Log a correlation ID and the Aircall call ID, never a full API key.
Do not assume that a field in an example payload is a cryptographic signature. Implement verification only according to the current Aircall webhook security documentation for your integration. If your setup does not provide a signed-request verification feature, use a secret endpoint path, network controls where appropriate, strict payload validation, and replay protection in your own service.
Understand the Aircall webhook payload before mapping it
Aircall webhook bodies are JSON envelopes with an event name, timestamp, and event data. A call event is structured around a data object containing the call record and related entities. The precise fields available can depend on the event, account configuration, and resources associated with the call, so treat optional nested values as optional in code.
The following is the relevant shape to design for. It illustrates the envelope and the common call-related objects; do not hard-code an assumption that every field, especially contact email data, will always be populated.
{
"event": "call.ended",
"timestamp": 1710000000,
"data": {
"id": 123456789,
"direct_link": "https://dashboard.aircall.io/calls/123456789",
"direction": "outbound",
"status": "done",
"started_at": 1710000000,
"answered_at": 1710000008,
"ended_at": 1710000122,
"duration": 114,
"raw_digits": "+14155550100",
"user": {
"id": 42,
"name": "Avery Agent"
},
"contact": {
"id": 99,
"first_name": "Jordan",
"last_name": "Lee",
"emails": [
{ "label": "work", "value": "jordan@example.com" }
]
},
"number": {
"id": 7,
"name": "Sales"
}
}
}
In production, record a redacted sample from your own test call and compare it with the current Aircall webhook schema. That is especially important for nested contact information. The contact might be absent, the contact might not have email data, or an email may live in a CRM rather than in Aircall. Phone-number matching alone is not a safe substitute for a verified email address.
Decide which contact email is allowed
A contact can have zero, one, or multiple addresses. Do not simply pick the first string that resembles an email. Your integration should define a selection policy, such as preferring an explicitly labelled work address, then another verified address held in your CRM, and otherwise not sending.
Before enabling a workflow, use an email address verification tool to test representative contact data and understand how often imported addresses are malformed or disposable. Verification does not establish consent, but it can prevent obvious bounces and make a missing-data problem visible before an agent triggers it.
A simple outbound-call filter can look like this:
const call = payload.data;
const emails = Array.isArray(call.contact?.emails) ? call.contact.emails : [];
const recipient = emails.find((item) => item.label === 'work')?.value
?? emails[0]?.value;
const shouldSend =
payload.event === 'call.ended' &&
call.direction === 'outbound' &&
Number(call.duration ?? 0) >= 30 &&
typeof recipient === 'string' && recipient.includes('@');
That includes('@') check is only a guard against an obviously wrong value. The actual receiver below uses a more restrictive email validator, and a mature system should additionally check suppression and consent records before sending.
Send the Volanea message from server-side code
The code below is a complete Node.js/Express pattern for the integration hop. It accepts the Aircall event, ignores unrelated or ineligible calls, uses the Aircall data.id as an idempotency key, then sends an HTML and text email through Volanea.
The Volanea key is read from VOLANEA_API_KEY in the server environment. It is not placed in Aircall, in frontend JavaScript, in a mobile application, or in an email template. Consult the Volanea API reference and setup guides for the current endpoint, authentication scheme, and message fields for your account before deploying.
import express from 'express';
import crypto from 'node:crypto';
const app = express();
app.use(express.json({ limit: '256kb' }));
const processedCallIds = new Set(); // Replace with Redis or a database in production.
function escapeHtml(value = '') {
return String(value)
.replaceAll('&', '&')
.replaceAll('<', '<')
.replaceAll('>', '>')
.replaceAll('"', '"')
.replaceAll("'", ''');
}
function validEmail(value) {
return typeof value === 'string' &&
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
}
function selectRecipient(contact) {
const emails = Array.isArray(contact?.emails) ? contact.emails : [];
const preferred = emails.find((email) => email?.label === 'work' && validEmail(email.value));
const fallback = emails.find((email) => validEmail(email?.value));
return preferred?.value ?? fallback?.value ?? null;
}
app.post('/webhooks/aircall/call-ended', async (req, res) => {
const payload = req.body;
// Accept only the subscribed Aircall event. Add Aircall-supported request
// verification here if it is configured for your webhook.
if (payload?.event !== 'call.ended' || !payload?.data?.id) {
return res.status(204).end();
}
const call = payload.data;
const callId = String(call.id);
const recipient = selectRecipient(call.contact);
const duration = Number(call.duration ?? 0);
// Example business rule: outbound, connected calls lasting 30+ seconds.
if (call.direction !== 'outbound' || duration < 30 || !recipient) {
return res.status(204).end();
}
// In production, atomically insert callId with a unique database constraint
// before sending. A Set is only illustrative and is lost on restart.
if (processedCallIds.has(callId)) {
return res.status(204).end();
}
processedCallIds.add(callId);
const firstName = escapeHtml(call.contact?.first_name || 'there');
const agentName = escapeHtml(call.user?.name || 'our team');
const callLink = typeof call.direct_link === 'string' ? call.direct_link : '';
const requestId = crypto.randomUUID();
const emailPayload = {
from: { email: 'followup@example.com', name: 'Example Company' },
to: [{ email: recipient, name: `${call.contact?.first_name ?? ''} ${call.contact?.last_name ?? ''}`.trim() }],
subject: `Thanks for speaking with ${agentName}`,
text: `Hi ${call.contact?.first_name || 'there'},\n\nThanks for your time today. Reply to this email if you have any questions.\n\n— ${agentName}`,
html: `<p>Hi ${firstName},</p><p>Thanks for your time today. Reply to this email if you have any questions.</p><p>— ${agentName}</p>`,
headers: {
'X-Aircall-Call-ID': callId,
'X-Integration-Request-ID': requestId
},
tags: [
{ name: 'source', value: 'aircall' },
{ name: 'event', value: 'call.ended' }
]
};
try {
const response = await fetch('https://api.volanea.com/v1/emails', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.VOLANEA_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': `aircall-call-ended-${callId}`
},
body: JSON.stringify(emailPayload)
});
if (!response.ok) {
const detail = await response.text();
processedCallIds.delete(callId); // Let durable retry logic decide what to retry.
console.error({ callId, requestId, status: response.status, detail });
return res.status(502).json({ error: 'Volanea send failed' });
}
// Store callId, requestId, Volanea message ID, and sent timestamp durably here.
return res.status(202).json({ accepted: true, callId, requestId, callLink });
} catch (error) {
processedCallIds.delete(callId);
console.error({ callId, requestId, error: error.message });
return res.status(503).json({ error: 'Temporary send failure' });
}
});
app.listen(3000, () => console.log('Aircall webhook receiver listening on :3000'));
The field mapping is explicit: data.contact.emails[].value becomes the recipient, data.contact.first_name becomes the greeting, data.user.name is the agent reference, and data.id becomes the cross-system identifier. Keep that mapping intentionally small. There is no need to place raw call notes, recordings, telephone numbers, or other sensitive call content into an email merely because it was present in the event.
Protect the Volanea API key
The Volanea API key belongs only in the trusted server-side component that makes the API call. In the example, that means a secret manager or protected environment variable named VOLANEA_API_KEY on the webhook receiver’s hosting platform.
Do not put the key in any of these locations:
- An Aircall custom field, note, or user-visible configuration value.
- Client-side JavaScript, a browser extension, or a static website build variable.
- A Git repository, code sample committed with real credentials, or a ticket attachment.
- An email template or webhook URL query parameter.
- Plaintext application logs or error responses.
Rotate the key if it is ever exposed. Use a key with the least practical scope, separate development and production keys, and make the sender identity in the API payload a domain you have authenticated for Volanea. Domain authentication, aligned sender identities, and a consistent sending pattern are deliverability controls—not optional polish after the integration is live.
Do not confuse Aircall authentication with Volanea authentication
Aircall’s credentials are for configuring or operating the Aircall integration. Volanea’s credential authorizes mail sending. They should be stored separately, rotated separately, and never forwarded from one system to another.
Your server can receive an Aircall request and then create a new authenticated request to Volanea. That separation is precisely why middleware is valuable. Aircall does not need access to your Volanea secret to cause an approved email to be sent.
When this breaks: failures at the Aircall-to-Volanea hop
This workflow has two independent network deliveries: Aircall to your receiver, then your receiver to Volanea. Build for failure at both boundaries rather than interpreting the first successful test as proof of reliability.
Aircall retries can create duplicate sends
Webhook providers may retry when they do not receive a timely successful response or when there is a transient network problem. A retry can carry the same Aircall call ID. If your service sends the email and then crashes before it returns its success response, the retry is especially dangerous: Aircall sees a failed delivery while the contact has already received a message.
Use a durable idempotency record keyed by the event type and data.id, for example call.ended:123456789. Insert it atomically with a unique constraint before sending, record the Volanea response, and retain the record long enough to cover realistic retry windows. Also pass the same key to Volanea if its current API supports idempotency headers, as shown in the example.
Do not rely on an in-memory Set outside a demonstration. It does not work across multiple instances, deployments, or process restarts.
Webhook timeouts create an ambiguous outcome
If your endpoint waits for Volanea, a database, a CRM lookup, and a template render before replying, it can exceed the sender’s webhook timeout. The response may be a timeout even when Volanea subsequently accepts the message.
For higher-volume or business-critical flows, make the receiver fast: validate and persist a job, return success, then process the job from a queue. The worker should use the same idempotency key and update a durable state machine such as received, eligible, sending, accepted, failed_retryable, or suppressed.
This design also gives operators a retry button that reuses the same call ID rather than creating a brand-new message without context.
Some payload fields will be absent
Never assume contact, contact.emails, user, duration, recording data, transcription data, or other optional related resources are universally present. A call may involve an unknown number, an unlinked contact, an integration-created contact with incomplete details, or account features that are not enabled.
Plan and product entitlements can also affect which Aircall capabilities and enriched data are available. Do not make a workflow’s core safety depend on a recording, AI transcript, or any premium enrichment field being available. The safe fallback for a missing recipient email is to skip the send, log a structured reason such as no_contact_email, and optionally create an internal task for the agent.
API errors are not all retryable
A network failure, HTTP 429, or server-side 5xx response may merit a bounded retry with backoff. A malformed recipient, missing authenticated sender, invalid request body, or unauthorized API key usually requires configuration or data correction—not repeated attempts.
Store the response status and a redacted error category. Avoid putting full contact data or authorization headers in logs. A dead-letter queue for terminal or exhausted failures lets support staff inspect the problem without silently dropping the follow-up.
Add the operational controls that keep email useful
The minimal code sends a message, but production quality depends on the rules around it. Add suppression checks before the Volanea call. Those checks should include your unsubscribe list, account status, contact preferences, recent-send frequency, and any legal or geographic rules applicable to your organization.
Frequency controls matter because a customer might have several short calls in one day. A practical policy could limit automated recap messages to one per contact per 24 hours, unless the message is a necessary service transaction. Use the contact’s canonical ID in your CRM when available; email-address-only controls can miss duplicate records.
Use event tags for observability
Attach tags or metadata that identify the source and trigger, such as source=aircall and event=call.ended. This helps separate call-driven mail from product notifications and campaigns in delivery monitoring.
Keep personally sensitive context out of tags. An Aircall call ID is generally more appropriate as an internal correlation value than a contact phone number, a call transcript, or a detailed disposition. In your own database, relate that ID to the internal records subject to your access controls and retention policy.
Test with realistic calls
Before enabling the webhook for all agents, make controlled test calls that cover:
- An outbound connected call with a work email address.
- An inbound call, which should be skipped under the example rule.
- A short outbound call, which should be skipped under the duration rule.
- A contact with no email address.
- The same event delivered twice, which must create one email at most.
- A temporary Volanea failure and a later successful retry.
Check both the recipient experience and the operational trace. You should be able to connect the Aircall call ID, webhook receipt, queue job, Volanea request ID, and message outcome without searching through unstructured logs.
Use Zapier or Make when you cannot host middleware
A direct webhook receiver is the strongest option when you need custom suppression logic, full idempotency control, or predictable operational ownership. If your team cannot deploy a small service, Aircall’s Zapier integration can provide a no-code route using its New Call trigger, followed by a server-side webhook/custom request step or an intermediary service.
However, do not paste a Volanea production key into any client-visible field. In a workflow tool, use its server-side connection or encrypted credential facility where available, restrict workspace access, and understand exactly who can view workflow configuration and run history. A workflow tool is still middleware; it is not a reason to weaken key management.
Zapier and Make can be appropriate for lower-risk, low-volume automations, particularly while validating the email copy and eligibility rules. For a workflow where duplicate sends, consent decisions, or call-data handling have material consequences, a dedicated receiver and queue are easier to test and audit.
Conclusion
A dependable Aircall email integration begins with Aircall’s call.ended event, but it should not end with a direct, unguarded API call. Receive the event in a trusted server-side service, map only the fields you need, skip incomplete contacts, enforce consent and frequency rules, and make the Aircall call ID your idempotency anchor.
That approach keeps the Volanea API key out of client-visible configuration, makes webhook retries safe, and gives your team a traceable path from a completed call to a delivered follow-up. Start with one narrow call outcome and one tested template, then expand only after you can observe delivery, suppressions, failures, and duplicates clearly.
FAQ
Does Volanea have a native Aircall marketplace integration?
No. This setup uses Aircall webhooks and a server-side integration service that calls Volanea’s REST API. It does not require, or claim to install, a native Aircall marketplace app.
What Aircall event should send the follow-up email?
Use call.ended for a post-call follow-up. It represents a completed call and gives your rules a chance to evaluate the final call context before sending.
Where should the Volanea API key be stored?
Store it in a secret manager or protected server environment variable used only by the webhook receiver or worker. Never place it in frontend code, a public webhook URL, or user-visible Aircall configuration.
How do I stop duplicate emails after a webhook retry?
Create a durable idempotency record using the Aircall call ID and event name before sending. Reuse that key for retries and, where supported, send it as the outbound provider’s idempotency key.
What happens if the Aircall contact has no email address?
Skip the email, record a structured reason, and optionally create an internal task. Do not infer an email address from a phone number or send to an unverified fallback address.