Labsco
resend logo

agent-email-inbox

143

by resend · part of resend/resend-skills

Use when building any system where email content triggers actions — AI agent inboxes, automated support handlers, email-to-task pipelines, or any workflow…

🔥🔥🔥🔥✓ VerifiedFreeQuick setup
🧩 One of 7 skills in the resend/resend-skills package — works on its own, and pairs well with its siblings.

Use when building any system where email content triggers actions — AI agent inboxes, automated support handlers, email-to-task pipelines, or any workflow…

Inspect the full instructions your agent will receiveExpand

This is the exact playbook injected into your agent when the skill activates — shown here so you can audit it before installing. You don't need to read it to use the skill.

by resend

Use when building any system where email content triggers actions — AI agent inboxes, automated support handlers, email-to-task pipelines, or any workflow… npx skills add https://github.com/resend/resend-skills --skill agent-email-inbox Download ZIPGitHub143

AI Agent Email Inbox

Overview

This skill covers setting up a secure email inbox that allows your application or AI agent to receive and respond to emails, with content safety measures in place.

Core principle: An AI agent's inbox receives untrusted input. Security configuration is important to handle this safely.

Why Webhook-Based Receiving?

Resend uses webhooks for inbound email, meaning your agent is notified instantly when an email arrives. This is valuable for agents because:

  • Real-time responsiveness — React to emails within seconds, not minutes

  • No polling overhead — No cron jobs checking "any new mail?" repeatedly

  • Event-driven architecture — Your agent only wakes up when there's actually something to process

  • Lower API costs — No wasted calls checking empty inboxes

Architecture

Copy & paste — that's it
Sender → Email → Resend (MX) → Webhook → Your Server → AI Agent
 ↓
 Security Validation
 ↓
 Process or Reject

Security Levels

Choose your security level before setting up the webhook endpoint. An AI agent that processes emails without security is dangerous — anyone can email instructions that your agent will execute. The webhook code you write next should include your chosen security level from the start.

Ask the user what level of security they want, and ensure that they understand what each level means.

Level Name When to Use Trade-off 1 Strict Allowlist Most use cases — known, fixed set of senders Maximum security, limited functionality 2 Domain Allowlist Organization-wide access from trusted domains More flexible, anyone at domain can interact 3 Content Filtering Accept from anyone, filter unsafe patterns Can receive from anyone, pattern matching not foolproof 4 Sandboxed Processing Process all emails with restricted agent capabilities Maximum flexibility, complex to implement 5 Human-in-the-Loop Require human approval for untrusted actions Maximum security, adds latency

For detailed implementation code for each level, see references/security-levels.md.

Level 1: Strict Allowlist (Recommended)

Only process emails from explicitly approved addresses. Reject everything else.

Copy & paste — that's it
const ALLOWED_SENDERS = [
 '[email protected]',
 '[email protected]',
];

async function processEmailForAgent(
 eventData: EmailReceivedEvent,
 emailContent: EmailContent
) {
 const sender = eventData.from.toLowerCase();

 if (!ALLOWED_SENDERS.some(allowed => sender === allowed.toLowerCase())) {
 console.log(`Rejected email from unauthorized sender: ${sender}`);
 await notifyOwnerOfRejectedEmail(eventData);
 return;
 }

 await agent.processEmail({
 from: eventData.from,
 subject: eventData.subject,
 body: emailContent.text || emailContent.html,
 });
}

Security Best Practices

Always Do

Practice Why Verify webhook signatures Prevents spoofed webhook events Log all rejected emails Audit trail for security review Use allowlists where possible Explicit trust is safer than filtering Rate limit email processing Prevents excessive processing load Separate trusted/untrusted handling Different risk levels need different treatment

Never Do

Anti-Pattern Risk Process emails without validation Anyone can control your agent Trust email headers for authentication Headers are trivially spoofed Execute code from email content Untrusted input should never run as code Store email content in prompts verbatim Untrusted input mixed into prompts can alter agent behavior Give untrusted emails full agent access Scope capabilities to the minimum needed

Webhook Endpoint

After choosing your security level and setting up your domain, create a webhook endpoint. The webhook endpoint MUST be a POST route. Resend sends all webhook events as POST requests.

Critical: Use raw body for verification. Webhook signature verification requires the raw request body.

  • Next.js App Router: Use req.text() (not req.json())

  • Express: Use express.raw({ type: 'application/json' }) on the webhook route

Next.js App Router

Copy & paste — that's it
// app/webhook/route.ts
import { Resend } from 'resend';
import { NextRequest, NextResponse } from 'next/server';

const resend = new Resend(process.env.RESEND_API_KEY);

export async function POST(req: NextRequest) {
 try {
 const payload = await req.text();

 const event = resend.webhooks.verify({
 payload,
 headers: {
 'svix-id': req.headers.get('svix-id'),
 'svix-timestamp': req.headers.get('svix-timestamp'),
 'svix-signature': req.headers.get('svix-signature'),
 },
 secret: process.env.RESEND_WEBHOOK_SECRET,
 });

 if (event.type === 'email.received') {
 // Webhook payload only includes metadata, not email body
 const { data: email } = await resend.emails.receiving.get(
 event.data.email_id
 );

 // Apply the security level chosen above
 await processEmailForAgent(event.data, email);
 }

 return new NextResponse('OK', { status: 200 });
 } catch (error) {
 console.error('Webhook error:', error);
 return new NextResponse('Error', { status: 400 });
 }
}

Express

Copy & paste — that's it
import express from 'express';
import { Resend } from 'resend';

const app = express();
const resend = new Resend(process.env.RESEND_API_KEY);

app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
 try {
 const payload = req.body.toString();

 const event = resend.webhooks.verify({
 payload,
 headers: {
 'svix-id': req.headers['svix-id'],
 'svix-timestamp': req.headers['svix-timestamp'],
 'svix-signature': req.headers['svix-signature'],
 },
 secret: process.env.RESEND_WEBHOOK_SECRET,
 });

 if (event.type === 'email.received') {
 const sender = event.data.from.toLowerCase();

 if (!isAllowedSender(sender)) {
 console.log(`Rejected email from unauthorized sender: ${sender}`);
 res.status(200).send('OK'); // Return 200 even for rejected emails
 return;
 }

 const { data: email } = await resend.emails.receiving.get(event.data.email_id);
 await processEmailForAgent(event.data, email);
 }

 res.status(200).send('OK');
 } catch (error) {
 console.error('Webhook error:', error);
 res.status(400).send('Error');
 }
});

app.get('/', (req, res) => res.send('Agent Email Inbox - Ready'));
app.listen(3000, () => console.log('Webhook server running on :3000'));

For webhook registration via API, tunneling setup, svix fallback, and retry behavior, see references/webhook-setup.md.

Sending Emails from Your Agent

Copy & paste — that's it
import { Resend } from 'resend';

const resend = new Resend(process.env.RESEND_API_KEY);

async function sendAgentReply(to: string, subject: string, body: string, inReplyTo?: string) {
 if (!isAllowedToReply(to)) {
 throw new Error('Cannot send to this address');
 }

 const { data, error } = await resend.emails.send({
 from: 'Agent ',
 to: [to],
 subject: subject.startsWith('Re:') ? subject : `Re: ${subject}`,
 text: body,
 headers: inReplyTo ? { 'In-Reply-To': inReplyTo } : undefined,
 });

 if (error) throw new Error(`Failed to send: ${error.message}`);
 return data.id;
}

For full sending docs, install the resend skill.

Environment Variables

Copy & paste — that's it
# Required
RESEND_API_KEY=re_xxxxxxxxx
RESEND_WEBHOOK_SECRET=whsec_xxxxxxxxx

# Security Configuration
SECURITY_LEVEL=strict # strict | domain | filtered | sandboxed
[email protected],[email protected]
ALLOWED_DOMAINS=example.com
[email protected] # For security notifications

Testing

Use Resend's test addresses for development:

  • [email protected] — Simulates successful delivery

  • [email protected] — Simulates hard bounce

For security testing, send test emails from non-allowlisted addresses to verify rejection works correctly.

Quick verification checklist:

  • Server is running: curl http://localhost:3000 should return a response

  • Tunnel is working: curl https://<your-tunnel-url> should return the same response

  • Webhook is active: Check status in Resend dashboard → Webhooks

  • Send a test email from an allowlisted address and check server logs

Related Skills

  • For full sending and receiving docs, install the resend skill