Send email from JustCall without exposing an API key by using JustCall’s outbound webhooks as the trigger and Volanea’s REST API as the delivery layer. This guide builds a production-minded flow for sending a booking confirmation whenever JustCall records an Appointment scheduled event.
There is no native Volanea app, marketplace listing, or one-click plugin for JustCall. That is not a limitation of the workflow: JustCall can deliver signed event payloads to an HTTPS endpoint you control, and that endpoint can validate the request, map the fields, and call Volanea securely.
The architecture is intentionally simple:
- A customer books an appointment through a JustCall calendar link.
- JustCall sends an
appointment.scheduledwebhook to your server. - Your server validates the JustCall signature and checks required fields.
- Your server sends one transactional email through Volanea.
- A stable idempotency key prevents a JustCall retry from becoming a duplicate email.
That separation matters. JustCall remains the source of the booking event. Your relay owns authentication, message policy, observability, and duplicate protection. Volanea handles the email send.
What triggers the email in JustCall
For this implementation, the concrete trigger is Appointment scheduled. JustCall documents this as an event that occurs when a new appointment is scheduled by a customer through a JustCall calendar link. The event type in the webhook body is appointment.scheduled.
This is a useful starting point because the event includes the details needed for a confirmation message: the customer’s name and email address, calendar name, scheduled slot, appointment type, notes, and assigned agents. It is more precise than sending mail after every incoming or completed call.
You can adapt the same relay pattern to other JustCall events later. For example, JustCall also offers events for missed calls, incoming voicemails, completed calls, SMS activity, contact status changes, and AI report generation. Do not switch event types casually, however. A Call completed in JustCall event does not guarantee that call notes, disposition, or AI-generated fields are ready yet. If your email depends on those values, use the later event designed for those updates.
The direct integration path
JustCall supports outbound webhook URLs. In the JustCall web portal, go to:
Profile icon → APIs and Webhooks → Webhook Settings
Add the public HTTPS URL for your relay endpoint and subscribe that URL to the Appointment scheduled event. Webhook access is available from JustCall’s Team plan and above; Sales Dialer webhook capabilities can have different plan requirements.
The URL belongs to your serverless function, application backend, container service, or worker. It is not a Volanea URL, and it is not browser code. For example:
https://app.example.com/webhooks/justcall/appointment-scheduled
When JustCall validates a new webhook URL, it can send an initial request to confirm that the endpoint exists. Build the endpoint before saving it in JustCall, and do not make the validation response depend on every production-only field being present.
Why not post straight to Volanea from JustCall?
A direct HTTP action would require placing Volanea authorization details in another product’s outbound configuration. JustCall’s webhook model is designed to deliver events to your application endpoint, not to safely transform data and hold a third-party transactional email secret.
A relay also gives you controls that a simple point-to-point request cannot provide:
- Signature validation before any email is created.
- Required-field checks and clear handling for missing email addresses.
- Consent and suppression policy checks.
- Idempotency across webhook retries.
- Safe HTML encoding for text that came from a customer or agent.
- Structured logs that connect the JustCall request ID to the Volanea send.
- A place to change templates or sender logic without editing the JustCall webhook configuration.
The real JustCall appointment payload
JustCall’s appointment webhook has a wrapper around the appointment data. The top-level fields identify the webhook request and event type, while data holds the appointment record.
A representative payload has this shape:
{
"request_id": "01HQ0MYWY2NJCTHPXXXXXXXXX",
"webhook_url": "https://app.example.com/webhooks/justcall/appointment-scheduled",
"url_id": "65d31ef34b883328XXXX",
"type": "appointment.scheduled",
"data": {
"id": "111505",
"booked_via": "Manual",
"calendar_id": "7736",
"calendar_name": "Onboarding Call",
"calendar_link": "https://app.justcall.io/calendar/1426xxx",
"calendar_language": "en",
"customer_first_name": "Robert",
"customer_last_name": "Zane",
"customer_email": "robert.zane@suits.co",
"customer_number": "917006003243",
"customer_timezone": "America/New_York",
"customer_slot": "29 Apr 25 08:45 pm - 29 Apr 25 09:00 pm",
"user_timezone": "Asia/Kolkata",
"user_slot": "11:15 am - 11:30 am",
"slot_duration": "15",
"type": "Call",
"notes": "Need a walk though of the calling functionality",
"created_at": "2025-04-27 10:27:36",
"appointment_date": "2025-04-29",
"appointment_user_date": "2025-04-29",
"appointment_customer_date": "2025-04-29",
"appointment_time": "11:15:00",
"appointment_user_time": "8:45:00",
"appointment_customer_time": "8:45:00",
"shared_with_agents": [
{
"id": "343567",
"name": "John Doe",
"email": "john.doe@suits.co"
}
],
"external_attendees": [
"Natallie@abc.co"
]
}
}
Treat this as event data, not as a complete customer profile. In particular, the customer_email field is the intended email recipient for this confirmation flow, but it can be absent or malformed in real-world bookings. A calendar may be configured without requiring an email address, an agent can schedule manually with incomplete data, or a test webhook may contain only validation data.
Your code should refuse to send when customer_email is missing rather than inventing a recipient, falling back to an agent’s address, or routing to an arbitrary shared inbox.
Field mapping for the confirmation email
The mapping below is deliberate:
| JustCall payload field | Volanea email field | Why it belongs there |
|---|---|---|
data.customer_email | to | The person who scheduled the appointment receives the confirmation. |
data.customer_first_name | HTML and text content | Personalizes the message without changing recipient identity. |
data.calendar_name | Subject and message content | Identifies which booking was made. |
data.customer_slot | HTML and text content | Uses the slot expressed in the customer’s timezone. |
data.customer_timezone | HTML and text content | Makes the time interpretation explicit. |
data.type | HTML and text content | Describes whether the appointment is a call or another appointment type. |
data.id | Idempotency-Key component | Identifies the logical booking event. |
request_id | Log correlation field | Helps trace one webhook delivery attempt. |
The email’s from address is intentionally not pulled from JustCall. Set it in protected server-side configuration and use an address on a domain verified in Volanea.
Prepare Volanea before connecting JustCall
Before adding the webhook, create a Volanea project, create a secret API key, and authenticate the domain that will appear in your From address. A request with an unverified sender domain should fail rather than silently impersonating a sender.
Volanea’s transactional endpoint is POST /v1/send at https://api.volanea.com. It uses Bearer authentication with a secret key and supports an Idempotency-Key header for safe retries. A single request can send to one recipient or up to 50 recipients, but this workflow should send one appointment confirmation to one customer at a time.
For a full list of fields, templates, API responses, and sender setup options, use the email API reference and setup guides. Keep the integration narrow at first: a verified sender, one recipient, a subject, an HTML body, a text body, and a stable idempotency key.
Store secrets on the server, not in JustCall or the browser
Set these values in your server environment, secret manager, or serverless platform’s encrypted configuration:
VOLANEA_API_KEY=sk_live_...
VOLANEA_FROM=appointments@example.com
JUSTCALL_API_SECRET=...
VOLANEA_API_KEY must never appear in a public JavaScript bundle, a mobile app, a client-visible environment variable, a shared webhook testing page, or a repository commit. Anyone with that key may be able to send email through your Volanea project.
The Volanea key does not live in the JustCall webhook settings. On the JustCall side, you store only the relay’s HTTPS URL. The JustCall API secret is used by your relay to validate JustCall’s request signature; it should be protected in the same way as the Volanea key.
This is also why a browser-based automation is the wrong place to call the email API. Browsers expose network requests, headers, bundled configuration, and developer tools to end users. Server-side code keeps the credential boundary intact.
Build the signed webhook relay
JustCall includes signature-related headers on webhook deliveries:
x-justcall-signaturex-justcall-signature-versionx-justcall-request-timestamp
For version v1, JustCall documents a SHA-256 HMAC signature derived from the JustCall API secret, the URL-encoded webhook_url value from the payload, the event type, and the request timestamp. Validate that signature before trusting customer names, email addresses, notes, or booking information.
The following Express route is a complete example for the Appointment scheduled trigger. It validates the signature, validates the event and recipient, escapes untrusted text before inserting it into HTML, and calls Volanea with a stable idempotency key.
import crypto from "node:crypto";
import express from "express";
const app = express();
app.use(express.json({ limit: "256kb" }));
const {
JUSTCALL_API_SECRET,
VOLANEA_API_KEY,
VOLANEA_FROM
} = process.env;
if (!JUSTCALL_API_SECRET || !VOLANEA_API_KEY || !VOLANEA_FROM) {
throw new Error("Missing required server-side environment variables");
}
function escapeHtml(value = "") {
return String(value)
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll('"', """)
.replaceAll("'", "'");
}
function isEmail(value) {
return typeof value === "string" && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
}
function validJustCallSignature(payload, headers) {
const timestamp = headers["x-justcall-request-timestamp"];
const received = headers["x-justcall-signature"];
if (!timestamp || !received || !payload?.webhook_url || !payload?.type) {
return false;
}
const signingPayload = [
JUSTCALL_API_SECRET,
encodeURIComponent(payload.webhook_url),
payload.type,
timestamp
].join("|");
const expected = crypto
.createHmac("sha256", JUSTCALL_API_SECRET)
.update(signingPayload)
.digest("hex");
const receivedBuffer = Buffer.from(received, "hex");
const expectedBuffer = Buffer.from(expected, "hex");
return receivedBuffer.length === expectedBuffer.length &&
crypto.timingSafeEqual(receivedBuffer, expectedBuffer);
}
app.post("/webhooks/justcall/appointment-scheduled", async (req, res) => {
const event = req.body;
if (!validJustCallSignature(event, req.headers)) {
return res.status(401).json({ error: "Invalid JustCall webhook signature" });
}
if (event.type !== "appointment.scheduled") {
return res.status(204).end();
}
const appointment = event.data ?? {};
const recipient = appointment.customer_email?.trim().toLowerCase();
// A missing address is not a transient delivery fault. Log it for review,
// acknowledge the event, and do not send to a substitute recipient.
if (!isEmail(recipient)) {
console.warn("JustCall appointment has no valid customer email", {
requestId: event.request_id,
appointmentId: appointment.id
});
return res.status(204).end();
}
if (!appointment.id) {
console.error("JustCall appointment is missing data.id", {
requestId: event.request_id
});
return res.status(422).json({ error: "Appointment ID is required" });
}
const firstName = escapeHtml(appointment.customer_first_name || "there");
const calendarName = escapeHtml(appointment.calendar_name || "your appointment");
const customerSlot = escapeHtml(appointment.customer_slot || "the scheduled time");
const timezone = escapeHtml(appointment.customer_timezone || "your local time zone");
const appointmentType = escapeHtml(appointment.type || "appointment");
const subject = `Confirmation: ${appointment.calendar_name || "your appointment"}`;
const text = [
`Hi ${appointment.customer_first_name || "there"},`,
"",
`Your ${appointmentType.toLowerCase()} has been scheduled for ${customerSlot}.`,
`Time zone: ${appointment.customer_timezone || "your local time zone"}.`,
`Calendar: ${appointment.calendar_name || "your appointment"}.`,
"",
"If you need to make a change, please contact our team."
].join("\n");
const html = `
<p>Hi ${firstName},</p>
<p>Your ${appointmentType.toLowerCase()} has been scheduled.</p>
<ul>
<li><strong>Calendar:</strong> ${calendarName}</li>
<li><strong>When:</strong> ${customerSlot}</li>
<li><strong>Time zone:</strong> ${timezone}</li>
</ul>
<p>If you need to make a change, please contact our team.</p>
`;
const volaneaResponse = await fetch("https://api.volanea.com/v1/send", {
method: "POST",
headers: {
"Authorization": `Bearer ${VOLANEA_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": `justcall:appointment.scheduled:${appointment.id}`
},
body: JSON.stringify({
from: VOLANEA_FROM,
to: recipient,
subject,
text,
html
})
});
const responseText = await volaneaResponse.text();
if (!volaneaResponse.ok) {
console.error("Volanea send failed", {
requestId: event.request_id,
appointmentId: appointment.id,
status: volaneaResponse.status,
responseText
});
// Returning 5xx asks JustCall to retry temporary upstream failures.
return res.status(502).json({ error: "Volanea send was not accepted" });
}
console.info("Appointment confirmation accepted by Volanea", {
requestId: event.request_id,
appointmentId: appointment.id,
recipient
});
return res.status(200).json({ ok: true });
});
app.listen(3000, () => {
console.log("Webhook relay listening on port 3000");
});
What the code maps and why
The Volanea request is the small block inside fetch():
body: JSON.stringify({
from: VOLANEA_FROM,
to: recipient,
subject,
text,
html
})
VOLANEA_FROM comes from server configuration rather than the webhook. recipient maps to data.customer_email. The subject uses the calendar name. The content combines the customer-facing slot with the customer timezone, which avoids a common booking-email mistake: presenting the internal user’s time instead of the booker’s time.
The relay deliberately sends both text and html. The text version is useful for simple mail clients, accessibility tooling, and situations where HTML rendering is disabled. The HTML version makes the event details scannable without requiring a visual email builder.
Do not include notes in an automatic confirmation by default. Appointment notes can contain sensitive details, customer-entered free text, or instructions that were meant only for a representative. If your business needs notes in the message, establish a clear disclosure policy and sanitize that content before rendering it.
Make retries safe with idempotency
Webhook delivery is at-least-once, not exactly-once. JustCall explicitly warns that webhook endpoints can receive the same event more than once and that event delivery order is not guaranteed. Its delivery system can retry transient failures, including responses such as HTTP 429, 500, 502, 503, and 504, up to five attempts with delays between attempts.
Without idempotency, this timeline can create duplicate mail:
- Your relay calls Volanea and Volanea accepts the message.
- The relay process times out or loses its connection before returning HTTP 200 to JustCall.
- JustCall retries the same event.
- Your relay sends the confirmation again.
The Idempotency-Key header stops that retry from producing another logical send. The key in the example is based on the immutable business event:
justcall:appointment.scheduled:<appointment-id>
That is better than using request_id, because a retry delivery can have a different request-level identifier while still representing the same appointment. It is also better than generating a random UUID, which would make every retry look like a new send.
Add durable deduplication for business rules
Volanea idempotency protects repeated API requests with the same key. For larger systems, add an application-level event table as well. Store at least:
- JustCall event type.
- JustCall appointment ID.
- JustCall request ID.
- Idempotency key.
- Recipient address.
- Volanea response or message ID, when available.
- Processing state and timestamps.
A durable record is useful when you need to answer operational questions such as: “Did this customer receive a confirmation?” or “Why did one booking receive no email?” It also lets you enforce policies beyond simple duplicate prevention, such as sending only once after a booking is finalized or suppressing a message after a cancellation event.
Test the complete path before enabling customers
Treat webhook setup as an integration test, not as a configuration checkbox. Test the actual trigger, the receiver, the email API call, and the mailbox outcome.
A practical test checklist
- Use a test appointment. Book through the exact JustCall calendar flow customers will use, with an inbox you control.
- Confirm the trigger. Verify the relay receives
appointment.scheduled, not a similarly named or unrelated event. - Verify signature validation. Send a request with an invalid signature and confirm the endpoint returns 401 without calling Volanea.
- Check field mapping. Confirm the recipient, calendar name, slot, and timezone are correct in the received email.
- Test missing email behavior. Create or simulate an appointment with no
customer_email; confirm the relay logs it and does not send to someone else. - Test a repeat delivery. Replay the same payload with the same appointment ID and confirm the idempotency key prevents a second logical send.
- Check sender authentication. Confirm the From domain is verified and that the message lands in an inbox with expected authentication results.
- Review delivery rather than acceptance alone. A successful API response means the message was accepted for processing; use email events and logs to investigate final delivery outcomes.
If you are collecting addresses from sources beyond JustCall or importing contacts before an event-triggered send, run the list through the free email address verification tool. Verification does not replace consent, but it can catch malformed or obviously undeliverable addresses before they affect campaign or transactional operations.
When this breaks
The most useful integrations assume failure will happen and define what each failure means. The JustCall-to-Volanea hop has a few failure modes that deserve specific handling.
JustCall retries cause duplicate sends
JustCall may retry a webhook delivery after selected transient HTTP responses, and it does not guarantee event ordering. Your endpoint might also receive the same event more than once. This is expected webhook behavior, not necessarily a JustCall bug.
Use two protections:
- A stable Volanea
Idempotency-Keyderived fromappointment.idand event type. - A durable application record keyed by the same business event for auditability and workflow policy.
Do not build an idempotency key from the current time, random values, or the email body. All three change across retry attempts and defeat deduplication.
The webhook times out after Volanea accepts the email
This is the classic uncertain-success case. Your server may successfully call Volanea but fail before it returns its HTTP response to JustCall. JustCall then sees a failed delivery and retries.
The idempotency key is the recovery mechanism. On the retry, send the same key. Volanea can replay the prior result instead of creating a second logical email. Avoid attempting to “fix” this by immediately sending a second message from a catch block; that guarantees duplicates in ambiguous cases.
Keep webhook work small. Validate, map, call Volanea, write a concise log entry, and respond. If rendering, CRM lookups, attachment generation, or database work becomes slow, acknowledge the webhook after durably queuing the job, then process the email asynchronously with the same idempotency key.
A payload field is missing
JustCall’s own documentation notes that validation or test payloads may not include every field found in a sample payload. Production data can also vary according to what was collected in the booking flow and what features are enabled on an account.
Classify fields by importance:
- Required to send:
data.idanddata.customer_email. - Useful but optional: customer name, calendar name, customer slot, customer timezone, and appointment type.
- Sensitive and usually excluded: notes, IP address, customer phone number, and agent details.
For an absent optional value, use a safe generic phrase such as “your scheduled time.” For a missing required field, do not fabricate data. Log the event with its request_id and appointment ID, return a response that matches your retry strategy, and route the issue to an operations queue if needed.
A field differs by plan, workflow, or booking method
Webhook access and feature availability can depend on JustCall plan level, and records created manually can be less complete than records created through a customer-facing calendar flow. For example, an appointment booked manually may not have the same customer-entered fields as one created from a calendar link.
Build templates that tolerate optional fields. Test at least these variants:
- A customer self-booking with a complete email address and timezone.
- A manually created appointment.
- A booking with no first name.
- A booking with blank notes.
- A test or validation delivery.
Avoid writing a template that fails simply because a friendly name is blank. The email can say “Hi there” without degrading the transaction.
Volanea rejects the request
An invalid API key, unverified sender, malformed recipient, or invalid request body can cause Volanea to reject the request. Log the response status and safe response content, but never log the API key.
Separate permanent and temporary failures. A bad recipient or unverified sender requires configuration or data correction. A gateway failure or rate limit can be retried. In the example, a non-successful Volanea response becomes HTTP 502 to invite JustCall’s transient-failure retry behavior. In a high-volume environment, replace blanket retries with a queue and explicit retry classification so permanent errors do not consume all five JustCall attempts.
Use templates once the message stabilizes
Inline HTML is useful for proving the flow, but a production confirmation often belongs in a versioned template. Templates separate message design from webhook plumbing and reduce the chance that a small content revision requires a deployment.
Keep the event-to-email contract stable even if you move to a template:
customer_first_namecalendar_namecustomer_slotcustomer_timezoneappointment_type
The relay still verifies the JustCall event, still validates recipients, and still sends the same idempotency key. Only the content construction changes from a JavaScript string to a template ID and controlled variables.
Templates also make it easier to localize confirmation emails. JustCall provides a calendar_language field in the appointment data, but do not assume it perfectly represents language preference. Use it only when your calendar configuration and consent model support sending localized content.
Alternatives: middleware and automation platforms
A direct signed webhook to your own relay is the cleanest option when you can deploy a small endpoint. It keeps both secrets in your environment, gives you signature verification, and enables precise idempotency behavior.
If you cannot host code, use an automation platform as middleware rather than pretending there is a native Volanea connector inside JustCall. JustCall documents integrations and automation paths through services such as Zapier, Make, Pipedream, Integrately, viaSocket, and Appy Pie.
The middleware pattern is:
JustCall Appointment scheduled webhook
→ Zapier, Make, or Pipedream webhook trigger
→ server-side HTTP request step or protected relay
→ Volanea POST /v1/send
Be careful with the final HTTP step. Some automation platforms store connection credentials securely, while others make it easy to paste sensitive headers into editable scenarios shared across a team. Prefer an automation platform’s secret or connection store where available. If that is not available, have the automation call your relay, and keep the Volanea key only in the relay’s secret manager.
Middleware can be appropriate for low-volume operational workflows, but it introduces another retry system, another log surface, and another place where field mappings can drift. For confirmation emails that affect customer experience, a compact relay is usually easier to audit and evolve.
Operational practices that keep the flow trustworthy
Transactional email is more than a successful HTTP request. The system needs a clear sender identity, predictable content, secure handling of event data, and enough evidence to debug failures without exposing customer information.
Start with these operating practices:
- Use a dedicated From address, such as
appointments@yourdomain.com, from a verified domain. - Make the email clearly transactional: confirm a booked appointment rather than mixing it with promotional content.
- Include a practical support or rescheduling route in the message.
- Escape all free-text fields before placing them in HTML.
- Log event IDs, status codes, and message correlation IDs—not API secrets.
- Retain only the payload data you genuinely need for troubleshooting and compliance.
- Watch bounce, complaint, suppression, and delivery events in Volanea after launch.
- Test deliverability whenever you change the sender domain, email template, or booking configuration.
One additional policy decision is worth making early: whether a rescheduled appointment should send a new confirmation. The appointment.scheduled event is a booking trigger, but a complete lifecycle may involve reschedule and cancellation signals handled elsewhere. Do not send a new confirmation merely because an event arrives out of order. Model the appointment as a business record, store its current state, and make each email type explicit.
Conclusion
To send email from JustCall with Volanea, use JustCall’s Appointment scheduled webhook as the trigger, not a fictional marketplace plug-in. Point the webhook to a server-side endpoint, validate JustCall’s HMAC signature, map data.customer_email and appointment details into a Volanea POST /v1/send request, and protect every retry with an idempotency key based on the appointment ID.
This pattern is intentionally boring in the best way: JustCall emits a real-time booking event, your relay applies your business rules and holds your secrets, and Volanea accepts a focused transactional send. The result is a confirmation email system that is safer to operate, easier to debug, and much less likely to send duplicates when a webhook delivery is retried.
FAQ
Does Volanea have a native JustCall integration?
No. Volanea does not ship a native JustCall marketplace app or plugin. The supported approach is to use JustCall’s outbound webhooks with a secure server-side relay that calls Volanea’s REST API.
What JustCall event should I use for a booking confirmation email?
Use Appointment scheduled. Its webhook event type is appointment.scheduled, and its payload includes customer email, calendar information, time slots, timezone details, and assigned-agent data.
Where should the Volanea API key be stored?
Store it only in server-side encrypted configuration, such as a cloud secret manager, serverless secret store, or backend environment variable. Do not place it in a browser app, public configuration, or JustCall webhook URL settings.
How do I stop JustCall webhook retries from sending duplicate emails?
Pass a stable Idempotency-Key on the Volanea request, such as justcall:appointment.scheduled:<appointment-id>. Use the appointment’s stable ID rather than a random ID or the current time.
What happens if a JustCall appointment has no customer email address?
Do not send the confirmation to a substitute recipient. Log the missing address with the JustCall request ID and appointment ID, acknowledge or route the event according to your operational policy, and correct the booking configuration or record.