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
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.jsonWhy 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"
doneHow 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.shWhat 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
- Automate Sales Follow-Up Emails (CLI) — Run a sales follow-up cadence from the terminal
- Email to Jira issues — the sibling Atlassian pattern, into Jira
- Send email to a Notion database — the same flow into Notion
- Email to Coda — file messages as Coda rows
- Email to OneDrive — archive messages and attachments to OneDrive
- Extract email data with jq — the JSON-shaping toolkit
- Parse inbound email webhooks — the real-time handler in detail
- Full command reference — every flag and subcommand documented
- Confluence Cloud v2: create page — the official POST contract
- Atlassian Basic auth for REST APIs — token setup
- jq manual — the JSON processor reference