# Email Agent

> Gmail integration via AtrisOS API. Read, send, archive emails. Use when user asks about email, inbox, or wants to send/check messages.

- Skill: `atrislabs/email-agent` (Agent Skill)
- Install (CLI): `npx skillmds@latest add atrislabs/email-agent`
- Raw SKILL.md: https://api.skillmd.com/api/skills/atrislabs/email-agent/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: atrislabs (https://skillmd.com/u/atrislabs)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/atrislabs/email-agent

---


# 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:

```bash
#!/bin/bash
set -e

# 1. Check if atris CLI is installed
if ! command -v atris &> /dev/null; then
  echo "Installing atris CLI..."
  npm install -g atris
fi

# 2. Check if logged in to AtrisOS
if [ ! -f ~/.atris/credentials.json ]; then
  echo "Not logged in to AtrisOS."
  echo ""
  echo "Option 1 (interactive): Run 'atris login' and follow prompts"
  echo "Option 2 (non-interactive): Get token from https://atris.ai/auth/cli"
  echo "                           Then run: atris login --token YOUR_TOKEN"
  echo ""
  exit 1
fi

# 3. Extract token (try node first, then python3, then jq)
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

# 4. Check Gmail connection status (also validates token)
STATUS=$(curl -s "https://api.atris.ai/api/integrations/gmail/status" \
  -H "Authorization: Bearer $TOKEN")

# Check for token expiry
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

# Parse connected status
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)
```bash
TOKEN=$(node -e "console.log(require('$HOME/.atris/credentials.json').token)")
```

### List Emails
```bash
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
```bash
curl -s "https://api.atris.ai/api/integrations/gmail/messages/{message_id}" \
  -H "Authorization: Bearer $TOKEN"
```

### Send Email
```bash
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:**
```bash
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.

```bash
# 1. First, get the message you're replying to (extract thread_id and id)
curl -s "https://api.atris.ai/api/integrations/gmail/messages/{message_id}" \
  -H "Authorization: Bearer $TOKEN"
# Response includes: id, thread_id, subject, from, etc.

# 2. Send reply in the same thread
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:**
```bash
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:**
```bash
curl -s "https://api.atris.ai/api/integrations/gmail/drafts?max_results=20" \
  -H "Authorization: Bearer $TOKEN"
```

**Read a draft:**
```bash
curl -s "https://api.atris.ai/api/integrations/gmail/drafts/{draft_id}" \
  -H "Authorization: Bearer $TOKEN"
```

**Create a draft:**
```bash
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:**
```bash
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:**
```bash
curl -s -X POST "https://api.atris.ai/api/integrations/gmail/drafts/{draft_id}/send" \
  -H "Authorization: Bearer $TOKEN"
```

**Delete a draft:**
```bash
curl -s -X DELETE "https://api.atris.ai/api/integrations/gmail/drafts/{draft_id}" \
  -H "Authorization: Bearer $TOKEN"
```

### Mark as Read / Unread
```bash
# Mark as read
curl -s -X POST "https://api.atris.ai/api/integrations/gmail/messages/{message_id}/read" \
  -H "Authorization: Bearer $TOKEN"

# Mark as unread
curl -s -X POST "https://api.atris.ai/api/integrations/gmail/messages/{message_id}/unread" \
  -H "Authorization: Bearer $TOKEN"
```

### Archive Email
```bash
# Single message
curl -s -X POST "https://api.atris.ai/api/integrations/gmail/messages/{message_id}/archive" \
  -H "Authorization: Bearer $TOKEN"

# Batch archive
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"]}'
```

### Trash Email
```bash
# Single message
curl -s -X POST "https://api.atris.ai/api/integrations/gmail/messages/{message_id}/trash" \
  -H "Authorization: Bearer $TOKEN"

# Batch trash
curl -s -X POST "https://api.atris.ai/api/integrations/gmail/messages/batch-trash" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"message_ids": ["id1", "id2", "id3"]}'
```

### Check Status
```bash
curl -s "https://api.atris.ai/api/integrations/gmail/status" \
  -H "Authorization: Bearer $TOKEN"
```

### Disconnect Gmail
```bash
curl -s -X DELETE "https://api.atris.ai/api/integrations/gmail" \
  -H "Authorization: Bearer $TOKEN"
```

---

## Workflows

### "Check my emails"
1. Run bootstrap
2. List messages: `GET /messages?query=in:inbox%20newer_than:1d&max_results=20`
3. Display: sender, subject, snippet for each

### "Send email to X about Y"
1. Run bootstrap
2. Draft email content
3. **Show user the draft for approval**
4. On approval: `POST /send` with `{to, subject, body}`
5. Confirm: "Email sent!"

### "Reply to this email"
1. Run bootstrap
2. Read the message: `GET /messages/{message_id}` — extract `id`, `thread_id`, `from`, `subject`
3. Draft reply content
4. **Show user the reply for approval**
5. On approval: `POST /send` with `{to, subject: "Re: ...", body, thread_id, reply_to_message_id}`
6. Verify: response `thread_id` matches original thread_id (if it doesn't, something went wrong)

### "Clean up my inbox"
1. Run bootstrap
2. List: `GET /messages?query=in:inbox&max_results=50`
3. Identify archivable emails (see rules below)
4. **Show user what will be archived, get approval**
5. Batch archive: `POST /batch-archive`

### "Show my drafts"
1. Run bootstrap
2. List drafts: `GET /gmail/drafts?max_results=20`
3. Display: to, subject, snippet for each

### "Draft an email to X about Y"
1. Run bootstrap
2. Compose email content
3. **Show user the draft for review**
4. On approval: `POST /gmail/drafts` with `{to, subject, body}`
5. Confirm: "Draft saved! You can find it in Gmail."

### "Send draft about X"
1. Run bootstrap
2. List drafts: `GET /gmail/drafts`
3. Find matching draft by subject/recipient
4. **Show user the draft content, confirm they want to send it**
5. Send: `POST /gmail/drafts/{draft_id}/send`

### "Archive all from [sender]"
1. Run bootstrap
2. Search: `GET /messages?query=from:{sender}`
3. Collect message IDs
4. **Confirm with user**: "Found N emails from {sender}. Archive all?"
5. 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

1. **Local token** (`~/.atris/credentials.json`): Your AtrisOS auth token, stored locally with 600 permissions. Same model as AWS CLI, GitHub CLI.

2. **Gmail credentials**: Your Gmail refresh token is stored **server-side** in AtrisOS encrypted vault. Never stored on your local machine.

3. **Access control**: AtrisOS API enforces that you can only access your own email. No cross-user access possible.

4. **OAuth scopes**: Only requests necessary Gmail permissions (read, send, modify labels).

5. **HTTPS only**: All API communication encrypted in transit.

---

## Quick Reference

```bash
# Setup (one time)
npm install -g atris && atris login

# Get token
TOKEN=$(node -e "console.log(require('$HOME/.atris/credentials.json').token)")

# Check connection
curl -s "https://api.atris.ai/api/integrations/gmail/status" -H "Authorization: Bearer $TOKEN"

# List inbox
curl -s "https://api.atris.ai/api/integrations/gmail/messages?query=in:inbox&max_results=10" -H "Authorization: Bearer $TOKEN"

# Send new email
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!"}'

# Reply in thread (pass thread_id + reply_to_message_id)
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"}'

# List drafts
curl -s "https://api.atris.ai/api/integrations/gmail/drafts" -H "Authorization: Bearer $TOKEN"

# Create draft
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"}'

# Mark as read
curl -s -X POST "https://api.atris.ai/api/integrations/gmail/messages/{message_id}/read" -H "Authorization: Bearer $TOKEN"

# Trash an email
curl -s -X POST "https://api.atris.ai/api/integrations/gmail/messages/{message_id}/trash" -H "Authorization: Bearer $TOKEN"

# Send a draft
curl -s -X POST "https://api.atris.ai/api/integrations/gmail/drafts/{draft_id}/send" \
  -H "Authorization: Bearer $TOKEN"
```

