Skip to main content

cloudflare-agentic-inbox

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

跳到安装

来源信息

仓库
reason-machines/ai-agent-skills
最近来源活动
2026年5月16日 23:50
检测到的 SKILL.md 语言
英语
星标
1
分支
1

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
cloudflare-agentic-inbox
description
Deploy and manage a self-hosted email client with AI agent on Cloudflare Workers
triggers
["set up agentic inbox on cloudflare","configure email routing with ai agent","deploy self-hosted email client","create cloudflare email worker","build email client with workers ai","set up durable objects for email","configure cloudflare access for inbox","troubleshoot agentic inbox deployment"]
# Cloudflare Agentic Inbox > Skill by [ara.so](https://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): ```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" } ] } ``` ### Manual Setup ```bash # 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 ```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 # 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 ```bash # 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 ```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: [] }) }); ``` ### Accessing 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: {...} } }; ``` ## Durable Object Implementation ### 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 }); } } ``` ### 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)); } } } ``` ## Email Routing Handler ```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) }); } }; ``` ## Common Patterns ### Access Authentication Middleware ```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; } ``` ### Agent System Prompt Customization ```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.` }) }); ``` ### Attachment Storage in 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' }
在 GitHub 查看
这个 SKILL.md 很大,SkillsMP 这里只预览前一段内容。 在 GitHub 查看