| name | email-agent |
| description | Gmail integration via AtrisOS API. Read, send, archive emails. Use when user asks about email, inbox, or wants to send/check messages. |
| version | 1.2.0 |
| tags | ["email-agent","backend","email"] |
Email Agent
Drop this in ~/.claude/skills/email-agent/SKILL.md and Claude Code becomes your email assistant.
Bootstrap (ALWAYS Run First)
Before any email operation, run this bootstrap to ensure everything is set up:
#!/bin/bash
set -e
if ! command -v atris &> /dev/null; then
echo "Installing atris CLI..."
npm install -g atris
fi
if [ -n "$ATRIS_TOKEN" ]; then
TOKEN="$ATRIS_TOKEN"
elif [ -f ~/.atris/credentials.json ]; then
if command -v node &> /dev/null; then
TOKEN=$(node -e "console.log(require('$HOME/.atris/credentials.json').token)")
elif command -v python3 &> /dev/null; then
TOKEN=$(python3 -c "import json,os; print(json.load(open(os.path.expanduser('~/.atris/credentials.json')))['token'])")
elif command -v jq &> /dev/null; then
TOKEN=$(jq -r '.token' ~/.atris/credentials.json)
else
echo "Error: Need node, python3, or jq to read credentials"
exit 1
fi
else
echo "Not logged in to AtrisOS."
echo ""
echo "Option 1: Set ATRIS_TOKEN environment variable"
echo "Option 2: Run 'atris login' (interactive)"
echo "Option 3: Get token from https://atris.ai/auth/cli"
echo ""
exit 1
fi
STATUS=$(curl -s "https://api.atris.ai/api/integrations/gmail/status" \
-H "Authorization: Bearer $TOKEN")
if echo "$STATUS" | grep -q "Token expired\|Not authenticated"; then
echo "Token expired. Please re-authenticate:"
echo " Run: atris login --force"
echo " Or get new token from: https://atris.ai/auth/cli"
exit 1
fi
if command -v node &> /dev/null; then
CONNECTED=$(node -e "console.log(JSON.parse('$STATUS').connected || false)")
elif command -v python3 &> /dev/null; then
CONNECTED=$(echo "$STATUS" | python3 -c "import sys,json; print(json.load(sys.stdin).get('connected', False))")
else
CONNECTED=$(echo "$STATUS" | jq -r '.connected // false')
fi
if [ "$CONNECTED" != "true" ] && [ "$CONNECTED" != "True" ]; then
echo "Gmail not connected. Getting authorization URL..."
AUTH=$(curl -s -X POST "https://api.atris.ai/api/integrations/gmail/start" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{}')
if command -v node &> /dev/null; then
URL=$(node -e "console.log(JSON.parse('$AUTH').auth_url || '')")
elif command -v python3 &> /dev/null; then
URL=$(echo "$AUTH" | python3 -c "import sys,json; print(json.load(sys.stdin).get('auth_url', ''))")
else
URL=$(echo "$AUTH" | jq -r '.auth_url // empty')
fi
echo ""
echo "Open this URL to connect your Gmail:"
echo "$URL"
echo ""
echo "After authorizing, run your email command again."
exit 0
fi
echo "Ready. Gmail is connected."
export ATRIS_TOKEN="$TOKEN"
Important: Run this script ONCE before email operations. If it exits with instructions, follow them, then run again.
API Reference
Base: https://api.atris.ai/api/integrations/gmail
All requests require: -H "Authorization: Bearer $TOKEN"
Get Token (after bootstrap)
TOKEN="${ATRIS_TOKEN:-$(node -e "console.log(require('$HOME/.atris/credentials.json').token)")}"
List Emails
curl -s "https://api.atris.ai/api/integrations/gmail/messages?query=in:inbox&max_results=20" \
-H "Authorization: Bearer $TOKEN"
Query syntax (Gmail search):
in:inbox — inbox only
in:inbox newer_than:1d — today's emails
is:unread — unread only
from:someone@example.com — from specific sender
subject:invoice — subject contains word
has:attachment — emails with attachments
Read Single Email
curl -s "https://api.atris.ai/api/integrations/gmail/messages/{message_id}" \
-H "Authorization: Bearer $TOKEN"
Send Email
curl -s -X POST "https://api.atris.ai/api/integrations/gmail/send" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"to": "recipient@example.com",
"subject": "Subject line",
"body": "Email body text"
}'
With CC/BCC:
curl -s -X POST "https://api.atris.ai/api/integrations/gmail/send" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"to": "recipient@example.com",
"cc": "copy@example.com",
"bcc": ["hidden1@example.com", "hidden2@example.com"],
"subject": "Subject line",
"body": "Email body text"
}'
Reply in thread (IMPORTANT — use this for all replies):
To reply within an existing email thread, you MUST pass thread_id and reply_to_message_id. Without these, Gmail creates a new thread.
curl -s "https://api.atris.ai/api/integrations/gmail/messages/{message_id}" \
-H "Authorization: Bearer $TOKEN"
curl -s -X POST "https://api.atris.ai/api/integrations/gmail/send" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"to": "original-sender@example.com",
"subject": "Re: Original Subject",
"body": "Your reply text here",
"thread_id": "THREAD_ID_FROM_STEP_1",
"reply_to_message_id": "MESSAGE_ID_FROM_STEP_1"
}'
thread_id — The thread ID from the original message. Tells Gmail which thread to add this to.
reply_to_message_id — The message ID you're replying to. The backend uses this to set In-Reply-To and References headers so Gmail threads it correctly.
subject — Must match the original subject with "Re: " prefix.
With attachments:
curl -s -X POST "https://api.atris.ai/api/integrations/gmail/send" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"to": "recipient@example.com",
"subject": "With attachment",
"body": "See attached.",
"attachments": [{"filename": "report.txt", "content": "base64-encoded-content", "mime_type": "text/plain"}]
}'
Drafts
List drafts:
curl -s "https://api.atris.ai/api/integrations/gmail/drafts?max_results=20" \
-H "Authorization: Bearer $TOKEN"
Read a draft:
curl -s "https://api.atris.ai/api/integrations/gmail/drafts/{draft_id}" \
-H "Authorization: Bearer $TOKEN"
Create a draft:
curl -s -X POST "https://api.atris.ai/api/integrations/gmail/drafts" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"to": "recipient@example.com",
"subject": "Subject line",
"body": "Draft body text"
}'
Supports same fields as send: cc, bcc, attachments, plus thread_id to attach to an existing thread.
Update a draft:
curl -s -X PUT "https://api.atris.ai/api/integrations/gmail/drafts/{draft_id}" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"to": "recipient@example.com",
"subject": "Updated subject",
"body": "Updated body"
}'
Send a draft:
curl -s -X POST "https://api.atris.ai/api/integrations/gmail/drafts/{draft_id}/send" \
-H "Authorization: Bearer $TOKEN"
Delete a draft:
curl -s -X DELETE "https://api.atris.ai/api/integrations/gmail/drafts/{draft_id}" \
-H "Authorization: Bearer $TOKEN"
Archive Email
curl -s -X POST "https://api.atris.ai/api/integrations/gmail/messages/{message_id}/archive" \
-H "Authorization: Bearer $TOKEN"
curl -s -X POST "https://api.atris.ai/api/integrations/gmail/messages/batch-archive" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"message_ids": ["id1", "id2", "id3"]}'
Check Status
curl -s "https://api.atris.ai/api/integrations/gmail/status" \
-H "Authorization: Bearer $TOKEN"
Disconnect Gmail
curl -s -X DELETE "https://api.atris.ai/api/integrations/gmail" \
-H "Authorization: Bearer $TOKEN"
Workflows
"Check my emails"
- Run bootstrap
- List messages:
GET /messages?query=in:inbox%20newer_than:1d&max_results=20
- Display: sender, subject, snippet for each
"Send email to X about Y"
- Run bootstrap
- Draft email content
- Show user the draft for approval
- On approval:
POST /send with {to, subject, body}
- Confirm: "Email sent!"
"Reply to this email"
- Run bootstrap
- Read the message:
GET /messages/{message_id} — extract id, thread_id, from, subject
- Draft reply content
- Show user the reply for approval
- On approval:
POST /send with {to, subject: "Re: ...", body, thread_id, reply_to_message_id}
- Verify: response
thread_id matches original thread_id (if it doesn't, something went wrong)
"Clean up my inbox"
- Run bootstrap
- List:
GET /messages?query=in:inbox&max_results=50
- Identify archivable emails (see rules below)
- Show user what will be archived, get approval
- Batch archive:
POST /batch-archive
"Show my drafts"
- Run bootstrap
- List drafts:
GET /gmail/drafts?max_results=20
- Display: to, subject, snippet for each
"Draft an email to X about Y"
- Run bootstrap
- Compose email content
- Show user the draft for review
- On approval:
POST /gmail/drafts with {to, subject, body}
- Confirm: "Draft saved! You can find it in Gmail."
"Send draft about X"
- Run bootstrap
- List drafts:
GET /gmail/drafts
- Find matching draft by subject/recipient
- Show user the draft content, confirm they want to send it
- Send:
POST /gmail/drafts/{draft_id}/send
"Archive all from [sender]"
- Run bootstrap
- Search:
GET /messages?query=from:{sender}
- Collect message IDs
- Confirm with user: "Found N emails from {sender}. Archive all?"
- Batch archive
Auto-Archive Rules
Safe to suggest archiving:
- From:
noreply@, notifications@, newsletter@, no-reply@
- Subject contains: digest, newsletter, notification, weekly update, daily summary
- Marketing: promotional, unsubscribe link present
NEVER auto-archive (always keep):
- Subject contains: invoice, receipt, payment, urgent, action required, password, verification, security
- From known contacts (check if user has replied to them)
- Flagged/starred messages
Always ask before archiving. Never archive without explicit user approval.
Error Handling
| Error | Meaning | Solution |
|---|
Token expired | AtrisOS session expired | Run atris login |
Gmail not connected | OAuth not completed | Re-run bootstrap, complete OAuth flow |
401 Unauthorized | Invalid/expired token | Run atris login |
400 Gmail not connected | No Gmail credentials | Complete OAuth via bootstrap |
429 Rate limited | Too many requests | Wait 60s, retry |
Invalid grant | Google revoked access | Re-connect Gmail via bootstrap |
Security Model
-
Local token (~/.atris/credentials.json): Your AtrisOS auth token, stored locally with 600 permissions. Same model as AWS CLI, GitHub CLI.
-
Gmail credentials: Your Gmail refresh token is stored server-side in AtrisOS encrypted vault. Never stored on your local machine.
-
Access control: AtrisOS API enforces that you can only access your own email. No cross-user access possible.
-
OAuth scopes: Only requests necessary Gmail permissions (read, send, modify labels).
-
HTTPS only: All API communication encrypted in transit.
Quick Reference
npm install -g atris && atris login
export ATRIS_TOKEN="your-token"
TOKEN="${ATRIS_TOKEN:-$(node -e "console.log(require('$HOME/.atris/credentials.json').token)")}"
curl -s "https://api.atris.ai/api/integrations/gmail/status" -H "Authorization: Bearer $TOKEN"
curl -s "https://api.atris.ai/api/integrations/gmail/messages?query=in:inbox&max_results=10" -H "Authorization: Bearer $TOKEN"
curl -s -X POST "https://api.atris.ai/api/integrations/gmail/send" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"to":"email@example.com","subject":"Hi","body":"Hello!"}'
curl -s -X POST "https://api.atris.ai/api/integrations/gmail/send" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"to":"sender@example.com","subject":"Re: Original","body":"Reply text","thread_id":"THREAD_ID","reply_to_message_id":"MSG_ID"}'
curl -s "https://api.atris.ai/api/integrations/gmail/drafts" -H "Authorization: Bearer $TOKEN"
curl -s -X POST "https://api.atris.ai/api/integrations/gmail/drafts" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"to":"email@example.com","subject":"Hi","body":"Draft text"}'
curl -s -X POST "https://api.atris.ai/api/integrations/gmail/drafts/{draft_id}/send" \
-H "Authorization: Bearer $TOKEN"