Send email from CloudTalk after a call ends by using CloudTalk’s account-level webhooks as the trigger and a small server-side service to call Volanea’s REST API. This approach does not require a native CloudTalk app or marketplace plugin, keeps your sending key private, and gives you the controls needed to prevent duplicate follow-up emails.
CloudTalk can send signed JSON events to an HTTPS endpoint when activity occurs in your phone system. For a common sales or support workflow, the concrete trigger is the call.ended webhook event: CloudTalk creates and delivers that event when a call has ended. Your middleware receives it, identifies a contact email address in the event payload, applies your business rules, and sends an email through Volanea with POST https://api.volanea.com/v1/send.
This guide builds the reliable version of that flow. It is designed for teams that want to send a short post-call recap, a missed-call acknowledgement, a promised resource, or a booking link—not a bulk campaign to every person who has ever called.
The integration architecture: CloudTalk webhook to Volanea API
CloudTalk offers several ways to connect external systems, including Workflow Automations and HTTP Request steps in Call Flow Designer. For post-call email, though, account-level webhooks are usually the best fit because CloudTalk documents the webhook event model, signing scheme, delivery log, replay controls, and retry behavior.
The recommended architecture has three parts:
- CloudTalk account-level webhook: CloudTalk emits a signed
call.endedevent to an HTTPS URL you control. - Your middleware endpoint: A small Node.js service, serverless function, or queue-backed worker validates the webhook signature, makes an eligibility decision, and records the event as processed.
- Volanea REST API: The worker sends the actual transactional message through Volanea using a server-side secret key and a stable idempotency key.
This is intentionally not a browser integration. The Volanea secret key must never be embedded in a web page, client-side JavaScript bundle, mobile app, CloudTalk contact custom field, or any configuration a non-admin user can retrieve.
CloudTalk’s webhook event envelope includes an event_id, a type, an occurred_at timestamp, and an event-specific data object. Call events share a call_uuid, which lets you treat the call as one logical record even as CloudTalk emits multiple lifecycle events such as call.started, call.answered, and call.ended.
For this use case, call.ended is the right starting point because it indicates the conversation is complete. A call-start trigger can be useful for internal logging, but it is a poor trigger for a customer-facing follow-up: the call may not connect, may be transferred, or may still be in progress.
What actually starts the email
The concrete CloudTalk trigger in this guide is the account-level call.ended webhook event.
Set up an endpoint in CloudTalk under Account → Webhooks, choose the call.ended event type, and point it to an HTTPS endpoint owned by your application. CloudTalk webhooks are available on the Essential plan and higher, and you need an Admin role to manage them.
A call ending does not automatically mean an email should send. Your service should make that decision explicitly. For example, a useful policy might be:
- Send a recap only after an answered outbound sales call.
- Send a missed-call acknowledgement only for inbound calls that meet your missed-call criteria.
- Send a resource email only when the agent applies a specific CloudTalk tag or records a qualifying disposition.
- Do not send if the contact has no email address, is suppressed, has opted out of this communication category, or has already received the same follow-up for the same call.
Treat call.ended as the signal that tells your system to evaluate a follow-up—not as permission to send indiscriminately.
Why not send directly from a CloudTalk Call Flow HTTP Request step?
CloudTalk’s Call Flow Designer includes an HTTP Request step that can call external HTTPS endpoints with methods including GET, POST, PUT, PATCH, and DELETE. It can send a JSON request body and insert dynamic call and contact values, such as {{call.uuid}}, {{contact.name}}, and contact custom fields.
That can be useful for real-time routing decisions during a call. It is not the preferred route for this email workflow for two reasons:
- A post-call follow-up benefits from webhook signing, delivery visibility, replay tools, and a durable event-processing layer.
- A direct request would require putting the Volanea Authorization bearer token in an external-request header configured in CloudTalk. Even if restricted to CloudTalk admins, that makes the email credential part of telephony workflow configuration rather than a dedicated server-side secret store.
Use the account webhook plus middleware when you need secure secret handling, durable deduplication, richer rules, and reliable observability. Use a Call Flow HTTP Request only for a narrowly scoped real-time integration where you understand the latency and credential trade-offs.
The CloudTalk payload you need to handle
CloudTalk account-level webhooks send signed JSON with a common event envelope. Empty optional fields are omitted rather than sent as null, so your code must not assume every contact, email address, user, note, or optional call attribute exists.
For a call.ended workflow, the fields that matter most are:
event_id: CloudTalk’s unique ID for this event. Use it for webhook-level deduplication and audit records.type: The event name. Your endpoint should only processcall.endedfor this workflow.occurred_at: When CloudTalk recorded the event.data.call_uuid: The stable ID that ties call lifecycle events together.data.contacts: Matching CloudTalk contacts associated with the call, when available.data.contacts[].emails: The contact email addresses, when present.data.contacts[].name: The contact display name, when present.data.user: The CloudTalk user handling the call, when one is assigned.
A representative shape for the fields this handler reads looks like this:
{
"event_id": "018f5d3b-8c4e-8f6a-9b2c-3d4e5f607182",
"type": "call.ended",
"version": "v1",
"occurred_at": "2026-08-17T14:22:08.412Z",
"company_id": "123456",
"data": {
"call_uuid": "018f9a3e-5b7c-4d21-9e8f-aa12bb34cc56",
"contacts": [
{
"id": "44210",
"name": "Jane Doe",
"emails": ["jane.doe@example.com"]
}
],
"user": {
"id": "4207",
"name": "Jordan Lee",
"email": "jordan.lee@example.com"
}
}
}
Do not build production logic around every field in that example being present. CloudTalk documents contact fields as optional apart from the contact ID, and its webhook documentation explicitly notes that empty values are omitted. A call can be associated with no resolved contact, a contact can have no stored email address, and a user can be omitted when no user is assigned.
The safest policy is simple: if the event cannot provide a valid recipient address, acknowledge the webhook after recording the reason, but do not attempt a send. If you use a CRM as the source of truth for email addresses, use the CloudTalk contact ID or call UUID to look up the recipient in your own system instead of assuming CloudTalk always carries the final address.
Configure the CloudTalk webhook
Before writing application code, create the webhook endpoint in CloudTalk.
1. Deploy an HTTPS endpoint first
Create a publicly reachable HTTPS endpoint, for example:
https://example.yourcompany.com/webhooks/cloudtalk
CloudTalk’s account-level webhook delivery requires HTTPS. Do not point the webhook directly at a local development server unless you are using a secure tunnel for temporary testing.
2. Add the endpoint in the CloudTalk dashboard
In CloudTalk, go to Account → Webhooks and choose Add endpoint. Enter the endpoint URL, optionally provide a description, and select call.ended as the event subscription.
Keeping the subscription narrow is a practical safety measure. An endpoint subscribed to every event has more payload types to secure, store, and reason about. Subscribe to additional events only when the business workflow genuinely needs them.
3. Reveal and store the signing secret
Open the endpoint after creating it and reveal its signing secret. CloudTalk signs account-level webhook deliveries using the Svix webhook signing scheme with HMAC-SHA256. Each delivery carries these headers:
svix-idsvix-timestampsvix-signature
Store the endpoint signing secret in your server-side secret manager as CLOUDTALK_WEBHOOK_SECRET. It is not the Volanea API key; it is the credential your endpoint uses to verify that the request came from CloudTalk and was not altered in transit.
4. Send a test event
CloudTalk lets you send a test event from the endpoint configuration. Use it after deployment to confirm that your endpoint returns a 2xx response and that your application logs a verified event.
A test that returns HTTP 200 but skips signature verification is not a successful security test. Verify the incoming request first, store it durably, then acknowledge it.
Send the Volanea email with secure field mapping
Volanea’s transactional endpoint is POST /v1/send at https://api.volanea.com. It accepts a project API key as a bearer token. Secret keys begin with sk_ and are server-side credentials; public keys are not accepted for email sending.
The handler below uses Express and the Svix library. It does four important things:
- Preserves the raw request body for signature verification.
- Verifies the CloudTalk webhook before trusting any fields.
- Uses
event_idfor inbound webhook deduplication andcall_uuidfor Volanea send idempotency. - Maps a verified CloudTalk contact name and email address to a Volanea recipient.
Install the dependencies:
npm install express svix
Set server-side environment variables:
CLOUDTALK_WEBHOOK_SECRET=whsec_your_cloudtalk_endpoint_secret
VOLANEA_API_KEY=sk_your_volanea_secret_key
VOLANEA_FROM="Sales Team <sales@example.com>"
Then use this handler:
import express from "express";
import { Webhook } from "svix";
const app = express();
// Keep raw bytes. Do not use express.json() on this route before Svix verifies it.
app.post(
"/webhooks/cloudtalk",
express.raw({ type: "application/json" }),
async (req, res) => {
let event;
try {
const verifier = new Webhook(process.env.CLOUDTALK_WEBHOOK_SECRET);
event = verifier.verify(req.body, {
"svix-id": req.headers["svix-id"],
"svix-timestamp": req.headers["svix-timestamp"],
"svix-signature": req.headers["svix-signature"]
});
} catch (error) {
console.error("Rejected CloudTalk webhook signature", error);
return res.status(401).send("invalid signature");
}
// This endpoint only sends a post-call message for completed calls.
if (event.type !== "call.ended") {
return res.status(204).send();
}
const callUuid = event.data?.call_uuid;
const contact = event.data?.contacts?.[0];
const recipientEmail = contact?.emails?.[0];
const recipientName = contact?.name || "there";
const agentName = event.data?.user?.name || "our team";
// Save event.event_id in a database with a UNIQUE constraint before sending.
// If it already exists, it is a duplicate delivery and can be acknowledged safely.
const alreadyProcessed = await hasProcessedWebhook(event.event_id);
if (alreadyProcessed) {
return res.status(200).send("duplicate acknowledged");
}
await recordWebhookReceived({
eventId: event.event_id,
eventType: event.type,
callUuid,
occurredAt: event.occurred_at
});
// Missing email is a business-rule skip, not an API failure.
if (!callUuid || !recipientEmail) {
await markWebhookSkipped(event.event_id, "missing call UUID or contact email");
return res.status(200).send("no eligible recipient");
}
// Put further policy checks here: consent, suppression, call outcome,
// CloudTalk tags, CRM status, or a once-per-call rule.
const subject = "Thanks for speaking with us";
const text = [
`Hi ${recipientName},`,
"",
`Thanks for speaking with ${agentName}. If you need anything else, reply to this email and our team will help.",
"",
"Best,",
"The Sales Team"
].join("\n");
const html = `
<p>Hi ${escapeHtml(recipientName)},</p>
<p>Thanks for speaking with ${escapeHtml(agentName)}. If you need anything else, reply to this email and our team will help.</p>
<p>Best,<br>The Sales Team</p>
`;
const volaneaResponse = await fetch("https://api.volanea.com/v1/send", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.VOLANEA_API_KEY}`,
"Content-Type": "application/json",
// Stable per call and message purpose: retries do not create another email.
"Idempotency-Key": `cloudtalk-call-ended:${callUuid}:follow-up-v1`
},
body: JSON.stringify({
from: process.env.VOLANEA_FROM,
to: [{ email: recipientEmail, name: contact?.name }],
subject,
html,
text
})
});
const responseBody = await volaneaResponse.text();
if (!volaneaResponse.ok) {
console.error("Volanea send failed", {
status: volaneaResponse.status,
responseBody,
eventId: event.event_id,
callUuid
});
// Returning a non-2xx tells CloudTalk this delivery was not accepted.
// Your database should retain the event for diagnosis and recovery.
return res.status(502).send("email send failed");
}
await markWebhookSent(event.event_id, {
callUuid,
recipientEmail,
volaneaResponse: responseBody
});
return res.status(200).send("sent");
}
);
function escapeHtml(value = "") {
return String(value)
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll('"', """)
.replaceAll("'", "'");
}
// Replace these with database-backed implementations.
async function hasProcessedWebhook(eventId) {
return false;
}
async function recordWebhookReceived(record) {
console.log("received", record);
}
async function markWebhookSkipped(eventId, reason) {
console.log("skipped", { eventId, reason });
}
async function markWebhookSent(eventId, details) {
console.log("sent", { eventId, details });
}
app.listen(3000, () => {
console.log("Listening on port 3000");
});
The mapping is deliberate:
| CloudTalk webhook field | Volanea send field | Why it is used |
|---|---|---|
data.contacts[0].emails[0] | to[0].email | Delivers to the first verified contact email present in the event. |
data.contacts[0].name | to[0].name and greeting | Personalizes the recipient record and message content. |
data.user.name | Email body | Identifies the assigned CloudTalk user when present. |
data.call_uuid | Idempotency-Key | Makes the follow-up stable across retries for the same call. |
event_id | Your webhook receipt table | Prevents repeat processing of the same CloudTalk event delivery. |
The example uses one recipient. That is the safest default because a single call normally belongs to one customer interaction. If your account can attach multiple contacts to a call, do not blindly email every address in contacts. Decide whether the correct behavior is first eligible contact, a CRM-selected primary contact, or no automatic send until an agent chooses the recipient.
For endpoint details, message fields, test keys, and delivery behavior, see the Volanea API reference and setup guides.
Where the Volanea API key belongs
In the recommended webhook architecture, the Volanea API key lives nowhere on the CloudTalk side.
Store VOLANEA_API_KEY in the secret manager or encrypted environment-variable facility used by your serverless platform, container host, or application runtime. The CloudTalk dashboard contains only the HTTPS webhook URL and CloudTalk’s endpoint signing secret. Your middleware owns the Volanea credential and performs the outbound API request.
This separation matters for both security and operations:
- Least privilege: CloudTalk can notify your endpoint about a call, but it cannot independently send arbitrary email through your Volanea account.
- Key rotation: You can replace the Volanea key without editing phone-routing or webhook subscriptions in CloudTalk.
- Auditability: Your application can log exactly which verified event caused each email send.
- Environment separation: Development can use a Volanea test key while production uses a production secret, without exposing either in browser-accessible configuration.
- Blast-radius reduction: If a webhook URL is discovered, it still cannot send email because signature verification rejects unsigned requests.
Do not confuse CloudTalk’s webhook signing secret with Volanea’s bearer token. The first validates incoming CloudTalk requests. The second authorizes outbound email sending. They should be stored separately, rotated independently, and never logged.
What if you need a no-code route?
CloudTalk supports Zapier and Make as automation bridges. A no-code route can be useful for a prototype, but it still needs thoughtful credential handling and deduplication.
The broad flow is:
- Use CloudTalk as the trigger source for a call event in your automation platform.
- Filter for the relevant finished-call condition.
- Map a contact email into a Webhooks action that calls a server-side endpoint you own.
- Let that endpoint verify, deduplicate, and call Volanea.
Avoid placing a Volanea secret key directly into an automation step unless the platform provides an appropriate secret mechanism, access controls, and audit trail that meet your organization’s requirements. Even then, middleware remains the stronger design when sending behavior depends on consent, CRM data, timing rules, or regulated customer communications.
Make the post-call email useful, not noisy
A phone call is a high-context interaction. The best automatic email extends that context without pretending it knows more than it does.
Suitable transactional follow-ups
Good candidates include:
- A promised link, guide, quote, or document after an agent has explicitly committed to sending it.
- A missed-call acknowledgement that provides a reply channel or booking link.
- A case or support reference confirmation after a customer service call.
- A meeting confirmation or next-step summary after a qualified conversation.
- A short request for information that the caller agreed to provide.
Risky automatic follow-ups
Avoid making these automatic from every call.ended event:
- A generic promotional sequence after any inbound call.
- A transcript-heavy recap containing sensitive details.
- An email to a guessed address based on a phone-number enrichment service.
- A message that claims an outcome the agent did not record.
- Multiple messages when a transferred call creates several lifecycle events or related records.
The less certain your system is about consent, recipient identity, or message relevance, the more conservative the automation should be. A correctly skipped email is better than an unwanted one.
A strong operational pattern is to require a specific marker. For example, an agent can apply a CloudTalk tag that means “send follow-up,” or your CRM can set a custom field after the agent selects an approved outcome. Your middleware checks that marker before constructing the Volanea message.
When this breaks: failures specific to the CloudTalk-to-email hop
No webhook integration is complete until it has failure behavior. CloudTalk and Volanea each have reliable mechanisms, but your application is responsible for joining them safely.
CloudTalk retries can create duplicate send attempts
For account-level webhooks, CloudTalk considers delivery successful only when your endpoint returns a 2xx response within 15 seconds. Failed attempts—such as timeouts, connection failures, 4xx or 5xx responses, and redirects—are retried automatically. CloudTalk documents up to 50 retries over roughly 11.5 hours.
That means this sequence is possible:
- Your endpoint calls Volanea successfully.
- Your server is slow while writing a database record or response.
- CloudTalk does not receive a timely 2xx response.
- CloudTalk retries the same webhook event.
- Your endpoint receives the event again.
Prevent duplicate email in two layers:
- Create a database record keyed by CloudTalk
event_idwith a unique constraint. If the event already exists, return 200 without sending again. - Use a stable Volanea
Idempotency-Key, such ascloudtalk-call-ended:<call_uuid>:follow-up-v1. If a network failure occurs after Volanea receives the request but before your service receives its response, retrying with the same key avoids creating another send.
Do not generate a random idempotency key on every retry. A random key defeats the entire protection because each retry looks like a new message.
A webhook timeout can happen even when your code works
CloudTalk’s 15-second delivery window is short enough that long database work, CRM enrichment, AI processing, or a slow email-provider request can turn a successful action into an apparent webhook failure.
The production-grade model is:
- Verify the signature from raw bytes.
- Store the event durably with a unique event ID.
- Enqueue work for a background worker.
- Return HTTP 200 promptly.
- Let the worker evaluate rules and call Volanea.
This creates a clear distinction between receiving a CloudTalk event and completing an email send. It also lets you retry email work independently without forcing CloudTalk to re-deliver an already accepted event.
If you choose to process synchronously for a small deployment, keep the handler minimal and measure its response time. Once you add CRM API lookups, template selection, or multiple downstream systems, move to a queue.
Payload fields can be missing
CloudTalk webhook payloads omit empty optional fields. The contact may not resolve, the contact may have no email address, the user may not be assigned, or fields you use in one account configuration may not exist in another.
Build explicit fallback paths:
- No
contactsarray: look up the call or customer in your CRM using the call UUID, or skip. - Contact exists but
emailsis empty: do not send; record the skip reason. - No assigned
user: use a generic team signature instead of a broken greeting. - Multiple contact emails: use your CRM’s primary-email rule rather than selecting every recipient.
- Missing business marker, such as an approved tag: skip rather than infer intent.
Plan availability matters too. CloudTalk states that API access and account-level webhooks are available from the Essential plan. If your account does not display Account → Webhooks, confirm that the account plan and your CloudTalk role support it before troubleshooting the application.
Your webhook endpoint might be disabled after repeated failures
CloudTalk can automatically disable an endpoint that fails continuously for about five days. It sends administrative notifications around disablement and re-enablement, and provides delivery logs plus recovery options for failed events.
Monitor for these conditions outside CloudTalk as well:
- A sudden increase in non-2xx webhook responses.
- Signature-verification failures.
- Queue backlogs.
- Volanea API errors or rate-limit responses.
- A high skip rate due to missing contact emails.
- A high idempotency-replay rate, which may indicate repeated retries or a stuck worker.
A dashboard that shows only “emails sent” is not enough. You need visibility into received events, ignored events, skipped recipients, queued work, successful Volanea responses, and failed sends.
A direct Workflow Automation request is not the same as an account webhook
CloudTalk Workflow Automations can send HTTP requests to an endpoint you control, but CloudTalk describes that delivery as best-effort and notes that an unreachable endpoint can miss a delivery without a self-service replay option. Account-level webhooks have their own signed delivery and replay model.
For an email that must be traceable and recoverable, prefer the account-level call.ended webhook. Use a Workflow Automation HTTP action when the action is intentionally lightweight and you accept the delivery characteristics of that path.
Test the integration before enabling real follow-ups
Use a staged rollout rather than immediately enabling email for every completed call.
Start with a Volanea test key
Volanea test keys run the rendering and logging pipeline without delivering to real recipients. Use one while verifying CloudTalk payload mappings, signature validation, template output, and idempotency behavior.
Test these cases deliberately:
- A completed call with one CloudTalk contact and one email address.
- A completed call with no matching contact.
- A contact with no email address.
- A contact with multiple email addresses.
- A repeated CloudTalk delivery of the same
event_id. - A retry after the Volanea request reaches the API but your worker loses the response.
- An invalid
svix-signaturerequest sent directly to your endpoint. - A call with no assigned CloudTalk user.
Your expected result should be specific for each test. “The endpoint returned 200” is not enough. Confirm that eligible events result in one message, ineligible events result in a recorded skip, duplicate events result in no additional message, and invalid signatures result in HTTP 401.
Use a production-safe launch rule
When you switch to a production secret key, begin with a narrow condition such as one internal test number, one CloudTalk tag, or one agent team. Review message logs and recipient experience before widening the policy.
This also helps your deliverability program. Transactional messages should come from a verified sending domain with an appropriate From identity, authenticated DNS, readable content, and a predictable message purpose. If you are planning volume or want to understand sending limits and plan design, review transactional email pricing and sending costs before rollout.
Conclusion
The secure way to send email from CloudTalk with Volanea is not a native plugin installation. It is a webhook-driven workflow: CloudTalk emits a signed call.ended event, your server verifies and records it, and a worker sends a targeted transactional message through Volanea’s REST API.
This design protects the Volanea API key, handles CloudTalk retries without duplicate email, survives missing optional payload fields, and gives you a durable audit trail from call UUID to email send. Start with one narrowly defined use case, make eligibility rules explicit, and use stable idempotency keys for every post-call message.
FAQ
Can CloudTalk send email directly through Volanea?
CloudTalk can make outbound HTTP requests through features such as Call Flow Designer and Workflow Automations, but Volanea does not provide a native CloudTalk marketplace app. For secure post-call email, use a CloudTalk account webhook and middleware that calls Volanea’s REST API.
What CloudTalk event should trigger a follow-up email?
Use call.ended for a post-call follow-up. It represents a completed call lifecycle event and provides a stable call_uuid you can use for deduplication and idempotency.
Where should I store the Volanea API key?
Store the sk_ Volanea secret key in your server-side secret manager or runtime environment. Do not put it in CloudTalk contact fields, frontend code, browser configuration, or any public client application.
How do I prevent duplicate emails after CloudTalk retries a webhook?
Deduplicate CloudTalk deliveries by event_id in your database and send Volanea a stable Idempotency-Key derived from call_uuid and the message purpose. Both protections are useful because webhook delivery and outbound email requests can fail at different points.
What happens if a CloudTalk contact has no email address?
Skip the send, log the reason, and return a successful response after you have stored the event. You can optionally look up the contact in your CRM using CloudTalk identifiers, but never guess an address or send to an unverified recipient.