Guide
File Linear Issues from Email (CLI)
Support and bug reports land in a shared inbox, but the work gets tracked in Linear. The manual hop is copy-paste: read the email, open Linear, retype the title and body. This guide closes the loop from the terminal — pull messages as JSON with the CLI, then POST a single GraphQL issueCreate mutation to the Linear API with your team ID. No paid connector, no per-issue fee.
Written by Nick Barraclough Product Manager
Command references used in this guide: nylas email search, nylas email list, and nylas email read.
How do you file a Linear issue from an email?
You file a Linear issue from an email by pulling the message as JSON and sending one GraphQL issueCreate mutation. The Nylas CLI returns structured messages with nylas email search --json, and a single POST to https://api.linear.app/graphql creates the issue. Linear has no REST endpoint — every write goes through one GraphQL URL.
Two setup steps come first. Generate a personal API key in Linear under Settings, API, then export it as LINEAR_API_KEY. Linear authenticates with the raw key in the Authorization header — no Bearer prefix for personal keys, per the Linear authentication docs. Below, the CLI pulls the latest bug reports into a file so the next step has clean JSON to read.
# Pull bug-report email from the last day into a JSON file
nylas email search "bug" --after 2026-06-08 --json --limit 50 > items.jsonWhere does the Linear team ID come from?
The teamId is a UUID that scopes an issue to one Linear team, and issueCreate rejects the mutation without it. You can read it from the team URL, but the reliable path is a teams query against the same GraphQL endpoint. A workspace with 3 teams returns 3 nodes, each with an id and a human key like ENG.
Query the endpoint once and cache the result. The teams connection returns up to 50 nodes per page by default, which covers most workspaces in a single request. Match on the key you recognize — ENG, SUP, OPS — rather than the UUID, since the key is stable and readable while the UUID is not. The command below pipes the response through jq to print key-to-ID pairs.
curl -s -X POST https://api.linear.app/graphql \
-H "Authorization: $LINEAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query":"{ teams { nodes { id key name } } }"}' \
| jq -r '.data.teams.nodes[] | "\(.key)\t\(.id)"'How do you map an email to an issueCreate mutation?
Map the email subject to the issue title and the body to description, both passed inside the issueCreate input alongside the teamId. The mutation returns a success boolean and the new issue's identifier, so one round trip both creates and confirms. Linear's description field accepts Markdown.
Build the request with GraphQL variables instead of string-interpolating the email text, so a subject containing quotes or a backslash can't break the query. The pattern below reads each message from the JSON, extracts subject and snippet with jq, and sends the same mutation per message. A blank subject falls back to a placeholder so no issue is created untitled. The Linear mutations reference documents the full IssueCreateInput.
TEAM_ID="your-team-uuid"
MUTATION='mutation($t:String!,$d:String,$team:String!){ issueCreate(input:{title:$t, description:$d, teamId:$team}){ success issue { identifier url } } }'
jq -c '.[]' items.json | while read -r msg; do
title=$(echo "$msg" | jq -r '.subject // "(no subject)"')
body=$(echo "$msg" | jq -r '.snippet // ""')
jq -n --arg q "$MUTATION" --arg t "$title" --arg d "$body" --arg team "$TEAM_ID" \
'{query:$q, variables:{t:$t, d:$d, team:$team}}' \
| curl -s -X POST https://api.linear.app/graphql \
-H "Authorization: $LINEAR_API_KEY" \
-H "Content-Type: application/json" \
--data @- \
| jq -r '.data.issueCreate.issue.identifier'
doneHow do you avoid filing the same email twice?
Avoid duplicates by scoping the search to a narrow window and recording which messages you have filed. Run the pipeline daily against --after set to yesterday, so only the last 24 hours of mail is ever processed. The CLI returns a stable message id per message, which is the key you track against a local ledger.
Keep a flat file of processed IDs and skip any message already in it. This survives reruns, partial failures, and an overlapping search window without creating a second Linear issue. For a 50-message daily batch, the ledger check adds under 1 second. Append each ID only after issueCreate returns success: true, so a failed call retries on the next run.
touch filed.txt
jq -c '.[]' items.json | while read -r msg; do
id=$(echo "$msg" | jq -r '.id')
grep -qx "$id" filed.txt && continue # already filed, skip
# ... run the issueCreate mutation here ...
echo "$id" >> filed.txt
doneWhy drive this from a webhook instead of a cron poll?
A webhook files the issue the moment the email arrives, while a cron poll waits for the next scheduled run. For a support team, the gap between a daily poll and a webhook is up to 24 hours of delay before a bug report becomes a tracked Linear issue. The CLI exposes a message.created trigger that fires on each new message.
Run a local webhook server with nylas webhook server, which opens a tunnel and prints events as they arrive. The same issueCreate mapping runs per event — only the trigger changes from a poll to a push. The Linear API key stays separate from the mailbox grant the CLI manages, so you rotate them independently. The command below subscribes to new-message events.
# Subscribe to new-message events, then map each to an issueCreate call
nylas webhook create --url https://your-app.example.com/hooks --triggers message.createdNext steps
- Fireflies vs Nylas Notetaker: Meeting Bots — Fireflies is a meeting-notes app with a GraphQL API for reading…
- File Jira issues from email — the same pattern against the Jira REST API
- Email to Trello cards — create cards instead of issues
- Send email to a Notion database — file messages as Notion pages
- Email to Airtable — the same pattern into Airtable
- Extract email data with jq — the JSON-shaping toolkit
- Full command reference — every flag and subcommand documented
- Linear GraphQL API overview — the endpoint, pagination, and rate limits
- jq manual — the JSON filter reference used throughout
- RFC 5322 — the internet message format behind every email field