Send email from Zoho Desk without exposing an API key by using Zoho Desk’s outbound webhook capability as the trigger and a small server-side relay as the delivery layer. This is the practical route when you need a support event—such as a newly created ticket—to start a branded transactional message through Volanea.

Zoho Desk does not need a native Volanea marketplace app for this workflow. Instead, Zoho Desk sends a webhook when a selected event occurs, your endpoint verifies and normalizes that event, and the endpoint makes an authenticated POST /v1/send request to Volanea. That extra relay is not needless plumbing: it is where you protect credentials, define recipient rules, prevent duplicate sends, and keep the webhook response fast enough for Zoho Desk.

This guide uses the Ticket_Add event as the concrete trigger. It sends an internal “new ticket received” alert to a support operations address. That is intentional: a ticket webhook payload may not always carry a usable customer email address, depending on the record, channel, and fields included in the event. If you need to email the ticket contact, use a deliberate contact lookup or store an approved notification address in a ticket custom field rather than guessing at a field that may not be present.

What the Zoho Desk to Volanea flow looks like

The integration has four parts:

  1. A customer creates a support request through a Zoho Desk channel, and Zoho Desk creates a ticket.
  2. Zoho Desk emits a Ticket_Add webhook event to an HTTPS endpoint you control.
  3. Your endpoint verifies Zoho Desk’s JWT header, extracts the ticket fields you care about, records the event as processed, and immediately returns a success response.
  4. A background worker—or a very small, bounded send operation—calls Volanea’s email API with your secret key and a stable Idempotency-Key.

The important design principle is that Zoho Desk is the event source, not the mailer. Volanea is the mail delivery service. Your relay is the policy boundary between them.

That boundary lets you make decisions that neither side can safely make alone. For example, you can send only for tickets in one department, suppress alerts for spam-tagged tickets, route high-priority tickets to an escalation address, remove unsafe HTML from a description, and ensure a delivery retry cannot create a second alert.

Zoho Desk supports webhooks for help-desk events including adding tickets and updating contacts. Its webhook documentation says the receiving endpoint must return 200 OK within five seconds; a non-200 response or a timeout is treated as failed receipt. That short deadline is why the endpoint should acknowledge the webhook quickly and avoid waiting on unrelated database operations, long template rendering, or slow downstream systems.

The concrete Zoho Desk trigger: Ticket_Add

For this integration, the trigger is a Zoho Desk ticket being added. In Zoho Desk webhook event terminology, that is Ticket_Add.

A ticket can originate from several channels: an incoming support email, a Help Center submission, a web form, chat, social channels, or an agent-created request. Those different origins matter. A ticket created from an internal workflow may not have the same contact details or description fields as a ticket created from a customer-facing form.

Use Ticket_Add when your email should happen once when the support request enters Zoho Desk. Typical examples include:

  • Alerting an on-call team that a ticket has arrived in a critical department.
  • Sending a separate operational notification to a vendor, account owner, or incident queue.
  • Creating a low-volume escalation message for tickets whose incoming data matches a known condition.
  • Sending a confirmation to a contact only after your relay has looked up and validated the intended recipient.

Do not use a general ticket-update event to send a one-time acknowledgement unless you also have durable state that proves the acknowledgement has already been sent. Ticket updates happen frequently: assignment changes, status changes, agent edits, automated field updates, SLA activity, and incoming conversations can all create updates. If your condition is “ticket added,” use the event that represents creation.

Create the webhook in Zoho Desk

In Zoho Desk, webhook creation is controlled by the Webhook permission on the user’s profile. The webhook area is available from the setup experience under Developer Space. Create a webhook that points to an HTTPS endpoint such as:

https://support.example.com/webhooks/zoho-desk/ticket-added

Subscribe it to the ticket-added event and enable it only after your endpoint is deployed and tested. Zoho Desk’s documented edition limits are important for planning: Free and Standard have zero enabled webhooks, while Professional, Enterprise, and Ultimate allow enabled webhooks in increasing quantities. If your edition has no webhook capacity, skip ahead to the middleware route in this guide rather than trying to place a Volanea secret in a browser, form, or publicly visible automation URL.

Use a dedicated endpoint for this integration. A route named for its source and purpose makes log searches, access control, and incident response much easier than a shared endpoint such as /webhook.

The Zoho Desk webhook payload you should expect

Zoho Desk sends webhook events as JSON. The event wrapper contains an event type, event time, organization ID, and a payload object for the affected record. A representative Ticket_Add request body has this shape:

[
  {
    "eventType": "Ticket_Add",
    "eventTime": "1735689600000",
    "orgId": "2389290",
    "payload": {
      "id": "903000000123456",
      "ticketNumber": "10123",
      "subject": "Unable to reset my password",
      "description": "I requested a reset link, but nothing arrived.",
      "status": "Open",
      "priority": "High",
      "departmentId": "903000000001234",
      "contactId": "903000000004321",
      "createdTime": "2025-01-01T00:00:00.000Z",
      "channel": "EMAIL",
      "customFields": {}
    }
  }
]

Treat that as a shape, not a permanent contract for every ticket. The wrapper keys—eventType, eventTime, orgId, and payload—are the stable parts to build around. Fields inside payload vary by module, ticket channel, organization configuration, permissions, layouts, and custom fields.

In particular, do not assume payload.contact.email will always exist. A ticket event can contain a contactId while omitting an expanded contact record. If your email recipient is the customer, your relay should either query the appropriate Zoho Desk record through the Desk API using server-side OAuth credentials, or use an approved address stored in a field you control. For an internal support alert, use a server-side recipient such as SUPPORT_ALERT_TO and avoid this dependency altogether.

Map only the fields your email actually needs

The strongest mapping is boring and explicit:

Zoho Desk fieldVolanea email fieldWhy it is used
payload.ididempotency key suffix and email contextIdentifies the ticket reliably.
eventTypeidempotency key prefixPrevents collisions between event classes.
payload.subjectemail subject and HTML bodyGives operators the ticket topic.
payload.ticketNumberemail bodyGives humans a concise ticket reference.
payload.priorityemail bodyHelps triage an alert.
payload.statusemail bodyPreserves current workflow state.
payload.descriptionescaped email bodyIncludes useful context without trusting raw HTML.
payload.departmentIdrule inputLets your relay route or filter deliberately.

Avoid copying every field into an email. Ticket payloads can contain personal information, internal notes, custom data, and user-supplied text. Send the minimum operational context necessary for the recipient to act.

Why the Volanea API key belongs in the relay, not Zoho Desk

The Volanea API key must live in server-side secret storage: for example, an encrypted deployment secret, secret manager, worker secret, or protected environment variable. It must not be placed in a public Help Center page, client-side JavaScript, a visible form configuration, a ticket custom field, or an endpoint URL.

A secret in client-visible configuration is not a secret. Anyone who can inspect the page, browser network requests, embedded source, shared automation configuration, or exposed URL can copy it. Once copied, an attacker could use the key to submit email requests as your account.

The recommended configuration is:

VOLANEA_API_KEY=sk_live_replace_with_your_secret
VOLANEA_FROM_EMAIL=support-notices@example.com
VOLANEA_FROM_NAME="Example Support"
SUPPORT_ALERT_TO=support-ops@example.com
ZOHO_DESK_ORG_ID=2389290

Store these values in the relay runtime, not in the Zoho Desk webhook configuration. Zoho Desk only needs the callback URL. The relay owns the Volanea authorization header.

This design also makes key rotation manageable. You can update the secret in one server-side location, deploy or reload the runtime, test with a known ticket, and revoke the old key. No agents, customers, browser sessions, or third-party workflow viewers need access to the credential.

Before sending, make sure the address in VOLANEA_FROM_EMAIL uses a domain verified in Volanea. Domain verification and correct SPF, DKIM, and DMARC configuration matter because the integration can be technically successful while messages still perform poorly if the sending identity is not authenticated. The Volanea API reference and setup guides are the right place to confirm your sending domain and API settings before going live.

Working Node.js relay: Zoho Desk payload to Volanea email

The example below uses Express and jose. It expects Zoho Desk to send the JWT in the X-ZDesk-JWT header, verifies that token against Zoho’s JWKS endpoint, accepts the webhook body, records a deterministic event key, and calls Volanea’s POST /v1/send endpoint.

The example uses an in-memory Set only to make the mapping easy to read. Replace it in production with a database table or a durable key-value store that has a unique constraint on event_key. A process restart clears an in-memory set and would allow a retry to send again.

// package.json dependencies:
//   "express": "^4.21.0",
//   "jose": "^5.9.0"

import express from "express";
import { createRemoteJWKSet, jwtVerify } from "jose";

const app = express();
app.use(express.json({ limit: "1mb" }));

const required = [
  "VOLANEA_API_KEY",
  "VOLANEA_FROM_EMAIL",
  "SUPPORT_ALERT_TO",
  "ZOHO_DESK_ORG_ID"
];

for (const name of required) {
  if (!process.env[name]) throw new Error(`Missing required secret: ${name}`);
}

const zohoJwks = createRemoteJWKSet(
  new URL("https://desk.zoho.com/.well-known/jwks.json")
);

// Demo only. Use Postgres, Redis, DynamoDB, etc. in production.
const processedEvents = new Set();

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

function text(value) {
  return value === null || value === undefined ? "" : String(value).trim();
}

app.post("/webhooks/zoho-desk/ticket-added", async (req, res) => {
  const jwt = req.get("X-ZDesk-JWT");
  if (!jwt) return res.status(401).json({ error: "Missing X-ZDesk-JWT" });

  try {
    // Verify the signed source request before processing its contents.
    await jwtVerify(jwt, zohoJwks, {
      algorithms: ["RS256"]
    });
  } catch (error) {
    console.error("Zoho Desk JWT verification failed", error);
    return res.status(401).json({ error: "Invalid webhook signature" });
  }

  const event = Array.isArray(req.body) ? req.body[0] : req.body;
  const ticket = event?.payload;

  if (!event || event.eventType !== "Ticket_Add" || !ticket?.id) {
    // Acknowledge unrelated events if this endpoint is accidentally reused.
    return res.status(200).json({ ignored: true });
  }

  if (String(event.orgId) !== process.env.ZOHO_DESK_ORG_ID) {
    return res.status(403).json({ error: "Unexpected Zoho Desk organization" });
  }

  const eventKey = `zoho-desk:${event.orgId}:${event.eventType}:${ticket.id}`;

  // In production, atomically INSERT this key before sending.
  if (processedEvents.has(eventKey)) {
    return res.status(200).json({ duplicate: true });
  }
  processedEvents.add(eventKey);

  const ticketNumber = text(ticket.ticketNumber) || text(ticket.id);
  const subject = text(ticket.subject) || "Untitled ticket";
  const priority = text(ticket.priority) || "Not set";
  const status = text(ticket.status) || "Not set";
  const description = text(ticket.description) || "No description supplied.";

  const volaneaPayload = {
    from: {
      email: process.env.VOLANEA_FROM_EMAIL,
      name: process.env.VOLANEA_FROM_NAME || "Example Support"
    },
    to: [process.env.SUPPORT_ALERT_TO],
    subject: `[Zoho Desk #${ticketNumber}] ${subject}`,
    text: [
      "A new Zoho Desk ticket was created.",
      `Ticket: ${ticketNumber}`,
      `Priority: ${priority}`,
      `Status: ${status}`,
      "",
      description
    ].join("\n"),
    html: `
      <h2>New Zoho Desk ticket</h2>
      <p><strong>Ticket:</strong> ${escapeHtml(ticketNumber)}</p>
      <p><strong>Priority:</strong> ${escapeHtml(priority)}</p>
      <p><strong>Status:</strong> ${escapeHtml(status)}</p>
      <p><strong>Subject:</strong> ${escapeHtml(subject)}</p>
      <hr>
      <p>${escapeHtml(description).replaceAll("\n", "<br>")}</p>
    `
  };

  try {
    const sendResponse = await fetch("https://api.volanea.com/v1/send", {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${process.env.VOLANEA_API_KEY}`,
        "Content-Type": "application/json",
        "Idempotency-Key": eventKey
      },
      body: JSON.stringify(volaneaPayload)
    });

    const responseBody = await sendResponse.text();
    if (!sendResponse.ok) {
      // Remove only if you want a later Zoho retry to retry the send.
      // A durable queue with explicit retry state is safer in production.
      processedEvents.delete(eventKey);
      console.error("Volanea send failed", sendResponse.status, responseBody);
      return res.status(500).json({ error: "Email send failed" });
    }

    console.log("Volanea accepted Zoho Desk alert", {
      eventKey,
      ticketId: ticket.id,
      result: responseBody
    });

    return res.status(200).json({ ok: true });
  } catch (error) {
    processedEvents.delete(eventKey);
    console.error("Volanea request error", error);
    return res.status(500).json({ error: "Email request failed" });
  }
});

app.listen(process.env.PORT || 3000, () => {
  console.log("Zoho Desk webhook relay listening");
});

What the code maps

The webhook body is an array, so the code first selects req.body[0]. It checks that eventType is exactly Ticket_Add, then uses payload.id to create a deterministic event key.

The Volanea request has these essential components:

  • Authorization: Bearer <Volanea API key> authenticates the server-to-server request.
  • Idempotency-Key makes repeated attempts for the same ticket-added event safe.
  • from identifies the verified sending identity.
  • to uses a server-side internal operations address.
  • subject, text, and html are derived from safe, explicitly selected ticket fields.

The escapeHtml() function matters. Ticket descriptions can be customer-provided and may contain markup-like text. Rendering it directly into HTML turns a notification email into a place where untrusted markup can alter the message structure. Escape the content first, and preserve line breaks only after escaping.

Use idempotency for retries and duplicate protection

Webhooks are not exactly-once delivery systems. A sender can retry because it did not receive a response in time, because a transient network failure occurred, or because your endpoint responded with an error after work had already started. Your application must assume a Ticket_Add event can arrive more than once.

The first protection is a durable event ledger. Store one row per processed event, with a unique key such as:

zoho-desk:{orgId}:Ticket_Add:{ticketId}

The second protection is Volanea’s Idempotency-Key header. Send the same key for the same logical message. If your process loses the response after Volanea accepts the message, a retry using the same key is safer than generating a fresh request identity.

Do not reuse an idempotency key for a different email body. An idempotency key identifies one logical operation, not one recipient or one workflow forever. If you later add a separate “priority escalated” email for the same ticket, use a distinct event key such as:

zoho-desk:{orgId}:Ticket_Priority_Changed:{ticketId}:{newPriority}

A useful production table has at least these columns:

  • event_key as a unique key.
  • ticket_id and org_id for investigation.
  • event_type and source event time.
  • send_status such as queued, accepted, or failed.
  • Volanea’s response identifier when available.
  • attempt_count, last_error, and timestamps.

This data gives support and engineering teams one shared answer when someone asks, “Did the alert send?”

When this breaks

Every integration needs a failure model. The following problems are specific to the Zoho Desk webhook-to-relay-to-Volanea hop and should be designed for before the workflow is business-critical.

Zoho Desk retries and duplicate messages

Zoho Desk considers webhook receipt failed when your endpoint does not return 200 OK within its response window. If your endpoint sends the email, then times out before sending its response, Zoho Desk can retry even though the first send may have been accepted.

Fix this with both a database-backed event key and Volanea’s Idempotency-Key. Do not rely on an in-memory flag, a timestamp comparison, or “it probably will not retry.” A unique database insert is the durable boundary: if inserting the event key conflicts, return 200 OK and do not send another message.

Webhook timeout

A slow route is a broken route. The receiver should authenticate the request, validate basic structure, place work onto a durable queue, and return 200 OK. The worker can then call Volanea, retry temporary failures, and update delivery state without holding Zoho Desk’s webhook connection open.

For low-volume notifications, a direct API call may work if the response is consistently fast. But a queue is the safer architecture because it separates event receipt from email dispatch. It also means a temporary Volanea outage does not force you to choose between losing Zoho Desk events and making users wait on a webhook timeout.

Missing or differently shaped ticket fields

Ticket payloads vary. A ticket may have no description, no priority, a different custom-field layout, or only a contactId instead of a contact email. Build field fallbacks for display values and reject sends that require a recipient you cannot prove.

For example, do not turn an absent customer email into undefined, an empty string, or a guessed field path. If the workflow must email the contact, make a server-side Zoho Desk API lookup using a scoped OAuth connection, verify the returned email format, and decide whether the contact has permission to receive that notification.

Also test tickets from the real channels you use: an email-created ticket, Help Center submission, agent-created ticket, and automation-created ticket. A payload captured from one test ticket is not a guarantee for every creation path.

JWT verification fails

Zoho Desk includes its webhook authentication token in the X-ZDesk-JWT header. Reject requests that do not have a token or fail signature verification. Use HTTPS, pin the allowed signing algorithm, and load public keys through Zoho’s JWKS endpoint rather than treating a decoded JWT payload as verified data.

JWT failures can also result from server clock drift, stale JWKS caching, proxy header handling, or a regional endpoint difference. Log the event safely—never log the full JWT or API key—and alert on repeated verification failures. A sudden increase can indicate a deployment or proxy configuration problem.

The Volanea request is rejected

An HTTP 401 usually means the relay’s API key is missing, revoked, malformed, or using the wrong authorization scheme. A 403 can indicate that the sending domain or sender identity is not permitted. Validation errors normally mean your JSON does not match the endpoint contract or contains a missing recipient, sender, or content field.

Log the response status and a safely redacted error body. Do not log full email content if it may contain customer data. For stable production operations, classify errors into retryable and non-retryable categories: network timeouts and some server errors can be retried; malformed payloads and unauthorized credentials should create an alert for a human instead.

The message is accepted but has a delivery problem

An API acceptance response means Volanea accepted the sending request. It is not proof that the recipient’s inbox accepted the message. Bounces, suppressions, unsubscribes, recipient mailbox problems, and downstream filtering happen later in the delivery lifecycle.

For operational alerts, route delivery or bounce events to your observability system. For customer-facing messages, use a verified sender domain, keep content transactional and expected, and avoid using support-ticket automation as a bulk marketing channel. If you are estimating operational volume before launch, review transactional email pricing alongside expected ticket creation rates and retry behavior.

Sending to the ticket contact instead of an internal team

Customer-facing confirmation emails need more safeguards than internal alerts. The customer may already have received Zoho Desk’s own acknowledgement or an existing email-thread response. Sending another message through a separate provider can create duplicate communication, inconsistent threading, or confusing sender identities.

Use a Volanea message to the ticket contact only when there is a distinct reason, such as a security confirmation, a product-specific status notice, or a workflow that requires an email independent of Zoho Desk’s conversation system.

A safe customer-contact flow is:

  1. Receive Ticket_Add and verify the webhook JWT.
  2. Read payload.contactId.
  3. Use server-side Zoho Desk OAuth credentials to fetch the contact record from the Desk API.
  4. Validate the contact email and your business rule for sending.
  5. Construct a transactional message with a verified Volanea sender.
  6. Persist the event and use an idempotency key before dispatching.
  7. Record the resulting Volanea message identifier alongside the ticket ID.

Keep the Zoho OAuth token and the Volanea API key separate. They solve different authentication problems. The Zoho token authorizes your relay to retrieve Desk data; the Volanea key authorizes your relay to submit mail. Neither belongs in front-end code.

A middleware route when direct webhooks are unavailable

If your Zoho Desk edition does not include enabled webhooks, do not pretend there is a direct connection. Use an automation middleware such as Zapier, Make, Zoho Flow, or a similar server-side integration platform that can receive a Zoho Desk trigger and invoke your protected endpoint.

The architecture is still the same:

Zoho Desk trigger → middleware workflow → your secure relay → Volanea API

The middleware should send a minimal event payload to your relay, not a Volanea API key. Your relay should still own sender identity, recipient policy, HTML construction, idempotency, logging, and the Volanea call.

This approach is especially useful when a team does not operate an always-on application server. However, it introduces another retrying system into the chain. Treat the middleware run ID plus the Zoho ticket ID as observability data, and continue to deduplicate at your relay. Never assume middleware retries and Zoho retries will line up neatly.

Testing before you enable the workflow

Test the integration in layers. Start with the relay, then test a manually crafted request, then enable the actual Zoho Desk webhook.

Relay test checklist

  • Confirm the relay has VOLANEA_API_KEY, sender identity, recipient, and expected Zoho organization ID.
  • Confirm the sender domain has been verified in Volanea.
  • Confirm HTTPS terminates correctly at the public endpoint.
  • Confirm the route returns 401 without X-ZDesk-JWT.
  • Confirm a known valid Zoho Desk event is accepted.
  • Confirm the same event key does not create a second email.
  • Confirm a missing ticket subject or description produces a safe fallback, not a crash.
  • Confirm untrusted ticket description content is escaped in HTML.
  • Confirm Volanea errors are visible in logs without exposing secret values.

Production test scenarios

Create test tickets with a short subject and description, then deliberately test less tidy cases:

  • A blank description.
  • Unicode characters and an emoji in the subject.
  • An HTML-looking description such as <b>test</b>.
  • A very long description.
  • A ticket created through each active support channel.
  • A repeated webhook request using the same ticket ID.
  • A temporary Volanea failure or blocked outbound connection.

The final test is the one most teams skip: force a retry. It proves whether idempotency is real instead of theoretical.

Operational practices that keep the integration reliable

Start with one narrowly defined event and one recipient rule. Once that works, add routing rules gradually. A simple policy might route only high-priority tickets from a particular department to an on-call address, while ordinary tickets stay inside Zoho Desk.

Keep content templates in code or in a controlled Volanea template workflow, not scattered across multiple webhook configurations. Version changes, approvals, localized copy, and rollback are easier when one system owns message construction.

Monitor these metrics:

  • Zoho Desk webhook requests received.
  • JWT verification failures.
  • Events ignored because they do not match your expected type.
  • Duplicate events suppressed.
  • Volanea API acceptance and rejection rates.
  • Queue depth and retry count.
  • Time from webhook receipt to API acceptance.
  • Bounce, suppression, and complaint signals for customer-facing use cases.

Finally, give your support team a way to correlate systems. Put the Zoho ticket ID in structured logs and in a harmless part of the email subject or body. Store the Volanea response identifier with the event record. That turns a cross-platform delivery investigation from a manual search exercise into a direct lookup.

Conclusion

The dependable way to send email from Zoho Desk with Volanea is not to expose a secret in Zoho Desk or try to make a webhook behave like a complete email platform. Use Zoho Desk’s Ticket_Add webhook as the event trigger, receive it at a secure HTTPS relay, validate the X-ZDesk-JWT signature, map only the fields you need, and submit an authenticated, idempotent request to Volanea.

That design handles the details that create real operational problems: variable ticket fields, fast webhook acknowledgements, duplicate retries, credential protection, delivery visibility, and different rules for internal alerts versus customer notifications. It also leaves room to add queues, contact lookups, templates, and escalation logic without rebuilding the integration later.

FAQ

Can Zoho Desk send directly to the Volanea API?

Zoho Desk can send outbound webhook events, but a direct call would require placing the Volanea API key where it is difficult to protect and would leave you without a safe transformation, authentication, and idempotency layer. Use a server-side relay between Zoho Desk and Volanea.

What Zoho Desk event should start the email?

Use Ticket_Add when the desired behavior is one notification when a new ticket is created. Avoid broad ticket-update events unless you persist state and intentionally want messages for specific changes.

Why does the relay need to return quickly?

Zoho Desk treats webhook receipt as failed if the endpoint does not return 200 OK within its documented response window. Verify, enqueue or dispatch bounded work, and acknowledge promptly.

How do I prevent duplicate email sends?

Create a durable unique event key from the Zoho Desk organization, event type, and ticket ID, then send that same value as Volanea’s Idempotency-Key. Use both protections because webhook delivery can be retried.

What if my Zoho Desk plan has no webhooks?

Use a server-side automation middleware such as Zapier, Make, or Zoho Flow to pass the relevant ticket event to your relay. Keep the Volanea API key in the relay’s secret storage, not in the middleware step or a public configuration.