If you need to send email from Acuity Scheduling beyond its built-in appointment notifications, the dependable route is Acuity’s webhook API plus a server-side call to Volanea. This guide shows the complete pattern: trigger an email when an appointment is scheduled, map Acuity fields safely, and prevent duplicate or exposed-key sends.

Acuity Scheduling does not provide a native Volanea app, marketplace listing, or one-click connector. That is not a blocker, but it does mean the integration needs a small middleware service between Acuity and Volanea. Acuity sends an HTTP webhook when an appointment event occurs; your receiver validates and processes that event; then the receiver calls Volanea’s REST email API.

That separation is important. It keeps the Volanea API key on infrastructure you control, gives you a place to implement idempotency, and lets you decide exactly which appointment events deserve an email.

What the Acuity Scheduling to Volanea flow looks like

The core flow has three systems and one important boundary:

  1. A customer books an appointment in Acuity Scheduling.
  2. Acuity emits an appointment.scheduled webhook to an HTTPS endpoint you operate.
  3. Your endpoint maps the webhook data into a transactional message and sends it through Volanea.
  4. Your database records the Acuity appointment ID and message status so a retried webhook cannot create a second email.

The concrete Acuity trigger in this guide is appointment.scheduled. It is the event Acuity uses when a new appointment is scheduled. Other lifecycle events, such as a reschedule or cancellation, should be handled as separate messages with separate idempotency keys. Do not treat a reschedule as a new booking confirmation unless that is truly the email experience you want.

The webhook is not the email itself. It is an event notification. Your service remains responsible for choosing the sender, recipient, subject, content, suppression behavior, and what to do if a downstream email request fails.

Why middleware is the right architecture

A webhook receiver can be a small Express, Fastify, Laravel, Django, Rails, Cloudflare Workers, or serverless function application. The implementation language matters less than four capabilities:

  • It can receive public HTTPS POST requests.
  • It can keep secrets in server-side environment variables or a secrets manager.
  • It can persist idempotency records before or during processing.
  • It can return a fast HTTP response to Acuity rather than waiting on slow downstream work.

Acuity is not a safe place to hold a Volanea credential. A client-side scheduling page is visible to a browser, and a generic webhook configuration is not a secret vault for a second provider’s API key. The Volanea credential belongs only in your receiver’s runtime environment.

Before you build: confirm Acuity API and webhook access

Acuity’s webhook capability is part of its developer API. Before writing code, confirm that the Acuity account and plan you are administering have API access and that the account owner can create a webhook. Plan entitlements can change, so use Acuity’s current developer documentation and account settings rather than relying on an old tutorial.

Acuity’s API uses an API user ID and API key for API authentication. Those credentials are for your server-to-Acuity requests, such as registering a webhook or fetching complete appointment information. They should be stored in the same kind of server-side secret store as the Volanea key.

A direct webhook approach is ideal when you control a backend. If you do not operate a backend, use an automation service such as Zapier or Make as the middle layer instead. In that version, Acuity’s new-appointment trigger starts the scenario or Zap, and the automation service makes an authenticated HTTP request to a backend endpoint you own. Avoid putting a Volanea API key into browser code, a public form, or any configuration that could be exposed to end users.

Choose the event deliberately

Start with appointment.scheduled for a new-booking message. Typical uses include:

  • A personalized confirmation from a brand instead of a generic notification.
  • An internal alert to a concierge, account manager, or sales team.
  • A pre-appointment checklist based on appointment type.
  • A webhook-triggered sequence in your own application after a consultation is booked.

For appointment.rescheduled, send a message that explicitly states the appointment changed and includes the new time. For appointment.canceled, do not send a cheerful booking confirmation; send a cancellation acknowledgement or notify internal staff. Event-specific templates reduce confusing customer communications and make reporting clearer.

The Acuity webhook payload you should expect

Acuity webhook callbacks use an event envelope with an event value and a payload object. For an appointment-scheduled event, the payload is appointment data, including identifying and scheduling fields. A representative callback has this shape:

{
  "event": "appointment.scheduled",
  "payload": {
    "id": 123456789,
    "calendarID": 987654321,
    "appointmentTypeID": 24681012,
    "datetime": "2026-10-15T14:30:00-0400",
    "firstName": "Maya",
    "lastName": "Chen",
    "email": "maya@example.com",
    "phone": "+1 555 010 0199",
    "timezone": "America/New_York",
    "calendar": "Downtown Studio",
    "appointmentType": "Initial Consultation",
    "created": "2026-09-28T10:12:14-0400"
  }
}

Treat the callback as external input, not as trusted template data. Names may be blank, email addresses may be absent or malformed, appointment type labels can be changed by staff, and custom intake-form answers are not a safe assumption in every webhook payload or account configuration.

The most useful stable correlation field is the appointment id. Store it. It lets you correlate Acuity activity with your outbound message, investigate a customer question, and avoid duplicate confirmation sends when a delivery is retried.

Map only what the template needs

For a booking confirmation, a practical mapping is:

Acuity fieldVolanea email use
payload.emailrecipient address
payload.firstNamegreeting name, with a fallback
payload.appointmentTypeappointment description
payload.datetimescheduled time, after formatting
payload.timezonetimezone label or time conversion input
payload.calendarlocation, staff member, or calendar context
payload.ididempotency and observability key

Do not put raw custom-answer content directly into HTML. If you use intake-form responses, fetch or process them on the server, allowlist the fields you need, and HTML-escape output. Booking notes can contain unexpected text, including markup-like characters or personally sensitive information.

Register the Acuity webhook

Register the webhook with Acuity’s developer API from a trusted administrative process, not from the public booking page. The registration identifies the event to watch and the HTTPS target Acuity should call.

Use a receiver URL that is hard to guess, such as https://hooks.example.com/acuity/8a0f..., and protect it with application-level controls. A random path is not a substitute for validation, but it reduces unsolicited traffic. Restrict logging, apply a request-size limit, use TLS, and retain only the personal data needed to operate the flow.

A webhook target should return promptly. Do not build an architecture that waits for rendering, an email API call, analytics requests, and database work before acknowledging the callback. If Acuity considers the endpoint unavailable or too slow, it may retry; that behavior is one reason idempotency is mandatory.

Do not confuse webhook registration credentials with email credentials

There are two separate credential sets in this integration:

  • Acuity API user ID and API key: used by an administrative server-side process to work with Acuity’s API, including webhook setup and any follow-up appointment retrieval.
  • Volanea API key: used only by your middleware to authorize the outbound email request.

Neither key belongs in JavaScript delivered to a browser. Neither key belongs in an Acuity intake form, appointment note, public URL parameter, or email template. In a serverless deployment, set them as encrypted environment variables or secrets. In a container or VM deployment, inject them from a managed secrets system and rotate them without committing them to source control.

For Volanea-specific authentication headers, sender verification requirements, and current request schema, keep the implementation aligned with the email API reference and setup guides. Your verified sending domain and sender address should be established before you activate the webhook, otherwise a successful booking may produce a failed downstream send.

Working webhook receiver and Volanea REST call

The following Node.js example shows the essential server-side path. It accepts the Acuity appointment.scheduled envelope, validates the fields needed for a confirmation, creates a deterministic idempotency key from the appointment ID, and calls Volanea.

The example assumes VOLANEA_API_KEY and VOLANEA_EMAIL_ENDPOINT are private environment variables. Keeping the endpoint in configuration is useful when you have separate test and production environments; set it to the current send-email endpoint shown in Volanea’s API documentation. The API key never appears in Acuity, browser code, or source control.

import express from "express";
import crypto from "node:crypto";

const app = express();
app.use(express.json({ limit: "256kb" }));

const sentAppointments = new Map(); // Replace with a database table in production.

const escapeHtml = (value = "") =>
  String(value)
    .replaceAll("&", "&")
    .replaceAll("<", "&lt;")
    .replaceAll(">", "&gt;")
    .replaceAll('"', "&quot;")
    .replaceAll("'", "&#039;");

function formatAppointmentTime(datetime, timezone) {
  const date = new Date(datetime);
  if (Number.isNaN(date.valueOf())) return "the scheduled time in your booking";

  return new Intl.DateTimeFormat("en-US", {
    dateStyle: "full",
    timeStyle: "short",
    timeZone: timezone || "UTC"
  }).format(date);
}

app.post("/acuity/webhooks/REPLACE_WITH_RANDOM_SECRET", async (req, res) => {
  const { event, payload } = req.body ?? {};

  // Only this route handles new-booking confirmations.
  if (event !== "appointment.scheduled") {
    return res.sendStatus(204);
  }

  const appointmentId = payload?.id;
  const recipient = payload?.email?.trim().toLowerCase();

  if (!appointmentId || !recipient) {
    console.error("Acuity appointment event lacks id or email", { event, appointmentId });
    // Acknowledge intentionally: retrying cannot create a recipient address.
    return res.sendStatus(204);
  }

  const idempotencyKey = crypto
    .createHash("sha256")
    .update(`acuity:appointment.scheduled:${appointmentId}`)
    .digest("hex");

  // Atomically insert this key in a real database before sending.
  if (sentAppointments.has(idempotencyKey)) {
    return res.sendStatus(204);
  }
  sentAppointments.set(idempotencyKey, { status: "processing", appointmentId });

  const firstName = escapeHtml(payload.firstName || "there");
  const appointmentType = escapeHtml(payload.appointmentType || "appointment");
  const calendar = escapeHtml(payload.calendar || "our team");
  const appointmentTime = escapeHtml(
    formatAppointmentTime(payload.datetime, payload.timezone)
  );

  const volaneaPayload = {
    from: {
      email: process.env.VOLANEA_FROM_EMAIL,
      name: process.env.VOLANEA_FROM_NAME || "Example Company"
    },
    to: [{ email: recipient, name: `${payload.firstName || ""} ${payload.lastName || ""}`.trim() }],
    subject: `Your ${payload.appointmentType || "appointment"} is confirmed`,
    html: `<p>Hi ${firstName},</p>
      <p>Your <strong>${appointmentType}</strong> is confirmed for ${appointmentTime}.</p>
      <p>Calendar: ${calendar}</p>
      <p>Booking reference: ${appointmentId}</p>`,
    text: `Hi ${payload.firstName || "there"},\n\nYour ${payload.appointmentType || "appointment"} is confirmed for ${formatAppointmentTime(payload.datetime, payload.timezone)}.\nCalendar: ${payload.calendar || "our team"}\nBooking reference: ${appointmentId}`
  };

  try {
    const response = await fetch(process.env.VOLANEA_EMAIL_ENDPOINT, {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${process.env.VOLANEA_API_KEY}`,
        "Content-Type": "application/json",
        "Idempotency-Key": idempotencyKey
      },
      body: JSON.stringify(volaneaPayload)
    });

    if (!response.ok) {
      const detail = await response.text();
      throw new Error(`Volanea send failed: ${response.status} ${detail}`);
    }

    sentAppointments.set(idempotencyKey, { status: "sent", appointmentId });
    return res.sendStatus(204);
  } catch (error) {
    sentAppointments.delete(idempotencyKey);
    console.error("Unable to send booking confirmation", { appointmentId, error: String(error) });
    return res.sendStatus(500);
  }
});

app.listen(process.env.PORT || 3000);

The field mapping is explicit: Acuity’s payload.email becomes the recipient, firstName becomes the greeting, appointmentType becomes the subject and message context, datetime plus timezone becomes the formatted time, and id becomes the deduplication reference.

In production, replace the in-memory Map with a database table having a unique constraint on the idempotency key. Also consider a queue. A robust pattern is to store a pending event transactionally, return quickly to Acuity, and let a worker perform the Volanea call with controlled retry behavior.

Personalize the message without creating data problems

Appointment emails often carry more personal information than a standard product event. That makes minimalism valuable. A confirmation usually needs a first name, service name, date and time, and next step. It generally does not need the customer’s phone number, full intake responses, internal notes, or every calendar attribute.

Use payload.datetime as an ISO-style timestamp and payload.timezone as timezone context. Do not concatenate a raw timestamp into an email and assume every recipient will interpret it correctly. Format it server-side, then test appointments created in each calendar timezone you support.

Build a fallback hierarchy

A production template should define sensible fallback behavior:

  • Missing first name: use “Hi there” rather than an empty greeting.
  • Missing appointment type: use “your appointment.”
  • Missing calendar name: omit that line or use a neutral brand label.
  • Missing email address: do not attempt to send; log the event with minimal metadata.
  • Invalid date: do not generate a misleading date. Use a safe phrase or fetch the appointment record from Acuity before sending.

This is especially relevant when teams have multiple booking forms, imported clients, or appointment types maintained by different staff members. The fields you see in one test booking may not represent every production record.

When this breaks: failures specific to this hop

Every webhook integration needs a failure plan. The problem is rarely that an HTTP request can never be made; it is that a transient failure creates ambiguity about whether the message was sent.

Acuity retries can create duplicate sends

If Acuity does not receive a successful response because your endpoint timed out, returned an error, or experienced a network interruption, it can retry the webhook. Your code may have already sent the email before the response was lost. Without idempotency, the recipient could receive two confirmations.

Use appointment.scheduled plus the Acuity appointment id to derive a unique key. Enforce uniqueness in a database. If Volanea supports an idempotency header for your selected endpoint, send the same key there too; this gives you protection at both your application and email-provider layers.

Do not use the event arrival time as the key. Retries arrive at different times. Do not use only the recipient email, either: the same person may legitimately book two appointments.

Webhook timeouts and slow dependencies

Acuity needs a prompt response from the receiver. Long template rendering, synchronous CRM calls, DNS trouble, or an email provider delay can make the receiver exceed the practical response window.

The best response is architectural: enqueue the normalized event and acknowledge it once it is safely persisted. A worker then sends through Volanea and retries according to your own policy. If you choose synchronous sending for a low-volume implementation, set conservative request timeouts and make the idempotency record durable before the send.

Payload fields may be absent or differ across booking setups

Not every Acuity appointment has every optional field your template expects. Calendar configuration, appointment-type settings, client entry, plan capabilities, and API changes can affect available data. Custom form answers are particularly unsuitable as assumed webhook fields.

Code defensively, as the example does. Validate required fields, use fallbacks for optional ones, and fetch the full appointment through Acuity’s API when your business process requires information not present in the event payload. Keep that extra fetch server-side with the Acuity API credentials.

A successful API request is not proof of inbox placement

A Volanea API acceptance response means the sending system accepted the request; it does not guarantee that a mailbox provider placed the message in the inbox. Monitor Volanea event data for delivery, bounce, complaint, and suppression outcomes. Authenticate the sending domain, use a real reply path, and avoid sending promotional content under the guise of an appointment confirmation.

Testing the integration before customers see it

Start with a non-production sender and a test Acuity calendar. Create several appointments rather than only one happy-path booking:

  1. A standard booking with a complete name, email, calendar, and appointment type.
  2. A booking with a blank or unusual first name.
  3. A booking in a different timezone.
  4. A second booking by the same address.
  5. A deliberate receiver failure followed by a retry test.
  6. A reschedule and cancellation to ensure they do not use the new-booking template.

Inspect logs using the appointment ID and your idempotency key, not full message bodies or complete personal profiles. This makes troubleshooting possible while reducing unnecessary exposure of customer data.

Before enabling production sends, verify the from address is authorized in Volanea and that the domain’s authentication records are complete. If you are estimating the operational impact of confirmations, reminders, and staff notifications at higher booking volume, review transactional email pricing before turning on every event type.

Direct webhooks versus Zapier or Make

There are two legitimate implementation routes, and the right choice depends on control and volume.

Direct Acuity webhook to your service

Choose direct webhooks when you have a developer, need exact deduplication behavior, want detailed logs, or handle sensitive booking workflows. You control the receiver, field normalization, error policy, template generation, and Volanea credential storage.

The tradeoff is that you own uptime and maintenance. You must host an HTTPS endpoint, observe failures, and update your code when your own requirements change.

Acuity trigger through an automation platform

Choose Zapier or Make when the workflow is simple, volumes are modest, and operating a receiver is not practical. Acuity’s appointment trigger can start an automation, which can then call an HTTP endpoint or an email action.

However, do not confuse convenience with secret safety. If the automation platform sends directly to Volanea, the Volanea key is stored in that platform’s connected-account or credential configuration, not in Acuity and never in a customer-visible step. You still need to understand duplicate-task behavior, retries, error handling, data retention, and whether the automation can consistently construct an idempotency key.

For important confirmation emails, a small purpose-built receiver is usually easier to audit than a long chain of automation steps. For a quick internal alert, an automation platform can be an appropriate first version.

Operational practices that keep the integration reliable

Treat this as a booking-system integration, not merely an email template. The details determine whether it remains dependable after the first week.

Keep separate development and production credentials. Use separate Acuity calendars or accounts where possible, separate Volanea API keys, and clearly labeled sender identities. A test appointment should never accidentally send a realistic confirmation from the production brand to a real customer.

Record structured events with at least the Acuity appointment ID, event type, receiver timestamp, idempotency key, Volanea response identifier when available, and final processing state. Avoid logging the API key, authorization header, raw payment-related data, or unnecessary intake-form responses.

Set alerts for a sustained increase in receiver errors, Volanea authorization failures, and email bounces. A sudden burst of errors can indicate an expired secret, a changed sender verification state, an endpoint deployment problem, or an upstream configuration change.

Finally, establish ownership. Someone should know who updates the sender identity, who rotates keys, who responds when appointment emails fail, and where to find the webhook registration. Integrations fail operationally as often as they fail technically.

Conclusion: make appointment emails event-driven and safe

To send email from Acuity Scheduling with Volanea, use Acuity’s appointment.scheduled webhook as the trigger and send through Volanea from a server-side middleware service. There is no native Acuity-to-Volanea app to install, so the middleware is where you safely keep credentials, map booking details, format timezones, and prevent retries from duplicating confirmations.

Start with one event and one narrowly scoped confirmation. Persist an idempotency key based on the appointment ID, test incomplete and retried payloads, and only then expand to reschedules, cancellations, reminders, or internal notifications. That approach produces an integration that is useful to customers and maintainable for your team.

FAQ

Can Acuity Scheduling send directly to Volanea?

Acuity does not have a native Volanea marketplace integration. Use an Acuity webhook with your own middleware service, or use Zapier or Make as an intermediary when that better fits your operating model.

What Acuity event should trigger a booking confirmation?

Use appointment.scheduled for a new booking confirmation. Handle reschedules and cancellations as distinct event types so customers receive the correct message.

Where should the Volanea API key be stored?

Store it only in server-side environment variables or a managed secrets service used by your webhook receiver. Do not put it in browser JavaScript, an Acuity form, a public webhook URL, or source control.

How do I prevent duplicate emails after an Acuity retry?

Create a durable idempotency key from the event type and Acuity appointment id, then enforce uniqueness in a database before sending. Reuse the same key with the email API if the selected Volanea endpoint supports idempotency.

What if the webhook does not include a field my email needs?

Use a safe fallback for optional display fields. If the workflow requires data that is not in the webhook callback, retrieve the appointment server-side through Acuity’s API using protected Acuity API credentials.