MCPInstall

cloudflare-agentic-inbox

Deploy and manage a self-hosted email client with AI agent on Cloudflare Workers

Installs
185
Last 24h
—
Trending rank
—
Security
Not audited

Install

Works with every agent the skills CLI supports.

npx skills add https://github.com/reason-machines/ai-agent-skills --skill cloudflare-agentic-inbox
  1. 01

    Install

    Run the command in your project. The CLI asks which agents to install for.

  2. 02

    Describe the task

    Ask your agent for something this skill covers. It loads cloudflare-agentic-inbox automatically when relevant.

  3. 03

    Review the result

    The agent follows the SKILL.md steps, scripts and references bundled with the skill.

SKILL.md

The instructions your agent reads when the skill is triggered.

Cloudflare Agentic Inbox

Skill by ara.so — AI Agent Skills collection.

Agentic Inbox is a self-hosted email client with an AI agent, running entirely on Cloudflare Workers. It uses Email Routing for receiving emails, Durable Objects with SQLite for per-mailbox storage, R2 for attachments, and Workers AI with the Cloudflare Agents SDK for AI-powered email assistance.

Installation & Deployment

Quick Deploy (Recommended)

  1. Deploy via button (provisions R2, Durable Objects, Workers AI automatically):

    # Visit: https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/agentic-inbox
    # When prompted, enter your domain: yourdomain.com
    
  2. Configure Cloudflare Access (required for production):

    • Navigate to Worker Settings → Domains & Routes
    • Enable one-click Cloudflare Access
    • Note the POLICY_AUD and TEAM_DOMAIN values
    • Set as Worker secrets:
    wrangler secret put POLICY_AUD
    wrangler secret put TEAM_DOMAIN
    
  3. Set up Email Routing:

    • Go to your domain in Cloudflare dashboard
    • Navigate to Email Routing
    • Create a catch-all rule forwarding to this Worker
  4. Enable Email Service:

    • Add send_email binding to wrangler.jsonc:
    {
      "send_email": [
        {
          "name": "SEB",
          "destination_address": "you@example.com"
        }
      ]
    }
    

Manual Setup

# Clone repository
git clone https://github.com/cloudflare/agentic-inbox.git
cd agentic-inbox

# Install dependencies
npm install

# Create R2 bucket
wrangler r2 bucket create agentic-inbox

# Configure domain in wrangler.jsonc
# Set DOMAINS variable to your domain

# Deploy
npm run deploy

Configuration

wrangler.jsonc Structure

{
  "name": "agentic-inbox",
  "main": "worker/index.ts",
  "compatibility_date": "2025-01-01",
  "compatibility_flags": ["nodejs_compat"],
  "vars": {
    "DOMAINS": "yourdomain.com"
  },
  "durable_objects": {
    "bindings": [
      {
        "name": "MAILBOX",
        "class_name": "MailboxDurableObject",
        "script_name": "agentic-inbox"
      },
      {
        "name": "EMAIL_AGENT",
        "class_name": "EmailAgentDurableObject",
        "script_name": "agentic-inbox"
      }
    ]
  },
  "r2_buckets": [
    {
      "binding": "R2",
      "bucket_name": "agentic-inbox"
    }
  ],
  "ai": {
    "binding": "AI"
  },
  "send_email": [
    {
      "name": "SEB",
      "destination_address": "fallback@yourdomain.com"
    }
  ]
}

Environment Variables (Secrets)

# Required for Cloudflare Access authentication
wrangler secret put POLICY_AUD
wrangler secret put TEAM_DOMAIN

# TEAM_DOMAIN can be either:
# - Your Access team URL: yourteam.cloudflareaccess.com
# - Full certs URL: yourteam.cloudflareaccess.com/cdn-cgi/access/certs

Development

Local Development

# Start dev server with hot reload
npm run dev

# Access at http://localhost:8787
# Note: Cloudflare Access is disabled in local development

Project Structure

agentic-inbox/
├── app/                    # React frontend
│   ├── routes/             # React Router v7 routes
│   ├── components/         # UI components
│   └── lib/                # Utilities, stores (Zustand)
├── worker/                 # Cloudflare Worker backend
│   ├── index.ts            # Hono router, email handler
│   ├── mailbox-do.ts       # Mailbox Durable Object
│   ├── email-agent-do.ts   # AI Agent Durable Object
│   └── auth.ts             # Access JWT validation
└── wrangler.jsonc          # Cloudflare configuration

Key API Patterns

Creating a Mailbox

// POST /api/mailboxes
const response = await fetch('/api/mailboxes', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    address: 'hello@yourdomain.com'
  })
});

const mailbox = await response.json();
// { id: "uuid", address: "hello@yourdomain.com", createdAt: "..." }

Sending Email

// POST /api/mailboxes/:id/send
const response = await fetch(`/api/mailboxes/${mailboxId}/send`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    to: ['recipient@example.com'],
    subject: 'Hello',
    body: '<p>Email content</p>',
    cc: [],
    bcc: [],
    inReplyTo: null,
    references: []
  })
});

Accessing AI Agent

// WebSocket connection to agent
const ws = new WebSocket(`wss://yourapp.workers.dev/agents/${mailboxId}`);

ws.onopen = () => {
  ws.send(JSON.stringify({
    type: 'message',
    content: 'Summarize my unread emails'
  }));
};

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  // Stream: { type: 'text-delta', text: '...' }
  // Tools: { type: 'tool-call', toolName: 'read_inbox', args: {...} }
  // Result: { type: 'tool-result', result: {...} }
};

Durable Object Implementation

Mailbox Durable Object

import { DurableObject } from 'cloudflare:workers';

export class MailboxDurableObject extends DurableObject {
  async fetch(request: Request) {
    const url = new URL(request.url);
    
    if (url.pathname === '/emails' && request.method === 'GET') {
      const stmt = this.ctx.storage.sql.exec(
        'SELECT * FROM emails ORDER BY receivedAt DESC LIMIT 50'
      );
      return Response.json(stmt.toArray());
    }
    
    if (url.pathname === '/emails' && request.method === 'POST') {
      const email = await request.json();
      const result = this.ctx.storage.sql.exec(
        `INSERT INTO emails (id, subject, from_address, to_address, body, receivedAt)
         VALUES (?, ?, ?, ?, ?, ?)`,
        email.id, email.subject, email.from, email.to, email.body, Date.now()
      );
      return Response.json({ success: true });
    }
    
    return new Response('Not found', { status: 404 });
  }
}

Email Agent Durable Object

import { AIChatAgent } from '@cloudflare/agents-sdk';
import { DurableObject } from 'cloudflare:workers';

export class EmailAgentDurableObject extends DurableObject {
  private agent?: AIChatAgent;
  
  async fetch(request: Request) {
    if (!this.agent) {
      this.agent = new AIChatAgent({
        model: '@cf/moonshotai/kimi-k2.5',
        binding: this.env.AI,
        tools: [
          {
            name: 'read_inbox',
            description: 'Read emails from the inbox',
            parameters: {
              type: 'object',
              properties: {
                limit: { type: 'number', default: 10 }
              }
            },
            handler: async ({ limit }) => {
              // Fetch from mailbox DO
              const mailboxId = this.ctx.id.toString();
              const emails = await this.fetchMailboxEmails(mailboxId, limit);
              return { emails };
            }
          },
          {
            name: 'send_email',
            description: 'Send an email',
            parameters: {
              type: 'object',
              properties: {
                to: { type: 'array', items: { type: 'string' } },
                subject: { type: 'string' },
                body: { type: 'string' }
              },
              required: ['to', 'subject', 'body']
            },
            handler: async ({ to, subject, body }) => {
              // Send via Email Service
              await this.env.SEB.send({
                from: this.getMailboxAddress(),
                to,
                subject,
                content: [{ type: 'text/html', value: body }]
              });
              return { success: true };
            }
          }
        ],
        systemPrompt: 'You are an email assistant...'
      });
    }
    
    // Handle WebSocket upgrade for streaming
    const upgradeHeader = request.headers.get('Upgrade');
    if (upgradeHeader === 'websocket') {
      const [client, server] = Object.values(new WebSocketPair());
      this.ctx.acceptWebSocket(server);
      return new Response(null, { status: 101, webSocket: client });
    }
    
    return new Response('Expected WebSocket', { status: 400 });
  }
  
  async webSocketMessage(ws: WebSocket, message: string) {
    const { content } = JSON.parse(message);
    
    for await (const chunk of this.agent.stream(content)) {
      ws.send(JSON.stringify(chunk));
    }
  }
}

Email Routing Handler

// worker/index.ts
import { EmailMessage } from 'cloudflare:email';

export default {
  async email(message: EmailMessage, env: Env) {
    const to = message.to;
    const mailboxId = await env.KV.get(`address:${to}`);
    
    if (!mailboxId) {
      message.setReject('Mailbox not found');
      return;
    }
    
    // Forward to Mailbox DO
    const id = env.MAILBOX.idFromString(mailboxId);
    const stub = env.MAILBOX.get(id);
    
    const emailData = {
      id: crypto.randomUUID(),
      from: message.from,
      to: message.to,
      subject: message.headers.get('subject'),
      body: await message.text(),
      receivedAt: Date.now()
    };
    
    await stub.fetch('https://mailbox/emails', {
      method: 'POST',
      body: JSON.stringify(emailData)
    });
  }
};

Common Patterns

Access Authentication Middleware

// worker/auth.ts
import * as jose from 'jose';

export async function validateAccessToken(request: Request, env: Env) {
  if (!env.POLICY_AUD || !env.TEAM_DOMAIN) {
    throw new Error('Cloudflare Access must be configured in production');
  }
  
  const token = request.headers.get('Cf-Access-Jwt-Assertion');
  if (!token) {
    throw new Error('Missing Access token');
  }
  
  const certsUrl = env.TEAM_DOMAIN.includes('/cdn-cgi/access/certs')
    ? env.TEAM_DOMAIN
    : `https://${env.TEAM_DOMAIN}/cdn-cgi/access/certs`;
  
  const jwks = jose.createRemoteJWKSet(new URL(certsUrl));
  
  const { payload } = await jose.jwtVerify(token, jwks, {
    audience: env.POLICY_AUD,
    issuer: env.TEAM_DOMAIN
  });
  
  return payload;
}

Agent System Prompt Customization

// Update system prompt per mailbox
const response = await fetch(`/api/mailboxes/${mailboxId}/agent/system-prompt`, {
  method: 'PUT',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    systemPrompt: `You are a professional email assistant for sales@company.com.
    Always be polite and concise. When drafting replies, maintain a friendly tone.`
  })
});

Attachment Storage in R2

// Store attachment in R2
async function storeAttachment(env: Env, emailId: string, file: File) {
  const key = `attachments/${emailId}/${file.name}`;
  await env.R2.put(key, file.stream(), {
    httpMetadata: {
      contentType: file.type
    }
  });
  return key;
}

// Retrieve attachment
async function getAttachment(env: Env, key: string) {
  const object = await env.R2.get(key);
  if (!object) return null;
  
  return new Response(object.body, {
    headers: {
      'Content-Type': object.httpMetadata?.contentType || 'application/octet-stream'
    }
  });
}

Troubleshooting

Invalid or Expired Access Token

Issue: Invalid or expired Access token error when accessing deployed app.

Solution:

# 1. Turn Access off and back on in Worker Settings → Domains & Routes
# 2. Copy new POLICY_AUD and TEAM_DOMAIN values from modal
# 3. Reset secrets
wrangler secret put POLICY_AUD
# Paste new value
wrangler secret put TEAM_DOMAIN
# Paste new value (can be team URL or full certs URL)

# 4. Redeploy
npm run deploy

Emails Not Arriving

Issue: Catch-all rule configured but emails not appearing in inbox.

Checklist:

  1. Verify Email Routing is enabled for your domain
  2. Check catch-all rule forwards to the Worker (not an email address)
  3. Confirm mailbox exists for the recipient address:
    # Check via API
    curl https://yourapp.workers.dev/api/mailboxes
    
  4. Check Worker logs:
    wrangler tail
    

Agent Not Responding

Issue: WebSocket connection fails or agent doesn't respond.

Solution:

// Verify Workers AI binding in wrangler.jsonc
{
  "ai": {
    "binding": "AI"
  }
}

// Check agent initialization
const ws = new WebSocket(`wss://yourapp.workers.dev/agents/${mailboxId}`);
ws.onerror = (error) => console.error('WebSocket error:', error);
ws.onclose = (event) => console.log('Closed:', event.code, event.reason);

R2 Bucket Not Found

Issue: Attachment upload fails with R2 error.

Solution:

# Create bucket if missing
wrangler r2 bucket create agentic-inbox

# Verify binding in wrangler.jsonc
{
  "r2_buckets": [
    {
      "binding": "R2",
      "bucket_name": "agentic-inbox"
    }
  ]
}

# Redeploy
npm run deploy

Send Email Not Working

Issue: send_email binding not available or emails not sending.

Solution:

  1. Enable Email Service in Cloudflare dashboard for your account
  2. Add binding to wrangler.jsonc:
    {
      "send_email": [
        {
          "name": "SEB",
          "destination_address": "verified@yourdomain.com"
        }
      ]
    }
    
  3. Verify domain ownership for sending
  4. Redeploy Worker

Local Development Access Errors

Issue: Access errors when running npm run dev.

Note: Cloudflare Access is intentionally disabled in local development. If you see Access-related errors in production mode locally, set:

// worker/auth.ts - for local testing only
if (env.ENVIRONMENT === 'development') {
  return { email: 'dev@localhost' }; // Skip Access validation
}

CLI Commands

# Development
npm run dev              # Start local dev server
npm run build            # Build frontend and worker
npm run deploy           # Deploy to Cloudflare

# Wrangler commands
wrangler tail            # Stream Worker logs
wrangler secret put KEY  # Set environment secret
wrangler r2 bucket list  # List R2 buckets
wrangler d1 execute DB --command "SELECT * FROM emails" # Query D1 (if using D1)

# Debugging
wrangler dev --remote    # Debug against production resources
wrangler kv:key list --binding=KV  # List KV keys (if using KV)

MCP Server Integration

Agentic Inbox includes an MCP server at /mcp for external AI tools (Claude Code, Cursor):

// claude_desktop_config.json or similar
{
  "mcpServers": {
    "agentic-inbox": {
      "url": "https://yourapp.workers.dev/mcp",
      "headers": {
        "Cf-Access-Client-Id": "YOUR_SERVICE_TOKEN_ID",
        "Cf-Access-Client-Secret": "YOUR_SERVICE_TOKEN_SECRET"
      }
    }
  }
}

MCP tools have access to all mailboxes by passing mailboxId parameter. Security relies on the Cloudflare Access policy.

FAQ

What does the cloudflare-agentic-inbox skill do?

Deploy and manage a self-hosted email client with AI agent on Cloudflare Workers

How do I install cloudflare-agentic-inbox?

Run npx skills add https://github.com/reason-machines/ai-agent-skills --skill cloudflare-agentic-inbox in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/cloudflare-agentic-inbox/ (personal) or .claude/skills/cloudflare-agentic-inbox/ (project).

Which AI agents can use cloudflare-agentic-inbox?

Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.

Is cloudflare-agentic-inbox safe to install?

No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.

Who maintains cloudflare-agentic-inbox?

It is published by reason-machines in reason-machines/ai-agent-skills. It has 185 recorded installs.

More from reason-machines/ai-agent-skills

View all →

agency-agents-zh-skill

reason-machines/ai-agent-skills

Open the skill to read its SKILL.md instructions.

AI & Agents322 installs

agent-reach-internet-access

reason-machines/ai-agent-skills

Open the skill to read its SKILL.md instructions.

AI & Agents228 installs

agent-sprite-forge-2d-game-assets

reason-machines/ai-agent-skills

Open the skill to read its SKILL.md instructions.

Media & Creative216 installs

agentmemory-persistent-memory

reason-machines/ai-agent-skills

Add persistent memory to AI coding agents using agentmemory - remembers context, preferences, and decisions across sessions

AI & Agents216 installs

awesome-agent-skills

reason-machines/ai-agent-skills

Open the skill to read its SKILL.md instructions.

AI & Agents214 installs

oh-my-openagent-orchestration

reason-machines/ai-agent-skills

Open the skill to read its SKILL.md instructions.

AI & Agents211 installs

Popular ai & agents skills

View category →

google-agents-cli-publish

google/agents-cli

This skill should be used when the user wants to "publish an agent", "publish my ADK agent", "register an agent with Gemini", "publish to Gemini" (formerly Gemini Enterprise), or needs guidance on the agents-cli publish gemini command. Also use when the user wants to "manage agents in Agent Registry", "list/update/delete registered agents", or "register an MCP server". Covers ADK vs A2A registration modes, programmatic and interactive usage, flag reference, auto-detection from deployment metadata, Agent Registry fleet management, and troubleshooting. Part of the agents-cli skills suite. Do NOT use for deployment (use google-agents-cli-deploy).

AI & Agents513.9K installs

google-agents-cli-eval

google/agents-cli

This skill should be used when the user wants to "run an evaluation", "evaluate my agent", "evaluate my ADK agent", "write an eval dataset", "analyze eval failures", "compare eval results", "optimize agent", or needs guidance on the Gemini platform eval methodology and the Quality Flywheel. Covers eval metrics, dataset schema, LLM-as-judge scoring, and common failure causes. Applies to any agents-cli project, whatever framework the agent is written in. Do NOT use for agent API code patterns (ADK: use google-agents-cli-adk-code), deployment (use google-agents-cli-deploy), or project scaffolding (use google-agents-cli-scaffold).

AI & Agents513.5K installs

skill-creator

anthropics/skills

Create new skills, modify and improve existing skills, and measure skill performance. Use when users want to create a skill from scratch, edit, or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy.

OfficialAI & Agents416.4K installs

codex-pet

prime-skills/runcomfy-agent-skills

Codex Pet generator on RunComfy. Build a Codex-compatible Codex Pet spritesheet.webp + pet.json from a single reference image, drop it into ${CODEXHOME:-$HOME/.codex}/pets/ / and Codex picks it up as a custom Codex Pet next to the 8 built-ins. This skill produces the exact Codex Pet atlas Codex expects (1536x1872 PNG/WebP, 8 cols x 9 rows, 192x208 cells, 9 animation states — idle, running-right, running-left, waving, jumping, failed, waiting, running, review). Calls OpenAI GPT Image 2 edit ONCE via the local RunComfy CLI as runcomfy run openai/gpt-image-2/edit to produce a canonical Codex Pet pose, then assembles all 9 animation rows programmatically with ImageMagick micro-transforms — no Codex Pro, no $imagegen, no OPENAIAPIKEY required, only RUNCOMFYTOKEN. Triggers on "codex pet", "create codex pet", "make codex pet", "hatch codex pet", "/hatch image", "desktop pet codex", "codex pets", "spritesheet.webp", or any explicit ask to build a custom pet for OpenAI Codex.

AI & Agents402.1K installs

full-output-enforcement

leonxlnx/taste-skill

Overrides default LLM truncation behavior. Enforces complete code generation, bans placeholder patterns, and handles token-limit splits cleanly. Apply to any task requiring exhaustive, unabridged output.

AI & Agents401K installs

executing-plans

obra/superpowers

Use when executing an implementation plan in the current session as the implementer yourself — your human partner chose inline execution, or no subagent tool is available

AI & Agents245.7K installs