Guide
Create ClickUp Tasks from Email (CLI)
Support requests, bug reports, and client asks land in email, but the work gets tracked in ClickUp. The no-code tools that bridge the two meter each task they file. The CLI hands you each message as JSON; the ClickUp API turns it into a task in a chosen list. This guide builds an email-to-ClickUp pipeline you control, mapping subject and sender to task fields and running it on a schedule for free.
Written by Qasim Muhammad Staff SRE
Command references used in this guide: nylas email search, nylas email list, and nylas email read.
How do I create a ClickUp task from email?
Creating a ClickUp task from email takes two calls: pull the message as JSON, then POST it to ClickUp. The CLI returns structured messages with nylas email search --json, and a single request to the ClickUp API create task endpoint turns each one into a task in a list you pick. The whole flow is 2 commands, with no SMTP, no OAuth app registration, and no per-task fee.
First, get a token and a list ID. ClickUp issues a personal token under Settings → Apps that starts with pk_; you pass it in an Authorization header. The 2 inputs you need are that token and the numeric list ID found in a list's URL. If the CLI isn't installed yet, run the Homebrew one-liner below, then see getting started for the other three install methods.
# Install the CLI (macOS / Linux)
brew install nylas/nylas-cli/nylas
# Pull the messages you want to file into ClickUp
nylas email search "*" --subject "bug" --after 2026-06-08 --json --limit 50 > items.jsonHow do I map an email to ClickUp task fields?
Map each email to a ClickUp task by sending a JSON body where name holds the subject and description holds the sender and body. ClickUp requires only the name field; everything else is optional. Build the body with jq so values are escaped correctly and a missing subject never produces a nameless task. The endpoint returns the created task with its ID in under 1 second on a typical workspace.
The loop below reads one message at a time, extracts the subject and sender, and POSTs a task to the chosen list. Using jq -n to assemble the payload keeps the JSON valid even when a subject contains quotes or newlines — a common cause of a 400 response from the ClickUp API. Each task lands in the list ID you set in the first line.
LIST_ID="901234567"
jq -c '.[]' items.json | while read -r msg; do
subject=$(echo "$msg" | jq -r '.subject // "(no subject)"')
sender=$(echo "$msg" | jq -r '.from[0].email // "unknown"')
body=$(jq -n --arg n "$subject" --arg d "From: $sender" \
'{name: $n, description: $d}')
curl -s -X POST "https://api.clickup.com/api/v2/list/$LIST_ID/task" \
-H "Authorization: $CLICKUP_TOKEN" \
-H "Content-Type: application/json" \
-d "$body"
doneWhat ClickUp fields can I set from an email?
Beyond the name, the ClickUp create task endpoint accepts priority, due_date, tags, and assignees in the same JSON body. Priority is an integer from 1 (urgent) to 4 (low), per the ClickUp API reference. Due dates use Unix time in milliseconds.
That lets you route by sender. A message from your on-call alias becomes priority 1; everything else stays at the default. The snippet picks priority based on the sender address and sets a due date 24 hours out (86,400,000 milliseconds). Assignees take an array of numeric ClickUp member IDs, which you can read once from the team endpoint and hard-code, so the loop stays a single call per message.
subject=$(echo "$msg" | jq -r '.subject // "(no subject)"')
sender=$(echo "$msg" | jq -r '.from[0].email // "unknown"')
case "$sender" in
*oncall@*) prio=1 ;;
*) prio=3 ;;
esac
due=$(( ($(date +%s) + 86400) * 1000 ))
jq -n --arg n "$subject" --arg d "From: $sender" \
--argjson p "$prio" --argjson due "$due" \
'{name: $n, description: $d, priority: $p, due_date: $due}'How do I avoid creating duplicate tasks?
Avoid duplicates by recording each email's message ID after you file it and skipping IDs you have already seen. Every Nylas message carries a stable id field, so a flat file of processed IDs is enough to make the pipeline safe to run every 5 minutes from cron without ever double-filing a message.
The loop checks a seen.txt file before each POST and appends the ID after a successful create. Scoping the search to newer_than:1d caps the working set, and the ID check guards the edge where the same message matches two consecutive runs. Combined, the two limits keep a 1-minute cron job idempotent.
touch seen.txt
jq -c '.[]' items.json | while read -r msg; do
id=$(echo "$msg" | jq -r '.id')
grep -qF "$id" seen.txt && continue
subject=$(echo "$msg" | jq -r '.subject // "(no subject)"')
curl -s -X POST "https://api.clickup.com/api/v2/list/$LIST_ID/task" \
-H "Authorization: $CLICKUP_TOKEN" -H "Content-Type: application/json" \
-d "$(jq -n --arg n "$subject" '{name: $n}')" \
&& echo "$id" >> seen.txt
doneWhy use a webhook instead of polling?
A webhook files a task the instant an email arrives, instead of waiting for the next poll. The CLI ships a webhook server; run it with --tunnel cloudflared to expose a temporary public URL, so you can develop against live message.created events without deploying anything. A 5-minute poll adds up to 300 seconds of latency; a webhook fires within seconds of delivery.
The nylas webhook create command registers the endpoint and the triggers; nylas webhook server runs a local listener on port 9000 for testing. The mapping code from earlier is identical — only the trigger changes from a cron timer to an inbound event. Your ClickUp token stays separate from the mailbox grant the tool manages, so you rotate each one independently.
# Register a webhook for new mail
nylas webhook create \
--url https://your-endpoint.example.com/hooks \
--triggers message.created \
--description "Email to ClickUp"
# Or test locally with an auto-generated tunnel
nylas webhook server --port 9000Next steps
- Email to Trello cards — the same pattern into a Trello board
- Email to Jira issues — file messages as Jira tickets
- Send email to a Notion database — create Notion pages from email
- Email to Airtable — write rows to an Airtable base
- Full command reference — every flag and subcommand documented
- ClickUp docs: create task, authentication, and the full API reference