Source: https://cli.nylas.com/guides/email-to-zendesk-tickets

# 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](https://cli.nylas.com/authors/hazik) Director of Product Management

Reviewed by [Qasim Muhammad](https://cli.nylas.com/authors/qasim-muhammad)

Updated June 9, 2026

> **TL;DR:** Pull support mail with `nylas email search "*" --json`, then open one ticket per message by POSTing to the Zendesk Tickets API `/api/v2/tickets.json` endpoint with a `subject`, `comment`, and `requester`. The CLI handles the inbox; Zendesk handles the ticket. The trick that stops duplicate tickets on every poll is at the end.

Command references used in this guide: [`nylas email search`](https://cli.nylas.com/docs/commands/email-search), [`nylas email list`](https://cli.nylas.com/docs/commands/email-list), and [`nylas email read`](https://cli.nylas.com/docs/commands/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](https://developer.zendesk.com/api-reference/ticketing/tickets/tickets/#create-ticket).

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](https://cli.nylas.com/guides/getting-started) for other install methods.

```bash
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](https://developer.zendesk.com/documentation/ticketing/using-the-zendesk-api/best-practices-for-avoiding-rate-limiting/).

```bash
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.

```bash
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.

```bash
# 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](https://cli.nylas.com/guides/parse-inbound-email-webhooks) for the listener.

```bash
# 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

- [Email to Jira issues](https://cli.nylas.com/guides/email-to-jira-issues) — the same pattern into a Jira project
- [Email to ClickUp tasks](https://cli.nylas.com/guides/email-to-clickup-tasks) — open tasks instead of tickets
- [Send email to a Notion database](https://cli.nylas.com/guides/email-to-notion) — file messages as database pages
- [Parse inbound email webhooks](https://cli.nylas.com/guides/parse-inbound-email-webhooks) — open tickets in real time
- [Receive inbound email with the CLI](https://cli.nylas.com/guides/receive-inbound-email-cli) — read and filter the mailbox
- [Full command reference](https://cli.nylas.com/docs/commands) — every flag and subcommand documented
- [Zendesk API introduction](https://developer.zendesk.com/api-reference/ticketing/introduction/) — auth, subdomains, and request basics
- [Zendesk create-ticket reference](https://developer.zendesk.com/api-reference/ticketing/tickets/tickets/#create-ticket) — the full ticket object schema
- [RFC 5322](https://datatracker.ietf.org/doc/html/rfc5322) — the internet message format behind every sender and subject field
