
Agent Email Inbox
Harden an agent-facing email inbox with Resend webhooks, per-sender rate limits, body truncation, and quoted-thread stripping before the agent sees content.
Install
npx skills add https://github.com/resend/resend-skills --skill agent-email-inboxWhat is this skill?
- Per-sender hourly rate limiting map pattern to cap abuse from a single address
- MAX_BODY_LENGTH truncation with explicit truncated marker to reduce token stuffing
- stripQuotedContent removes > lines and On-date-wrote blocks before safety analysis
- Advanced Patterns doc for operational hardening beyond basic Resend send/receive
- TypeScript-oriented snippets for webhook handlers and content hygiene
Adoption & trust: 3.1k installs on skills.sh; 129 GitHub stars; 2/3 security scanners passed (skills.sh audits).
Recommended Skills
Microsoft Foundrymicrosoft/azure-skills
Azure Aimicrosoft/azure-skills
Azure Hosted Copilot Sdkmicrosoft/azure-skills
Lark Eventlarksuite/cli
Running Claude Code Via Litellm Copilotxixu-me/skills
Setup Matt Pocock Skillsmattpocock/skills
Journey fit
Primary fit
The inbox pipeline is built while wiring the agent product; patterns are authored before production traffic. Inbound email as an agent channel is agent-tooling—distinct from generic SMTP integration because prompts and safety rules wrap the handler.
Common Questions / FAQ
Is Agent Email Inbox safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.
SKILL.md
READMESKILL.md - Agent Email Inbox
# Advanced Patterns — Rate Limiting, Content Limits, Troubleshooting ## Rate Limiting per Sender Prevent any single sender from overwhelming your agent with emails: ```typescript const rateLimiter = new Map<string, { count: number; resetAt: Date }>(); function checkRateLimit(sender: string, maxPerHour: number = 10): boolean { const now = new Date(); const entry = rateLimiter.get(sender); if (!entry || entry.resetAt < now) { rateLimiter.set(sender, { count: 1, resetAt: new Date(now.getTime() + 3600000) }); return true; } if (entry.count >= maxPerHour) { return false; } entry.count++; return true; } ``` ## Content Length Limits Prevent token stuffing by truncating oversized email content: ```typescript const MAX_BODY_LENGTH = 10000; // Prevent token stuffing function truncateContent(content: string): string { if (content.length > MAX_BODY_LENGTH) { return content.slice(0, MAX_BODY_LENGTH) + '\n[Content truncated for security]'; } return content; } ``` ## Stripping Quoted Threads Before analyzing email content for safety, strip quoted reply threads. Old instructions buried in `>` quoted sections or `On [date], [person] wrote:` blocks could contain unintended directives hidden in legitimate-looking reply chains. ```typescript function stripQuotedContent(text: string): string { return text // Remove lines starting with > .split('\n') .filter(line => !line.trim().startsWith('>')) .join('\n') // Remove "On ... wrote:" blocks .replace(/On .+wrote:[\s\S]*$/gm, '') // Remove "From: ... Sent: ..." forwarded headers .replace(/^From:.+\nSent:.+\nTo:.+\nSubject:.+$/gm, ''); } ``` This is critical for Level 3+ security. Even emails from trusted senders can contain quoted sections with malicious content. ## Troubleshooting ### "Cannot read properties of undefined (reading 'verify')" **Cause:** Resend SDK version too old — `resend.webhooks.verify()` was added in recent versions. **Fix:** Update to the latest SDK: ```bash npm install resend@latest ``` Or use the Svix fallback (see [webhook-setup.md](webhook-setup.md)). ### "Cannot read properties of undefined (reading 'get')" **Cause:** Resend SDK version too old — `emails.receiving.get()` requires a recent SDK. **Fix:** ```bash npm install resend@latest # Verify version: npm list resend ``` ### Webhook returns 400 errors **Possible causes:** 1. **Wrong signing secret** — The signing secret is returned when you create the webhook via the API (`data.signing_secret`). If you've lost it, delete and recreate the webhook to get a new one. 2. **Body parsing issue** — You must use the raw body for verification. Use `express.raw({ type: 'application/json' })` on the webhook route, not `express.json()`. 3. **SDK version too old** — Update to `resend@latest`. ### ngrok connection refused / tunnel died **Cause:** Free ngrok tunnels time out and change URLs on restart. **Fix:** Restart ngrok, then delete and recreate the webhook via the API with the new tunnel URL. **Better:** Use Tailscale Funnel or deploy to production. ### Email received but no webhook fires 1. Check the webhook is "Active" in Resend dashboard → Webhooks 2. Check the endpoint URL is correct (including the path, e.g., `/webhook`) 3. Check the tunnel is running: `curl https://<your-tunnel-url>` 4. Check the "Recent Deliveries" section on your webhook for status codes ### Security check rejecting all emails 1. Check the sender address is in your `ALLOWED_SENDERS` list 2. Check for case mismatch — the comparison should be case-insensitive 3. Debug by logging: `console.log('Sender:', event.data.from.toLowerCase())` ### Agent doesn't auto-respond to emails **This is expected behavior.** The webhook delivers a notification to the user, who then instructs the agent how to respond. This is the safest approach — the user reviews each email before the agent acts on it. # Security Levels — Detailed Implementation This reference contains full im