Guide

Create Zendesk Tickets from Email (CLI)

Support requests arrive as email, but Zendesk's built-in mail connector forces every inbox through a single support address and its own parsing rules. When you need a shared mailbox, a forwarded alias, or a triage filter the connector won't honor, you can build the bridge yourself: the CLI returns each message as JSON, and the Zendesk Tickets API opens one ticket per message with the subject, requester, and body mapped to fields you control.

Written by Hazik Director of Product Management

Reviewed by Qasim Muhammad

VerifiedCLI 3.1.17 · Gmail, Outlook · last tested June 9, 2026

Command references used in this guide: nylas email search, nylas email list, and nylas email read.

How do I create a Zendesk ticket from email?

You create a Zendesk ticket from email by pulling the message as JSON and POSTing a ticket object to the Zendesk Tickets API. The CLI returns structured messages with nylas email search "*" --json, and a single POST to /api/v2/tickets.json opens a ticket. The request shape is documented in the Zendesk create-ticket reference.

First, install the CLI and confirm you can read the mailbox. The search command takes a positional query and an optional --from filter, so a single line returns the last 50 unread messages from a shared support alias. Pulling fields you don't need wastes tokens downstream, so scope the query before piping anywhere. See getting started for other install methods.

brew install nylas/nylas-cli/nylas

# Pull recent unread support mail as JSON
nylas email search "*" --from "support@yourco.com" --unread --json --limit 50 > inbox.json

What does the Zendesk ticket object need?

A Zendesk ticket needs at minimum a subject, a comment.body, and a requester with a name and email. The CLI message exposes .subject, .from[0].email, and .snippet, which map onto those fields one to one. Zendesk auto-creates the requester user if the email is new.

Build the ticket payload with jq so the mapping is explicit and null-safe. A message with no subject would otherwise open a blank ticket, so fall back to a placeholder. Zendesk requires API-token auth in the form {email}/token:{token}, and rejects requests over plain HTTP, so every call goes to your https://{subdomain}.zendesk.com host. The default rate ceiling on the Suite Team plan is 400 requests per minute, per the Zendesk rate-limit docs.

ZD="https://yourco.zendesk.com"
AUTH="agent@yourco.com/token:$ZENDESK_API_TOKEN"

jq -c '.[]' inbox.json | while read -r msg; do
  subject=$(echo "$msg" | jq -r '.subject // "(no subject)"')
  name=$(echo "$msg"    | jq -r '.from[0].name // .from[0].email')
  email=$(echo "$msg"   | jq -r '.from[0].email')
  body=$(echo "$msg"    | jq -r '.snippet // ""')

  jq -n --arg s "$subject" --arg b "$body" --arg n "$name" --arg e "$email" \
    '{ticket:{subject:$s, comment:{body:$b}, requester:{name:$n, email:$e}}}' \
    | curl -s -X POST "$ZD/api/v2/tickets.json" \
        -u "$AUTH" -H "Content-Type: application/json" -d @-
done

How do I add tags, priority, and the full body?

You enrich the ticket by adding tags, a priority, and the complete message body instead of the snippet. The snippet from search truncates around 100 characters, so for the full text fetch the message with nylas email read <message-id> --json and read its .body. Zendesk accepts four priority values: low, normal, high, and urgent.

The read command returns the same message ID you already have from the search JSON, so chaining is direct. Set priority from a keyword scan of the subject and attach tags so your triage views and triggers fire automatically. Tags are free-form strings; a routing trigger can move any ticket tagged urgent-email into a dedicated group within seconds of creation.

MSG_ID="message-id-from-inbox.json"
full=$(nylas email read "$MSG_ID" --json | jq -r '.body // .snippet')

prio="normal"
echo "$full" | grep -qiE 'outage|urgent|down' && prio="urgent"

jq -n --arg b "$full" --arg p "$prio" \
  '{ticket:{subject:"Inbound: support", comment:{body:$b}, priority:$p,
            tags:["email-intake","auto-created"]}}' \
  | curl -s -X POST "$ZD/api/v2/tickets.json" \
      -u "$AUTH" -H "Content-Type: application/json" -d @-

Why do I get duplicate tickets, and how do I stop them?

You get duplicate tickets because a poll that re-runs every few minutes re-reads the same messages and opens a new ticket each time. The fix is an external ID: Zendesk stores an external_id on every ticket, so set it to the message ID and the second create for the same email is a no-op you can detect. This is the payoff the TL;DR teased.

Before creating, query /api/v2/tickets.json?external_id=<id> and skip the POST when a ticket already exists. Combined with a --unread search and a nylas email mark step that flips the message to read after filing, you process each email exactly once. A 5-minute cron then keeps the queue current while opening zero duplicates across thousands of messages.

# Skip if a ticket already carries this message ID
exists=$(curl -s -u "$AUTH" \
  "$ZD/api/v2/tickets.json?external_id=$MSG_ID" | jq '.tickets | length')

if [ "$exists" -eq 0 ]; then
  jq -n --arg id "$MSG_ID" --arg s "$subject" --arg b "$body" \
    '{ticket:{external_id:$id, subject:$s, comment:{body:$b}}}' \
    | curl -s -X POST "$ZD/api/v2/tickets.json" \
        -u "$AUTH" -H "Content-Type: application/json" -d @-
fi

How do I open tickets in real time instead of polling?

You open tickets in real time by driving the same create step from a webhook instead of a cron poll. Nylas fires a message.created trigger the moment a message lands, so a small listener turns each delivery into a Zendesk ticket within a second or two rather than waiting up to 5 minutes for the next poll.

Register the webhook with nylas webhook create, pointing it at your endpoint and subscribing to the message trigger. The handler reuses the exact jq-to-ticket mapping from above; only the trigger changes from a scheduled pull to an event push. Keep the external_id dedupe in place, since providers occasionally redeliver a webhook. See parse inbound email webhooks for the listener.

# Subscribe to new-message events and route them to your ticket handler
nylas webhook create \
  --url "https://your-handler.example.com/zendesk" \
  --triggers message.created \
  --description "Open a Zendesk ticket per inbound support email"

Next steps