cloudflare-agentic-inbox

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Cloudflare Agentic Inbox

Cloudflare Agentic 收件箱

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.
ara.so提供的Skill — AI Agent技能合集。
Agentic Inbox是一款搭载AI Agent的自托管邮件客户端,完全运行在Cloudflare Workers上。它使用Email Routing接收邮件,通过搭配SQLite的Durable Objects实现每个邮箱的存储,利用R2存储附件,并借助Workers AI和Cloudflare Agents SDK提供AI驱动的邮件辅助功能。

Installation & Deployment

安装与部署

Quick Deploy (Recommended)

快速部署(推荐)

  1. Deploy via button (provisions R2, Durable Objects, Workers AI automatically):
    bash
    # 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:
    bash
    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
      :
    jsonc
    {
      "send_email": [
        {
          "name": "SEB",
          "destination_address": "you@example.com"
        }
      ]
    }
  1. 通过按钮部署(自动配置R2、Durable Objects、Workers AI):
    bash
    # 访问:https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/agentic-inbox
    # 提示时输入你的域名:yourdomain.com
  2. 配置Cloudflare Access(生产环境必需):
    • 导航至Worker设置 → 域名与路由
    • 启用一键式Cloudflare Access
    • 记录
      POLICY_AUD
      TEAM_DOMAIN
      的值
    • 设置为Worker密钥:
    bash
    wrangler secret put POLICY_AUD
    wrangler secret put TEAM_DOMAIN
  3. 设置Email Routing
    • 进入Cloudflare控制台中的你的域名页面
    • 导航至Email Routing
    • 创建一个全匹配规则,将邮件转发至该Worker
  4. 启用邮件服务
    • wrangler.jsonc
      中添加
      send_email
      绑定:
    jsonc
    {
      "send_email": [
        {
          "name": "SEB",
          "destination_address": "you@example.com"
        }
      ]
    }

Manual Setup

手动设置

bash
undefined
bash
undefined

Clone repository

克隆仓库

Install dependencies

安装依赖

npm install
npm install

Create R2 bucket

创建R2存储桶

wrangler r2 bucket create agentic-inbox
wrangler r2 bucket create agentic-inbox

Configure domain in wrangler.jsonc

在wrangler.jsonc中配置域名

Set DOMAINS variable to your domain

将DOMAINS变量设置为你的域名

Deploy

部署

npm run deploy
undefined
npm run deploy
undefined

Configuration

配置

wrangler.jsonc Structure

wrangler.jsonc结构

jsonc
{
  "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"
    }
  ]
}
jsonc
{
  "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)

环境变量(密钥)

bash
undefined
bash
undefined

Required for Cloudflare Access authentication

Cloudflare Access认证必需

wrangler secret put POLICY_AUD wrangler secret put TEAM_DOMAIN
wrangler secret put POLICY_AUD wrangler secret put TEAM_DOMAIN

TEAM_DOMAIN can be either:

TEAM_DOMAIN可以是以下任意一种:

- Your Access team URL: yourteam.cloudflareaccess.com

- 你的Access团队URL:yourteam.cloudflareaccess.com

- Full certs URL: yourteam.cloudflareaccess.com/cdn-cgi/access/certs

- 完整证书URL:yourteam.cloudflareaccess.com/cdn-cgi/access/certs

undefined
undefined

Development

开发

Local Development

本地开发

bash
undefined
bash
undefined

Start dev server with hot reload

启动带热重载的开发服务器

npm run dev
npm run dev

Note: Cloudflare Access is disabled in local development

注意:本地开发时Cloudflare Access处于禁用状态

undefined
undefined

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
agentic-inbox/
├── app/                    # React前端
│   ├── routes/             # React Router v7路由
│   ├── components/         # UI组件
│   └── lib/                #工具函数、状态存储(Zustand)
├── worker/                 # Cloudflare Worker后端
│   ├── index.ts            # Hono路由、邮件处理器
│   ├── mailbox-do.ts       # Mailbox Durable Object
│   ├── email-agent-do.ts   # AI Agent Durable Object
│   └── auth.ts             # Access JWT验证
└── wrangler.jsonc          # Cloudflare配置文件

Key API Patterns

核心API模式

Creating a Mailbox

创建邮箱

typescript
// 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: "..." }
typescript
// 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

发送邮件

typescript
// 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: []
  })
});
typescript
// 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

访问AI Agent

typescript
// 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: {...} }
};
typescript
// 与Agent建立WebSocket连接
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);
  // 流式输出:{ type: 'text-delta', text: '...' }
  // 工具调用:{ type: 'tool-call', toolName: 'read_inbox', args: {...} }
  // 结果返回:{ type: 'tool-result', result: {...} }
};

Durable Object Implementation

Durable Object实现

Mailbox Durable Object

Mailbox Durable Object

typescript
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 });
  }
}
typescript
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

Email Agent Durable Object

typescript
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));
    }
  }
}
typescript
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 }) => {
              // 从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 }) => {
              // 通过邮件服务发送
              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...'
      });
    }
    
    // 处理WebSocket升级以实现流式输出
    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

Email Routing处理器

typescript
// 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)
    });
  }
};
typescript
// 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;
    }
    
    // 转发至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

Access认证中间件

typescript
// 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;
}
typescript
// 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');
  }
  
  const token = request.headers.get('Cf-Access-Jwt-Assertion');
  if (!token) {
    throw new Error('缺少Access令牌');
  }
  
  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

Agent系统提示词自定义

typescript
// 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.`
  })
});
typescript
// 按邮箱更新系统提示词
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

R2附件存储

typescript
// 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'
    }
  });
}
typescript
// 在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;
}

// 获取附件
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

Access令牌无效或过期

Issue:
Invalid or expired Access token
error when accessing deployed app.
Solution:
bash
undefined
问题:访问已部署应用时出现
Invalid or expired Access token
错误。
解决方案
bash
undefined

1. Turn Access off and back on in Worker Settings → Domains & Routes

1. 在Worker设置 → 域名与路由中,先关闭再重新开启Access

2. Copy new POLICY_AUD and TEAM_DOMAIN values from modal

2. 从弹窗中复制新的POLICY_AUD和TEAM_DOMAIN值

3. Reset secrets

3. 重置密钥

wrangler secret put POLICY_AUD
wrangler secret put POLICY_AUD

Paste new value

粘贴新值

wrangler secret put TEAM_DOMAIN
wrangler secret put TEAM_DOMAIN

Paste new value (can be team URL or full certs URL)

粘贴新值(可以是团队URL或完整证书URL)

4. Redeploy

4. 重新部署

npm run deploy
undefined
npm run deploy
undefined

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:
    bash
    # Check via API
    curl https://yourapp.workers.dev/api/mailboxes
  4. Check Worker logs:
    bash
    wrangler tail
问题:已配置全匹配规则,但邮件未出现在收件箱中。
检查清单
  1. 确认你的域名已启用Email Routing
  2. 检查全匹配规则是否转发至Worker(而非邮箱地址)
  3. 确认收件人地址对应的邮箱已存在:
    bash
    # 通过API检查
    curl https://yourapp.workers.dev/api/mailboxes
  4. 查看Worker日志:
    bash
    wrangler tail

Agent Not Responding

Agent无响应

Issue: WebSocket connection fails or agent doesn't respond.
Solution:
typescript
// 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);
问题:WebSocket连接失败或Agent无响应。
解决方案
typescript
// 验证wrangler.jsonc中的Workers AI绑定
{
  "ai": {
    "binding": "AI"
  }
}

// 检查Agent初始化
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

R2存储桶未找到

Issue: Attachment upload fails with R2 error.
Solution:
bash
undefined
问题:附件上传失败,出现R2错误。
解决方案
bash
undefined

Create bucket if missing

若存储桶缺失则创建

wrangler r2 bucket create agentic-inbox
wrangler r2 bucket create agentic-inbox

Verify binding in wrangler.jsonc

验证wrangler.jsonc中的绑定

{ "r2_buckets": [ { "binding": "R2", "bucket_name": "agentic-inbox" } ] }
{ "r2_buckets": [ { "binding": "R2", "bucket_name": "agentic-inbox" } ] }

Redeploy

重新部署

npm run deploy
undefined
npm run deploy
undefined

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
    :
    jsonc
    {
      "send_email": [
        {
          "name": "SEB",
          "destination_address": "verified@yourdomain.com"
        }
      ]
    }
  3. Verify domain ownership for sending
  4. Redeploy Worker
问题
send_email
绑定不可用或邮件无法发送。
解决方案
  1. 在Cloudflare控制台中为你的账户启用邮件服务
  2. wrangler.jsonc
    中添加绑定:
    jsonc
    {
      "send_email": [
        {
          "name": "SEB",
          "destination_address": "verified@yourdomain.com"
        }
      ]
    }
  3. 验证发送域名的所有权
  4. 重新部署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:
typescript
// worker/auth.ts - for local testing only
if (env.ENVIRONMENT === 'development') {
  return { email: 'dev@localhost' }; // Skip Access validation
}
问题:运行
npm run dev
时出现访问错误。
说明:本地开发时Cloudflare Access会被有意禁用。如果在本地生产模式下看到Access相关错误,请设置:
typescript
// worker/auth.ts - 仅用于本地测试
if (env.ENVIRONMENT === 'development') {
  return { email: 'dev@localhost' }; // 跳过Access验证
}

CLI Commands

CLI命令

bash
undefined
bash
undefined

Development

开发相关

npm run dev # Start local dev server npm run build # Build frontend and worker npm run deploy # Deploy to Cloudflare
npm run dev # 启动本地开发服务器 npm run build # 构建前端和Worker npm run deploy # 部署至Cloudflare

Wrangler commands

Wrangler命令

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)
wrangler tail # 流式输出Worker日志 wrangler secret put KEY # 设置环境密钥 wrangler r2 bucket list # 列出R2存储桶 wrangler d1 execute DB --command "SELECT * FROM emails" # 查询D1(若使用D1)

Debugging

调试相关

wrangler dev --remote # Debug against production resources wrangler kv:key list --binding=KV # List KV keys (if using KV)
undefined
wrangler dev --remote # 针对生产资源进行调试 wrangler kv:key list --binding=KV # 列出KV键(若使用KV)
undefined

MCP Server Integration

MCP服务器集成

Agentic Inbox includes an MCP server at
/mcp
for external AI tools (Claude Code, Cursor):
json
// 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.
Agentic Inbox在
/mcp
路径提供了MCP服务器,供外部AI工具(如Claude Code、Cursor)使用:
json
// claude_desktop_config.json或类似配置文件
{
  "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工具可通过传递
mailboxId
参数访问所有邮箱,安全性依赖于Cloudflare Access策略。