Calendly can trigger booking data in real time, and you can use that event to send email from Calendly through Volanea without relying on a native marketplace app. The reliable pattern is Calendly webhook → your server-side relay → Volanea’s REST API.
There is an important constraint up front: Volanea does not provide a native Calendly app or Calendly Marketplace plugin. You should not put a Volanea API key into a booking page, browser script, or any client-visible automation field. Instead, Calendly sends a signed HTTP webhook to an endpoint you control, and that endpoint sends the email with Volanea.
This guide builds that direct integration around Calendly’s invitee.created webhook event. That event fires when an invitee schedules a meeting. Your relay verifies that Calendly sent the request, maps the invitee and meeting fields into an email, and calls POST /v1/send on Volanea.
What this Calendly-to-Volanea integration does
The basic use case is straightforward: someone books a meeting in Calendly, and they receive an email that is more tailored than a generic scheduling confirmation. You might send a welcome note, a pre-meeting checklist, a product-specific preparation guide, a link to an intake form, or an internal alert to the meeting owner.
The concrete trigger is Calendly’s invitee.created webhook event. Calendly documents this event as the scheduled-event notification for an invitee booking. The webhook envelope contains an event value of invitee.created, a creation timestamp, and a payload object representing the invitee and scheduled event.
A typical flow looks like this:
- An invitee selects a time and completes a Calendly booking.
- Calendly posts an
invitee.createdwebhook to your HTTPS endpoint. - Your endpoint validates Calendly’s webhook signature against the raw request body.
- Your code reads fields such as
payload.email,payload.name, andpayload.scheduled_event.start_time. - Your server calls Volanea’s
POST https://api.volanea.com/v1/sendendpoint with a server-side API key. - Volanea processes the message through its transactional sending pipeline.
The webhook relay is not unnecessary plumbing. It is the security boundary between public scheduling activity and your sending credentials. It is also the right place to validate data, choose the appropriate template, manage duplicates, and record an audit trail.
Why a webhook relay is required
Calendly supports webhook subscriptions that deliver scheduling events to a server endpoint. It does not turn an arbitrary third-party email API request into a safe browser-side action. Even if a low-code tool offers an HTTP request step, a Volanea secret key must remain private.
A direct relay gives you three things that a client-side implementation cannot.
It protects the Volanea API key
Volanea secret keys are credentials for sending email. Put VOLANEA_API_KEY in your hosting provider’s encrypted environment-variable or secret manager interface, then read it only inside a server route, worker, or queue consumer. Do not place it in:
- Calendly event descriptions or custom-question text
- JavaScript embedded on a public scheduling page
- A frontend
.envvariable that is bundled into browser code - A mobile app or desktop client configuration
- A repository, webhook URL query string, or support screenshot
If a key appears in a client-visible place, anyone who finds it may be able to submit mail on your account. Rotation after exposure is necessary, but it does not undo the possibility of unauthorized sends made before rotation.
It verifies the source event
Calendly can sign webhook requests with a webhook signing key. Signed deliveries include the Calendly-Webhook-Signature header, with a timestamp and a versioned signature. Your endpoint should calculate the expected HMAC from the timestamp and the unmodified raw request body, compare it in constant time, and reject stale requests.
This matters because a public webhook URL is discoverable in more ways than teams expect: logs, deployment settings, pasted examples, monitoring tools, and former employee documentation. Signature verification means URL knowledge alone is not enough to cause an email send.
It makes the automation reliable
Production automation needs business logic, not just an outbound HTTP call. Your relay can ignore cancellation events, escape untrusted form answers, branch by event type, add an idempotency key, capture Volanea errors, and queue slow work. These steps prevent an appointment booking from becoming a duplicate-email or spoofed-request problem.
For more endpoint details and sending examples, see the Volanea API reference and setup guides.
Calendly capability and plan requirements
Calendly’s current API supports webhook subscriptions for scheduling events, including invitee.created, invitee.canceled, and routing_form_submission.created. The booking trigger used here is invitee.created; it is the correct event for a message that should be sent only after a meeting is actually scheduled.
Calendly’s developer FAQ states that webhook subscriptions require the Calendly user associated with the subscription to have a paid Standard, Teams, or Enterprise subscription. API read and write access may be available more broadly, but do not assume the Free plan can create this webhook integration. Confirm the subscription level of the specific Calendly user or organization before you build the workflow around it.
For an internal integration for one team, create the webhook with a personal access token. For a product that connects multiple customers’ Calendly accounts, build a Calendly OAuth integration instead. In both cases, the running relay owns the secret material; it should not be configured in a visitor’s browser.
Choose the right webhook scope
Calendly supports webhook scope at the user or organization level for scheduled-event notifications.
Use user scope when only one host’s bookings should trigger messages. This is useful for an individual consultant, founder, recruiter, or account executive.
Use organization scope when any eligible team member’s booking should trigger the same integration. This is usually the better choice for a shared sales or customer-success process, but it requires the appropriate Calendly ownership or administrator permissions.
A routing-form event is different. routing_form_submission.created fires whenever someone submits a routing form, whether or not they book. That is useful for an “we received your request” email, but it is not a substitute for invitee.created when the email must mean “your meeting is confirmed.”
Create the Calendly webhook subscription
Before creating a subscription, decide where the endpoint will live. It must be publicly reachable over HTTPS in production. A route such as https://app.example.com/api/calendly/webhook is a sensible choice. During local development, use a secure tunnel rather than exposing an unauthenticated development server to the internet.
You also need the Calendly organization URI or user URI for the desired scope. Calendly’s webhook-subscription API uses a bearer-authenticated request and a JSON body that specifies the endpoint URL, subscribed events, organization or user, and scope.
The following example creates an organization-scoped subscription for newly scheduled invitees. Replace the placeholders with your actual values and keep the Calendly token outside source control.
curl --request POST \
--url https://api.calendly.com/webhook_subscriptions \
--header "Content-Type: application/json" \
--header "Authorization: Bearer $CALENDLY_PERSONAL_ACCESS_TOKEN" \
--data '{
"url": "https://app.example.com/api/calendly/webhook",
"events": ["invitee.created"],
"organization": "https://api.calendly.com/organizations/AAAAAAAAAAAAAAAA",
"scope": "organization"
}'
When using a personal access token, configure a webhook signing key as part of your subscription setup. Calendly describes signing keys as optional but strongly security-enhancing. Treat them as mandatory for a production email trigger.
For a multi-tenant SaaS integration, create subscriptions through your OAuth flow rather than collecting customers’ personal access tokens. Store each customer’s OAuth credentials and associated webhook configuration in encrypted server-side storage, scoped so one account cannot send through another account’s configuration.
Understand the Calendly invitee.created payload
Calendly posts an event envelope, not an email-ready object. The properties you use are nested within payload, and some optional booking details can be absent depending on the event configuration, account configuration, or what the invitee entered.
The practical fields for an appointment confirmation are:
event: check that it equalsinvitee.created.created_at: when Calendly created the webhook event.payload.uri: the canonical unique URI for the invitee record; use it as part of deduplication.payload.email: the invitee’s email address.payload.name: the invitee’s display name.payload.first_nameandpayload.last_name: may benullwhen Calendly is configured to collect one combined name field.payload.status: normallyactivefor a newly created invitee.payload.timezone: the invitee’s time zone when available.payload.scheduled_event: the nested event data, including the meeting name and scheduled time.payload.questions_and_answers: answers to custom booking questions, if present.
The exact nested scheduled_event data can vary with event type and Calendly configuration. For that reason, production code should not assume that every optional field exists. It should require the values that are essential to the send—at minimum, a valid recipient email—and use reasonable fallbacks for presentation fields such as a meeting title.
Here is a representative shape for the parts used in this integration:
{
"event": "invitee.created",
"created_at": "2026-09-19T15:00:00.000000Z",
"payload": {
"uri": "https://api.calendly.com/scheduled_events/EVENT_UUID/invitees/INVITEE_UUID",
"email": "alex@example.com",
"name": "Alex Morgan",
"first_name": "Alex",
"last_name": "Morgan",
"status": "active",
"timezone": "America/New_York",
"questions_and_answers": [],
"scheduled_event": {
"uri": "https://api.calendly.com/scheduled_events/EVENT_UUID",
"name": "Product consultation",
"start_time": "2026-09-22T14:00:00.000000Z",
"end_time": "2026-09-22T14:30:00.000000Z",
"status": "active"
}
}
}
Do not treat custom-question answers as trusted HTML. A prospect may type angle brackets, links, or misleading text into an answer. If you include answers in internal notification email, HTML-escape them first. For attendee-facing email, only include the values you truly need.
Send email from Calendly with Volanea: working Node.js relay
The example below is a Next.js App Router route handler, but the same sequence works in Express, a Cloudflare Worker, AWS Lambda, or any server environment that lets you access the raw request body.
It performs five important jobs:
- Reads the raw body before parsing JSON.
- Verifies Calendly’s timestamped signature.
- Rejects stale replay attempts.
- Maps the Calendly invitee fields into Volanea’s send payload.
- Sets an
Idempotency-Keybased on the immutable Calendly invitee URI, making repeated delivery attempts safe at the Volanea send hop.
// app/api/calendly/webhook/route.ts
import crypto from "node:crypto";
export const runtime = "nodejs";
type CalendlyInviteeCreated = {
event: "invitee.created" | string;
created_at: string;
payload: {
uri: string;
email: string;
name?: string | null;
first_name?: string | null;
last_name?: string | null;
status?: "active" | "canceled" | string;
timezone?: string | null;
scheduled_event?: {
uri?: string;
name?: string | null;
start_time?: string | null;
end_time?: string | null;
status?: string | null;
};
};
};
function parseCalendlySignature(header: string) {
const values = Object.fromEntries(
header.split(",").map((part) => {
const [key, value] = part.trim().split("=");
return [key, value];
})
);
return { timestamp: values.t, signature: values.v1 };
}
function safeEqualHex(a: string, b: string) {
const left = Buffer.from(a, "hex");
const right = Buffer.from(b, "hex");
return left.length === right.length && crypto.timingSafeEqual(left, right);
}
function escapeHtml(value: string) {
return value.replace(/[&<>'"]/g, (character) => {
const entities: Record<string, string> = {
"&": "&",
"<": "<",
">": ">",
"'": "'",
'"': """
};
return entities[character];
});
}
export async function POST(request: Request) {
const rawBody = await request.text();
const signatureHeader = request.headers.get("Calendly-Webhook-Signature");
if (!signatureHeader) {
return Response.json({ error: "Missing Calendly signature" }, { status: 401 });
}
const { timestamp, signature } = parseCalendlySignature(signatureHeader);
if (!timestamp || !signature) {
return Response.json({ error: "Malformed Calendly signature" }, { status: 401 });
}
// Calendly recommends replay protection using the timestamp. Allow 3 minutes.
const ageInSeconds = Math.abs(Date.now() / 1000 - Number(timestamp));
if (!Number.isFinite(ageInSeconds) || ageInSeconds > 180) {
return Response.json({ error: "Stale webhook" }, { status: 401 });
}
const signingSecret = process.env.CALENDLY_WEBHOOK_SIGNING_KEY;
const volaneaApiKey = process.env.VOLANEA_API_KEY;
const from = process.env.CALENDLY_EMAIL_FROM;
if (!signingSecret || !volaneaApiKey || !from) {
console.error("Required server secret is not configured");
return Response.json({ error: "Server configuration error" }, { status: 500 });
}
// Calendly signs: `${timestamp}.${rawBody}`
const expectedSignature = crypto
.createHmac("sha256", signingSecret)
.update(`${timestamp}.${rawBody}`, "utf8")
.digest("hex");
if (!safeEqualHex(signature, expectedSignature)) {
return Response.json({ error: "Invalid Calendly signature" }, { status: 401 });
}
const calendly = JSON.parse(rawBody) as CalendlyInviteeCreated;
// This endpoint intentionally sends only for a newly scheduled, active invitee.
if (calendly.event !== "invitee.created" || calendly.payload.status !== "active") {
return Response.json({ ignored: true }, { status: 200 });
}
const invitee = calendly.payload;
if (!invitee.email || !invitee.uri) {
return Response.json({ error: "Calendly payload lacks email or invitee URI" }, { status: 422 });
}
const recipientName = invitee.first_name || invitee.name || "there";
const meetingName = invitee.scheduled_event?.name || "your meeting";
const startTime = invitee.scheduled_event?.start_time
? new Intl.DateTimeFormat("en-US", {
dateStyle: "full",
timeStyle: "short",
timeZone: invitee.timezone || "UTC"
}).format(new Date(invitee.scheduled_event.start_time))
: "the scheduled time shown in your Calendly confirmation";
const emailPayload = {
from,
to: invitee.email,
subject: `You're booked: ${meetingName}`,
text: `Hi ${recipientName},\n\nYou're booked for ${meetingName} on ${startTime}.\n\nIf you need to make a change, use the reschedule or cancel link in your Calendly confirmation.`,
html: `<p>Hi ${escapeHtml(recipientName)},</p><p>You’re booked for <strong>${escapeHtml(meetingName)}</strong> on ${escapeHtml(startTime)}.</p><p>If you need to make a change, use the reschedule or cancel link in your Calendly confirmation.</p>`
};
const volaneaResponse = await fetch("https://api.volanea.com/v1/send", {
method: "POST",
headers: {
"Authorization": `Bearer ${volaneaApiKey}`,
"Content-Type": "application/json",
// Calendly can retry webhook delivery. Keep this value stable per invitee.
"Idempotency-Key": `calendly-invitee-created:${invitee.uri}`
},
body: JSON.stringify(emailPayload)
});
if (!volaneaResponse.ok) {
const details = await volaneaResponse.text();
console.error("Volanea send failed", volaneaResponse.status, details);
// Return a non-2xx response so Calendly can retry the delivery.
return Response.json({ error: "Email send failed" }, { status: 502 });
}
return Response.json({ sent: true }, { status: 200 });
}
The mapping is explicit: payload.email becomes Volanea’s to, the verified sender in CALENDLY_EMAIL_FROM becomes from, and Calendly’s invitee and event fields create the subject and message body. The Idempotency-Key is deliberately not random. A random value on every retry would defeat its purpose.
Before production use, authenticate the domain represented by CALENDLY_EMAIL_FROM. A recognizable From address on a domain you control gives recipients continuity between the calendar booking, the message, and your organization. It also lets you configure the DNS authentication records Volanea provides instead of sending from an unverified identity.
Store secrets in the right place
There are two independent classes of secrets in this integration:
- Calendly credentials: a personal access token for a private internal integration, or OAuth credentials and tokens for a customer-facing integration. The webhook signing key is also secret.
- Volanea credentials: the secret API key used in the relay’s
Authorizationheader.
Store both only in server-side secret storage. On Vercel, use encrypted project environment variables; on Cloudflare, use Worker secrets; on AWS, use Secrets Manager, Parameter Store, or a protected deployment environment. Local development can use a .env.local file excluded from Git.
The Calendly personal access token is used to create or administer the webhook subscription. It is not used inside every incoming webhook request. The CALENDLY_WEBHOOK_SIGNING_KEY is used by the receiver to validate each incoming webhook. The Volanea key is used only when your receiver sends the email.
This separation is useful during incident response. If you suspect a Calendly signing key was exposed, rotate or recreate the webhook signing configuration and reject requests signed with the old key. If you suspect a Volanea key was exposed, rotate that sending key without changing the Calendly subscription itself.
Make meeting email useful rather than redundant
Calendly already sends scheduling-related communication depending on the account’s notification settings. Your Volanea message should add value instead of simply restating a date and time the invitee has received elsewhere.
Good messages triggered by invitee.created include:
- A consultation preparation checklist based on the appointment type.
- An onboarding message that explains what happens during the call.
- A secure link to submit documents before the meeting.
- A reminder to bring account, billing, or technical details.
- A product-demo email with relevant case studies or integration instructions.
- An internal host notification with booking answers and lead-routing information.
Avoid using this transactional trigger as a way to add a person automatically to broad promotional campaigns. A booked meeting is a strong reason to send operational information about that meeting; it is not automatically permission for unrelated marketing. Keep the intent clear, maintain your subscription and suppression rules, and honor applicable consent requirements.
Branch by event type carefully
Most teams eventually have more than one Calendly event type: demo, support escalation, onboarding, interview, partner call, or renewal review. You can choose message content based on payload.scheduled_event.name, but a stable event-type identifier or URI is safer than a human-facing name if your process can retrieve and maintain it.
For example, a message title such as “30 Minute Demo” is likely to change when marketing adjusts naming. Build an allowlist in configuration or a database that maps the event type to an email template and sender. Then make the fallback behavior explicit: either send a generic scheduling email or do not send at all.
If you want reusable content rather than inline HTML, create a Volanea template and send using its template identifier and variables. That keeps copy changes out of deployment code, while the webhook relay continues to own security, data validation, and idempotency.
When this breaks: Calendly webhook failures and duplicate sends
The vulnerable point in this design is the hop from Calendly to your endpoint. Treat it as an at-least-once delivery system, not a guarantee that every booking produces exactly one HTTP request.
Calendly retries can create duplicate sends
Calendly retries webhook deliveries that receive errors. Its webhook error documentation says it retries failed 3xx, 4xx, and 5xx deliveries for up to 24 hours with exponential backoff, and can disable a subscription after continued failure. A timeout can create the same problem even if your code actually sent the email: Calendly may not receive your success response and may retry.
The first defense is Volanea’s Idempotency-Key header. Use a stable key tied to the event being processed, such as calendly-invitee-created:${payload.uri}. The invitee URI is a canonical identifier, so it is materially better than using an email address, which can be reused across different bookings.
The second defense is your own database-backed event ledger. In a higher-volume integration, record the invitee URI and processing state in a transaction before sending. Mark it completed after the Volanea request succeeds. If a retry arrives, return a fast 200 response when the same completed event is detected. The Volanea idempotency key remains valuable because it protects the narrow crash window between “email accepted” and “database marked complete.”
Webhook timeouts can make a successful send look failed
The sample route waits for Volanea to respond before returning success to Calendly. That is simple and practical for a low-volume integration, but it makes the webhook response dependent on the downstream send API and your network path.
For more resilience, verify and persist the Calendly event quickly, return a success response, and send email from a background job. A typical production sequence is:
- Verify the signature from the raw webhook body.
- Insert the event into durable storage using
payload.urias a unique key. - Return a 200 response immediately after the insert succeeds.
- Let a worker call Volanea with the same deterministic idempotency key.
- Retry worker failures with monitoring and a dead-letter path.
Do not return 200 before you have durably recorded the event. Otherwise an application restart after the response can lose the message permanently, because Calendly has no reason to retry a delivery it believes succeeded.
Payload fields can be missing or differ by configuration
Calendly documents email and name in the invitee payload, but presentation fields and nested data deserve defensive handling. For example, first_name and last_name can be null when the event type collects a single combined name. Booking questions may be empty. Time zone may not be available. Certain functionality may also depend on your Calendly subscription or event configuration.
The correct response is not to guess. Require the recipient address; use fallbacks for optional copy; log a structured warning when expected nonessential fields are absent; and fail visibly when an essential business field is missing. If your email requires a custom booking answer, validate that answer before calling Volanea and decide whether to retry, alert an operator, or send a generic message.
Reschedules produce two different webhook events
Calendly states that a reschedule triggers both invitee.canceled for the old booking and invitee.created for the new booking. If you subscribe only to invitee.created, the attendee will receive the new-booking email, which is often right. If you also subscribe to cancellations, ensure cancellation logic checks Calendly’s rescheduled value before sending a message that sounds like a final cancellation.
Never treat an invitee.canceled payload as if it were a new booking. Keep event-specific code paths separate and use unique idempotency keys for each action, such as calendly-invitee-canceled:${payload.uri}.
Signature verification fails after a framework parses JSON
HMAC verification must use the raw body exactly as Calendly sent it. Parsing JSON and serializing it again can change whitespace, escaping, key ordering, or Unicode representation, producing a signature mismatch.
In Next.js, use await request.text() before JSON.parse. In Express, use a raw body parser for the webhook route. In any serverless platform, confirm the framework has not modified the payload before verification. Also compare signatures with a timing-safe method rather than ordinary string comparison.
Test the integration before enabling real booking traffic
A reliable test plan has both transport and email checks. Start with a non-production Volanea key where available, a verified test recipient, and a temporary Calendly event type that is not linked from public pages.
Test these cases deliberately:
- Normal booking: Confirm one
invitee.createdrequest reaches the endpoint and one email is accepted by Volanea. - Combined-name booking: Confirm the greeting uses
payload.namewhenfirst_nameis null. - Missing optional data: Test with no custom-question answers and verify the email still renders.
- Replay attempt: Re-send an old captured request and verify the timestamp window rejects it.
- Duplicate delivery: Deliver the exact same valid payload twice and verify Volanea receives the same
Idempotency-Key, preventing a second message. - Volanea failure: Temporarily use an invalid key or simulated send failure and observe that your alerting, logs, and retry behavior work as planned.
- Reschedule: Reschedule a meeting and confirm that your subscribed events produce the expected email behavior.
Keep structured logs with the Calendly event name, invitee URI, scheduled-event URI where available, Volanea response status, and a message identifier returned by Volanea. Do not log raw API keys, webhook signing keys, full message bodies containing sensitive form answers, or any personal data you do not need for operations.
Direct webhook relay versus Zapier or Make
A direct webhook relay is the best fit when you need deterministic idempotency, strict signature verification, custom formatting, high-volume control, or a durable event ledger. It also keeps implementation ownership with your engineering team.
Zapier and Make can be reasonable middleware options when you do not have a server endpoint and your workflow is simple. Calendly supports automation integrations, and an automation platform can receive a Calendly booking trigger and make an authenticated HTTP request to Volanea. However, you must confirm that the platform stores the Volanea key as a private connection or encrypted credential—not as data exposed in browser code, task output, or shared configuration.
The trade-off is operational control. Middleware platforms can simplify setup, but webhook retry behavior, duplicate handling, timeout limits, error visibility, and payload transformations are governed by the platform. If the email is business-critical, retain a stable idempotency strategy and test retries in that platform just as you would in custom code.
For teams evaluating sending volume, domains, and plan limits before they automate, review transactional email pricing.
Conclusion
To send email from Calendly with Volanea, subscribe to Calendly’s invitee.created webhook and point it at a secure server-side relay. Verify Calendly’s Calendly-Webhook-Signature against the raw body, map payload.email and scheduled-event details into Volanea’s POST /v1/send payload, and use a deterministic Idempotency-Key derived from the invitee URI.
The resulting workflow is simple enough for one appointment type, but it also scales into a durable integration: queue the verified event, choose content by event type, store processing state, observe failures, and handle reschedules as separate lifecycle events. Most importantly, keep both the Calendly secrets and Volanea API key on the server side.
FAQ
Can I install Volanea from the Calendly Marketplace?
No. Volanea does not ship a native Calendly Marketplace app or plugin. Use Calendly webhooks with a server-side relay, or use an automation middleware platform that can securely call Volanea’s REST API.
What Calendly event should trigger a booking email?
Use invitee.created. Calendly triggers it when an invitee schedules a meeting. Use routing_form_submission.created only when you want to react to a routing-form submission, including submissions that do not result in a booking.
Can I send directly from a Calendly booking page to Volanea?
No. Do not expose a Volanea secret key in client-side code or public booking-page configuration. Send from a server endpoint, worker, or trusted middleware connection where the key remains private.
How do I prevent duplicate Calendly confirmation emails?
Use a stable Volanea Idempotency-Key, ideally based on Calendly’s canonical invitee URI, and maintain a database-backed event ledger for production workloads. Calendly can retry failed or timed-out webhook deliveries.
What happens when someone reschedules through Calendly?
Calendly triggers both invitee.canceled for the prior booking and invitee.created for the replacement booking. Handle them as separate event types, and inspect the cancellation payload’s reschedule status before sending a final-cancellation message.