Best for
- Use when receiving emails with Resend - setting up inbound domains, processing email.
MoizIbnYousaf/marketing-cli/skills/resend-inbound/SKILL.md
Use when receiving emails with Resend - setting up inbound domains, processing email.received webhooks, retrieving email content/attachments, or forwarding received emails. Triggers on "inbound email", "receive email", "email webhook", "Resend inbound", "process incoming email", "email forwarding", "email.received", "MX records for email".
Decision brief
received webhooks, retrieving email content/attachments, or forwarding received emails. Triggers on "inbound email", "receive email", "email webhook", "Resend inbound", "process incoming email", "email forwarding", "email.
Compatibility matrix
| Platform | Status | Evidence | What to check |
|---|---|---|---|
| Codex | Not declared | No explicit evidence | Portability before use |
| Claude Code | Not declared | No explicit evidence | Portability before use |
| Cursor | Not declared | No explicit evidence | Portability before use |
| Gemini CLI | Not declared | No explicit evidence | Portability before use |
Installation
The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.
npx skills add https://github.com/MoizIbnYousaf/marketing-cli --skill "skills/resend-inbound"Inspect the Agent Skill "resend-inbound" from https://github.com/MoizIbnYousaf/marketing-cli/blob/f12fbcbe4929584697b309b9096c9427b0cfce8e/skills/resend-inbound/SKILL.md at commit f12fbcbe4929584697b309b9096c9427b0cfce8e. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.
Workflow
1. Configure receiving domain — Use Resend's .resend.app domain or add MX record for custom domain 2. Set up webhook — Subscribe to email.received event 3. Retrieve content — Call Receiving API for body, Attachments API for files
Use your auto-generated address: @.resend.app
Dashboard → Webhooks → Add Webhook → Select email.received
1. Determine if the user needs a Resend-managed domain or custom domain. 2. Walk through domain setup and webhook configuration. 3. Implement webhook handler with signature verification. 4. Add content retrieval logic (body + attachments as needed). 5. Add routing logic if multi…
Use your auto-generated address: @.resend.app
Permission review
The documentation includes network, browsing, or remote request actions.
# Use https://abc123.ngrok.io/api/webhook as endpointThe documentation includes sending, uploading, or posting data to a remote service.
### Webhook Payload StructureThe documentation includes network, browsing, or remote request actions.
### Webhook Payload StructureThe documentation includes sending, uploading, or posting data to a remote service.
| Expecting body in webhook payload | Webhook has metadata only — call `resend.emails.receiving.get()` for body |Evidence record
| Signal | Value | Evidence type | Meaning |
|---|---|---|---|
| Quality score | 78/100 | Computed | Documentation, specificity, maintenance, and trust rules |
| Repository stars | 27 | Source | Repository attention, not individual Skill quality |
| Compatibility | 0 platforms | Source | Declared in the catalog source record |
| Usage guide | automated source guide | Editorial | Generated or reviewed according to the visible evidence level |
Pinned source
Resend processes incoming emails for your domain and sends webhook events to your endpoint. Webhooks contain metadata only — you must call separate APIs to retrieve email body and attachments.
This skill is context-independent — it does not use brand/ files and works identically in any project.
Output: A webhook handler file with signature verification, content retrieval, and routing logic.
.resend.app domain or add MX record for custom domainemail.received eventUse your auto-generated address: <anything>@<your-id>.resend.app
No DNS configuration needed. Find your address in Dashboard → Emails → Receiving → "Receiving address".
Add MX record to receive at <anything>@yourdomain.com.
| Setting | Value |
|---|---|
| Type | MX |
| Host | Your domain or subdomain |
| Value | Provided in Resend dashboard |
| Priority | 10 (lowest number wins a conflict, but typically only multiples of 10 are used) |
Critical: Your MX record must have the lowest priority value, or emails won't route to Resend.
If you already have MX records (e.g., Google Workspace, Microsoft 365):
| Approach | Result |
|---|---|
| Use subdomain (recommended) | support.acme.com → Resend, acme.com → existing provider |
| Use root domain | All email routes to Resend (breaks existing email) |
# Example: receive at support.acme.com without affecting acme.com
support.acme.com. MX 10 <resend-mx-value>
If you set up Resend to receive email on a root domain, all traffic will be routed to Resend, not to any other mailbox. It's crucial, then, to use a subdomain with inbound emails.
email.receivedDashboard → Webhooks → Add Webhook → Select email.received
For local development, use tunneling (ngrok, VS Code Port Forwarding):
ngrok http 3000
# Use https://abc123.ngrok.io/api/webhook as endpoint
Important: Payload contains metadata only, not email body or attachment content.
{
"type": "email.received",
"created_at": "2024-02-22T23:41:12.126Z",
"data": {
"email_id": "a1b2c3d4-...",
"from": "sender@example.com",
"to": ["support@acme.com"],
"cc": [],
"bcc": [],
"subject": "Question about my order",
"attachments": [
{
"id": "att_abc123",
"filename": "receipt.pdf",
"content_type": "application/pdf"
}
]
}
}
Always verify signatures to prevent spoofed events:
import { Resend } from 'resend';
const resend = new Resend(process.env.RESEND_API_KEY);
export async function POST(req: Request) {
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') {
// Process the email
}
return new Response('OK', { status: 200 });
}
Webhooks exclude email body and headers. Call the Receiving API to get them:
if (event.type === 'email.received') {
const { data: email } = await resend.emails.receiving.get(
event.data.email_id
);
console.log(email.html); // HTML body
console.log(email.text); // Plain text body
console.log(email.headers); // Email headers
}
Why this design? Serverless environments have request body size limits. Separating content retrieval supports large emails and attachments.
const { data: attachments } = await resend.emails.receiving.attachments.list({
emailId: event.data.email_id,
});
for (const attachment of attachments) {
console.log(attachment.filename);
console.log(attachment.download_url); // Valid for 1 hour
console.log(attachment.expires_at);
}
const response = await fetch(attachment.download_url);
const buffer = await response.arrayBuffer();
// Save to storage, process, etc.
await saveToStorage(attachment.filename, buffer);
Important: download_url expires after 1 hour. Call the API again for a fresh URL if needed.
Complete workflow to receive and forward an email with attachments:
import { Resend } from 'resend';
const resend = new Resend(process.env.RESEND_API_KEY);
export async function POST(req: Request) {
const payload = await req.text();
const event = resend.webhooks.verify({ /* ... */ });
if (event.type === 'email.received') {
// 1. Get email content
const { data: email } = await resend.emails.receiving.get(
event.data.email_id
);
// 2. Get attachments (if any)
const { data: attachmentList } = await resend.emails.receiving.attachments.list({
emailId: event.data.email_id,
});
// 3. Download and encode attachments
const attachments = await Promise.all(
attachmentList.map(async (att) => {
const res = await fetch(att.download_url);
const buffer = Buffer.from(await res.arrayBuffer());
return {
filename: att.filename,
content: buffer.toString('base64'),
};
})
);
// 4. Forward the email
await resend.emails.send({
from: 'Support System <system@acme.com>',
to: ['team@acme.com'],
subject: `Fwd: ${email.subject}`,
html: email.html,
text: email.text,
attachments,
});
}
return new Response('OK', { status: 200 });
}
All emails to your domain arrive at the same webhook. Route based on the to field:
if (event.type === 'email.received') {
const recipient = event.data.to[0];
if (recipient.includes('support@')) {
await handleSupportEmail(event.data);
} else if (recipient.includes('billing@')) {
await handleBillingEmail(event.data);
} else {
await handleUnknownEmail(event.data);
}
}
| Failure | Action |
|---|---|
| Webhook signature verification fails | Return 400, log the attempt. Never process unverified webhooks. |
resend.emails.receiving.get() returns error | Log the email_id, return 200 to acknowledge webhook, queue for retry via your own retry logic. |
Attachment download_url expired | Call resend.emails.receiving.attachments.list() again for a fresh URL. |
| Attachment download times out | Retry with exponential backoff (max 3 attempts). Log failure if all retries exhaust. |
| Malformed email (missing from/subject) | Log and skip gracefully. Return 200 to prevent Resend retries on bad data. |
| Mistake | Fix |
|---|---|
| Expecting body in webhook payload | Webhook has metadata only — call resend.emails.receiving.get() for body |
| MX record not lowest priority | Ensure Resend's MX has lowest number (highest priority) |
| Adding MX to root domain with existing email | Use subdomain to avoid breaking existing email service |
| Using expired download_url | URLs expire after 1 hour — call attachments API again for fresh URL |
| Not verifying webhook signatures | Always verify — attackers can send fake events |
| Forgetting to return 200 OK | Resend retries on non-200 responses |
Resend stores received emails even if:
View all received emails in Dashboard → Emails → Receiving tab.
Alternatives
event4u-app/agent-config
Grounded design brief from the adopted corpus — style, WCAG-checked color tokens, typography, layout pattern, anti-patterns. Use on ui-design-brief or any which-style/palette/font/chart decision.
wshobson/agents
Brand-first landing page designer — runs a brand-identity interview (colors, typography, shape language), then generates and iterates on a polished landing page via Stitch with deployment-ready HTML. Use when the user asks to create, design, or build a landing page, homepage, or marketing page and has no established visual direction. Skip when they have a design mockup, need a dashboard or app UI, are working at component level, building a multi-page app, or restyling with known design tokens —
MoizIbnYousaf/marketing-cli
Brand-grounded Remotion video pipeline. Take a brief, ground it in `brand/` (voice, audience, positioning, creative-kit), write a beat sheet, scaffold a fresh Remotion project, generate any required assets, compose with frame-driven animations, and bake a final MP4. Use this skill whenever the user wants to build a NEW Remotion video from scratch — product films, motion graphics, code-driven shaders, CRT/glitch effects, HTML-in-canvas demos, React video. Triggers on requests like "make a remotio
MoizIbnYousaf/marketing-cli
Generate images using the brand's visual identity and Gemini API. Reads brand/creative-kit.md for visual style, crafts narrative prompts, and produces images via Nano Banana Pro (gemini-3-pro-image-preview). Supports on-brand and freestyle modes. Use when the user needs a blog header, social graphic, product shot, hero image, banner, thumbnail, or any generated image. Also use proactively when building content that would benefit from visuals. Triggers on "generate image", "create image", "make m