Guide

Create Confluence Pages from Email

Support escalations, release notes, and incident write-ups start as email but belong in Confluence. The paid connectors charge per record and bury the field mapping. The Nylas CLI hands you the message as JSON; the Confluence Cloud REST API creates one page per message under a space you control. This guide builds an email-to-Confluence pipeline with nylas email search, jq, and a POST to /wiki/api/v2/pages.

Written by Prem Keshari Senior SRE

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 read, and nylas email list.

How do you create a Confluence page from an email?

You create a Confluence page from an email by pulling the message as JSON and POSTing it to the Confluence Cloud REST API. The Nylas CLI returns structured messages with nylas email search --json, and a single request to the /wiki/api/v2/pages endpoint creates a page with your title and body. The contract is documented in the Confluence Cloud v2 page reference.

Two setup steps come first. Create an API token from your Atlassian account, and authenticate with Basic auth using your email plus that token — Atlassian deprecated cookie-based and password auth, so the token is the only path for scripts. Per the Atlassian Basic auth docs, you base64-encode email:token. After that, each message maps to one page. Pull the last day's incident mail first.

# Pull the messages you want to file into Confluence
nylas email search "incident" --after 2026-06-08 --json --limit 50 > items.json

Why does the API want a numeric space ID, not the key?

The v2 API wants a numeric spaceId because Confluence Cloud moved off the human-readable space key for write operations. The key you see in a URL like /spaces/ENG is ENG, but the page POST rejects it. You resolve the numeric ID once with a GET to the spaces endpoint, then reuse it.

The Confluence Cloud v2 intro notes the v2 endpoints live under the /wiki/api/v2/ prefix, separate from the legacy /wiki/rest/api/ v1 routes. Filtering /spaces by your key returns a single result whose id is the value you pass as spaceId. Cache it in an environment variable so the per-message loop never re-fetches it. The lookup takes one request and the ID is stable for the life of the space.

# Resolve the space key (e.g. ENG) to its numeric ID, once
AUTH=$(printf '%s' "$EMAIL:$ATLASSIAN_TOKEN" | base64)
SPACE_ID=$(curl -s "https://your-domain.atlassian.net/wiki/api/v2/spaces?keys=ENG" \
  -H "Authorization: Basic $AUTH" \
  -H "Accept: application/json" | jq -r '.results[0].id')
echo "$SPACE_ID"

How do you map a message to a Confluence page body?

You map a message to a page by sending a JSON object with spaceId, a title, and a body whose representation is storage. The subject becomes the title; the sender and snippet become the body. Confluence storage format is XHTML, so wrap text in <p> tags rather than sending raw newlines.

Build the payload with jq so the JSON is escaped correctly and a missing subject falls back to a placeholder instead of an empty title, which the API rejects with a 400. The jq manual covers the // alternative operator used for the fallback. Each new page returns status 200 with the page id and a web link you can log.

jq -c '.[]' items.json | while read -r msg; do
  title=$(echo "$msg" | jq -r '.subject // "(no subject)"')
  sender=$(echo "$msg" | jq -r '.from[0].email // "unknown"')
  snippet=$(echo "$msg" | jq -r '.snippet // ""')
  payload=$(jq -n \
    --arg sid "$SPACE_ID" --arg t "$title" --arg s "$sender" --arg b "$snippet" \
    '{spaceId:$sid, status:"current", title:$t,
      body:{representation:"storage",
            value:("<p><strong>From:</strong> "+$s+"</p><p>"+$b+"</p>")}}')
  curl -s -X POST "https://your-domain.atlassian.net/wiki/api/v2/pages" \
    -H "Authorization: Basic $AUTH" \
    -H "Content-Type: application/json" \
    -d "$payload"
done

How do you keep Confluence in sync without duplicates?

You keep it in sync by scoping each run to new mail and de-duplicating on a stable field. Scope the search with --after set to yesterday and run the job daily, so each pass only processes the last 24 hours. To guard against a re-run, prefix the page title with the message ID and search the space before creating, skipping any title that already exists.

A daily cron entry files new incidents at 6 a.m. and finishes in seconds for a typical inbox of 20 to 50 messages. The Atlassian token is separate from the mailbox grant the CLI manages, so you rotate the two on independent schedules — Atlassian tokens can be revoked from the account security page without touching the email connection. Read a full message body with nylas email read when the snippet isn't enough for the page.

# Daily cron: file yesterday's incident mail into Confluence at 06:00
0 6 * * * nylas email search "incident" --after $(date -d yesterday +\%Y-\%m-\%d) --json --limit 100 > /tmp/items.json && /usr/local/bin/file-to-confluence.sh

What if you need real-time intake instead of a daily run?

For real-time intake, drive the same create step from a Nylas webhook instead of a poll. When a new message arrives, the platform fires a message.created event to your endpoint, and your handler runs the identical jq-and-POST mapping. Latency drops from up to 24 hours on a daily cron to a few seconds per message.

Register the webhook with nylas webhook create, pointing it at your handler URL and subscribing to the message.created trigger. The handler receives a message ID, fetches the message, and posts the page. Only the trigger changes; the Confluence payload code is the same one the cron uses. Verify each delivery signature before acting on it so a forged request can't create pages.

# Fire a page-creation step on every new message
nylas webhook create \
  --url https://your-handler.example.com/confluence \
  --triggers message.created \
  --description "Email to Confluence page"

Next steps