Guide

Create Jira Issues from Email (CLI)

Support requests, bug reports, and stakeholder asks all land in an inbox before they ever reach a backlog. The usual bridge is Jira's email-to-issue handler or a paid connector that charges per automation run. The Nylas CLI gives you the message as JSON; the Jira REST API creates an issue with a project key, issue type, summary, and description mapped from the email. This guide builds an email-to-Jira pipeline you control, formatting the description as Atlassian Document Format and running it on a schedule for free.

Written by Qasim Muhammad Staff SRE

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 Jira issue from an email?

You create a Jira issue from an email by pulling the message as JSON and POSTing it to the Jira REST API. The CLI returns structured messages with nylas email search --json, and a single call to /rest/api/3/issue creates a ticket from a project key, an issue type, a summary, and a description. The endpoint is documented in the Jira Cloud REST API v3 reference.

Two setup steps come first. Mint an API token from your Atlassian account, then authenticate with HTTP Basic auth where the username is your account email and the password is the token. Jira Cloud retired username-and-password Basic auth for the REST API in 2019, so the token is mandatory. Install the CLI with one Homebrew command, or see getting started for other methods.

brew install nylas/nylas-cli/nylas

# Pull the messages you want to turn into Jira issues
nylas email search "*" --subject "bug" --after 2026-06-08 --json --limit 50 > items.json

What does the Jira create-issue payload look like?

The Jira create-issue payload is a JSON object with a single fields key holding the project, issue type, summary, and description. The project takes a key like SUP, and the issue type takes a name like Bug or Task. Both must match your project exactly.

The field that trips up most first-time callers is description. Jira Cloud v3 rejects a plain string with a 400 error because it expects Atlassian Document Format (ADF), a structured JSON tree of content nodes. The minimal valid ADF body is a doc node wrapping a paragraph node wrapping a text node. The ADF schema is published in the Atlassian Document Format docs.

{
  "fields": {
    "project":   { "key": "SUP" },
    "issuetype": { "name": "Bug" },
    "summary":   "Login button unresponsive on mobile",
    "description": {
      "type": "doc",
      "version": 1,
      "content": [
        { "type": "paragraph",
          "content": [ { "type": "text", "text": "Reported via email." } ] }
      ]
    }
  }
}

How do I map an email to Jira fields with jq?

You map an email to Jira fields with jq by reading the subject into the summary and the body or sender into the ADF description. The CLI returns each message with .subject, .from[0].email, and .snippet fields, so one jq filter builds the whole payload.

Build the payload with jq so the ADF nesting and null handling stay correct. Jira summaries cap at 255 characters and reject newlines, so truncate the subject defensively. A message with no subject would otherwise send an empty summary, which returns a 400, so fall back to a placeholder. The jq manual covers the string and object builders used here.

jq -c '.[]' items.json | while read -r msg; do
  echo "$msg" | jq '{
    fields: {
      project:   { key: "SUP" },
      issuetype: { name: "Bug" },
      summary:   ((.subject // "(no subject)") | .[0:250]),
      description: {
        type: "doc", version: 1,
        content: [ { type: "paragraph", content: [
          { type: "text",
            text: ("From: " + (.from[0].email // "unknown") + "\n" + (.snippet // "")) }
        ] } ]
      }
    }
  }' > payload.json
  # POST step follows in the next section
done

How do I POST the issue to the Jira REST API?

You POST the issue with curl to https://your-site.atlassian.net/rest/api/3/issue using HTTP Basic auth and a JSON content type. A successful create returns HTTP 201 with the new issue key, such as SUP-142, in the response body. The key is what you store back on the email to avoid duplicates.

Send the payload built in the previous step. Pass credentials with curl -u email:token, set Content-Type: application/json, and capture the returned key with jq. Jira Cloud rate-limits the REST API by a cost budget rather than a fixed count, so a one-second pause between creates keeps a 50-message batch well under the threshold. Authentication details are in the REST API v3 intro.

SITE="your-site.atlassian.net"
key=$(curl -s -X POST "https://$SITE/rest/api/3/issue" \
  -u "$JIRA_EMAIL:$JIRA_TOKEN" \
  -H "Content-Type: application/json" \
  --data @payload.json | jq -r '.key')

echo "Created $key"
sleep 1

How do I avoid creating duplicate Jira issues?

You avoid duplicate Jira issues by de-duplicating on a stable field before each create. The simplest approach scopes the search to the last 24 hours with --after and runs the pipeline once a day on cron, so only that window of mail is ever processed. A daily run that files new bug reports keeps the backlog current without re-importing old threads.

For stronger guarantees, store the email's message ID in a Jira label or custom field and search Jira with JQL before creating. Or drive the same create step from a webhook so each new message files immediately instead of waiting for the next poll. The mapping and POST code stay identical; only the trigger changes. The Jira API token is separate from the mailbox grant the CLI manages, so rotate them on independent schedules. Scope each run with the --after flag, which works the same across every provider.

# Daily cron entry: only process the last 24 hours of mail
0 8 * * * /usr/local/bin/email-to-jira.sh >> /var/log/email-jira.log 2>&1

Next steps