{"id":"2a99975f-7ae7-465f-b5fe-822e0a1b5433","shortId":"Mgmmz9","kind":"skill","title":"resend-inbound","tagline":"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\", ","description":"# Receive Emails with Resend\n\n## Overview\n\nResend 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.\n\nThis skill is context-independent — it does not use `brand/` files and works identically in any project.\n\n## On Activation\n\n1. Determine if the user needs a Resend-managed domain or custom domain.\n2. Walk through domain setup and webhook configuration.\n3. Implement webhook handler with signature verification.\n4. Add content retrieval logic (body + attachments as needed).\n5. Add routing logic if multiple recipients are expected.\n\n**Output:** A webhook handler file with signature verification, content retrieval, and routing logic.\n\n## Quick Start\n\n1. **Configure receiving domain** — Use Resend's `.resend.app` domain or add MX record for custom domain\n2. **Set up webhook** — Subscribe to `email.received` event\n3. **Retrieve content** — Call Receiving API for body, Attachments API for files\n\n## Domain Setup\n\n### Option 1: Resend-Managed Domain (Fastest)\n\nUse your auto-generated address: `<anything>@<your-id>.resend.app`\n\nNo DNS configuration needed. Find your address in Dashboard → Emails → Receiving → \"Receiving address\".\n\n### Option 2: Custom Domain\n\nAdd MX record to receive at `<anything>@yourdomain.com`.\n\n| Setting | Value |\n|---------|-------|\n| **Type** | MX |\n| **Host** | Your domain or subdomain |\n| **Value** | Provided in Resend dashboard |\n| **Priority** | 10 (**lowest number** wins a conflict, but typically only multiples of 10 are used) |\n\n**Critical:** Your MX record must have the lowest priority value, or emails won't route to Resend.\n\n### Subdomain Recommendation\n\nIf you already have MX records (e.g., Google Workspace, Microsoft 365):\n\n| Approach | Result |\n|----------|--------|\n| **Use subdomain** (recommended) | `support.acme.com` → Resend, `acme.com` → existing provider |\n| **Use root domain** | All email routes to Resend (breaks existing email) |\n\n```\n# Example: receive at support.acme.com without affecting acme.com\nsupport.acme.com.  MX  10  <resend-mx-value>\n```\n\nIf 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.\n\n## Webhook Setup\n\n### Subscribe to `email.received`\n\nDashboard → Webhooks → Add Webhook → Select `email.received`\n\nFor local development, use tunneling (ngrok, VS Code Port Forwarding):\n```bash\nngrok http 3000\n# Use https://abc123.ngrok.io/api/webhook as endpoint\n```\n\n### Webhook Payload Structure\n\n**Important:** Payload contains metadata only, not email body or attachment content.\n\n```json\n{\n  \"type\": \"email.received\",\n  \"created_at\": \"2024-02-22T23:41:12.126Z\",\n  \"data\": {\n    \"email_id\": \"a1b2c3d4-...\",\n    \"from\": \"sender@example.com\",\n    \"to\": [\"support@acme.com\"],\n    \"cc\": [],\n    \"bcc\": [],\n    \"subject\": \"Question about my order\",\n    \"attachments\": [\n      {\n        \"id\": \"att_abc123\",\n        \"filename\": \"receipt.pdf\",\n        \"content_type\": \"application/pdf\"\n      }\n    ]\n  }\n}\n```\n\n### Verify Webhook Signatures\n\nAlways verify signatures to prevent spoofed events:\n\n```typescript\nimport { Resend } from 'resend';\n\nconst resend = new Resend(process.env.RESEND_API_KEY);\n\nexport async function POST(req: Request) {\n  const payload = await req.text();\n\n  const event = resend.webhooks.verify({\n    payload,\n    headers: {\n      'svix-id': req.headers.get('svix-id'),\n      'svix-timestamp': req.headers.get('svix-timestamp'),\n      'svix-signature': req.headers.get('svix-signature'),\n    },\n    secret: process.env.RESEND_WEBHOOK_SECRET,\n  });\n\n  if (event.type === 'email.received') {\n    // Process the email\n  }\n\n  return new Response('OK', { status: 200 });\n}\n```\n\n## Retrieving Email Content\n\nWebhooks exclude email body and headers. Call the Receiving API to get them:\n\n```typescript\nif (event.type === 'email.received') {\n  const { data: email } = await resend.emails.receiving.get(\n    event.data.email_id\n  );\n\n  console.log(email.html);    // HTML body\n  console.log(email.text);    // Plain text body\n  console.log(email.headers); // Email headers\n}\n```\n\n**Why this design?** Serverless environments have request body size limits. Separating content retrieval supports large emails and attachments.\n\n## Handling Attachments\n\n### Get Attachment Metadata and Download URLs\n\n```typescript\nconst { data: attachments } = await resend.emails.receiving.attachments.list({\n  emailId: event.data.email_id,\n});\n\nfor (const attachment of attachments) {\n  console.log(attachment.filename);\n  console.log(attachment.download_url);  // Valid for 1 hour\n  console.log(attachment.expires_at);\n}\n```\n\n### Download Attachment Content\n\n```typescript\nconst response = await fetch(attachment.download_url);\nconst buffer = await response.arrayBuffer();\n\n// Save to storage, process, etc.\nawait saveToStorage(attachment.filename, buffer);\n```\n\n**Important:** `download_url` expires after 1 hour. Call the API again for a fresh URL if needed.\n\n## Forwarding Emails\n\nComplete workflow to receive and forward an email with attachments:\n\n```typescript\nimport { Resend } from 'resend';\n\nconst resend = new Resend(process.env.RESEND_API_KEY);\n\nexport async function POST(req: Request) {\n  const payload = await req.text();\n  const event = resend.webhooks.verify({ /* ... */ });\n\n  if (event.type === 'email.received') {\n    // 1. Get email content\n    const { data: email } = await resend.emails.receiving.get(\n      event.data.email_id\n    );\n\n    // 2. Get attachments (if any)\n    const { data: attachmentList } = await resend.emails.receiving.attachments.list({\n      emailId: event.data.email_id,\n    });\n\n    // 3. Download and encode attachments\n    const attachments = await Promise.all(\n      attachmentList.map(async (att) => {\n        const res = await fetch(att.download_url);\n        const buffer = Buffer.from(await res.arrayBuffer());\n        return {\n          filename: att.filename,\n          content: buffer.toString('base64'),\n        };\n      })\n    );\n\n    // 4. Forward the email\n    await resend.emails.send({\n      from: 'Support System <system@acme.com>',\n      to: ['team@acme.com'],\n      subject: `Fwd: ${email.subject}`,\n      html: email.html,\n      text: email.text,\n      attachments,\n    });\n  }\n\n  return new Response('OK', { status: 200 });\n}\n```\n\n## Routing by Recipient\n\nAll emails to your domain arrive at the same webhook. Route based on the `to` field:\n\n```typescript\nif (event.type === 'email.received') {\n  const recipient = event.data.to[0];\n\n  if (recipient.includes('support@')) {\n    await handleSupportEmail(event.data);\n  } else if (recipient.includes('billing@')) {\n    await handleBillingEmail(event.data);\n  } else {\n    await handleUnknownEmail(event.data);\n  }\n}\n```\n\n## Error Handling\n\n| Failure | Action |\n|---------|--------|\n| Webhook signature verification fails | Return 400, log the attempt. Never process unverified webhooks. |\n| `resend.emails.receiving.get()` returns error | Log the email_id, return 200 to acknowledge webhook, queue for retry via your own retry logic. |\n| Attachment `download_url` expired | Call `resend.emails.receiving.attachments.list()` again for a fresh URL. |\n| Attachment download times out | Retry with exponential backoff (max 3 attempts). Log failure if all retries exhaust. |\n| Malformed email (missing from/subject) | Log and skip gracefully. Return 200 to prevent Resend retries on bad data. |\n\n## Anti-Patterns\n\n| Mistake | Fix |\n|---------|-----|\n| Expecting body in webhook payload | Webhook has metadata only — call `resend.emails.receiving.get()` for body |\n| MX record not lowest priority | Ensure Resend's MX has lowest number (highest priority) |\n| Adding MX to root domain with existing email | Use subdomain to avoid breaking existing email service |\n| Using expired download_url | URLs expire after 1 hour — call attachments API again for fresh URL |\n| Not verifying webhook signatures | Always verify — attackers can send fake events |\n| Forgetting to return 200 OK | Resend retries on non-200 responses |\n\n## Storage Note\n\nResend stores received emails even if:\n- Webhook isn't configured yet\n- Webhook endpoint is down\n\nView all received emails in Dashboard → Emails → Receiving tab.","tags":["resend","inbound","marketing","cli","moizibnyousaf","agent-skills","ai-agents","ai-marketing","brand-memory","brand-voice","claude-code","claude-code-skills"],"capabilities":["skill","source-moizibnyousaf","skill-resend-inbound","topic-agent-skills","topic-ai-agents","topic-ai-marketing","topic-brand-memory","topic-brand-voice","topic-claude-code","topic-claude-code-skills","topic-cli","topic-cmo","topic-marketing-automation","topic-marketing-cli","topic-seo"],"categories":["marketing-cli"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/MoizIbnYousaf/marketing-cli/resend-inbound","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add MoizIbnYousaf/marketing-cli","source_repo":"https://github.com/MoizIbnYousaf/marketing-cli","install_from":"skills.sh"}},"qualityScore":"0.459","qualityRationale":"deterministic score 0.46 from registry signals: · indexed on github topic:agent-skills · 18 github stars · SKILL.md body (8,337 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T19:05:51.967Z","embedding":null,"createdAt":"2026-05-04T19:06:15.682Z","updatedAt":"2026-05-18T19:05:51.967Z","lastSeenAt":"2026-05-18T19:05:51.967Z","tsv":"'-02':405 '-200':970 '-22':406 '/api/webhook':382 '0':786 '1':91,153,192,596,629,681,941 '10':244,255,318 '12.126':409 '2':105,169,219,692 '200':508,759,829,878,964 '2024':404 '3':113,177,705,861 '3000':378 '365':287 '4':120,734 '400':813 '41':408 '5':129 'a1b2c3d4':414 'abc123':429 'abc123.ngrok.io':381 'abc123.ngrok.io/api/webhook':380 'acknowledg':831 'acme.com':295,315 'action':807 'activ':90 'ad':918 'add':121,130,163,222,361 'address':203,211,217 'affect':314 'alreadi':279 'alway':438,954 'anti':887 'anti-pattern':886 'api':64,182,186,455,521,633,663,945 'application/pdf':434 'approach':288 'arriv':768 'async':458,666,715 'att':428,716 'att.download':721 'att.filename':730 'attach':70,126,185,397,426,566,568,570,578,586,588,602,652,694,709,711,753,841,852,944 'attachment.download':592,609 'attachment.expires':599 'attachment.filename':590,622 'attachmentlist':699 'attachmentlist.map':714 'attack':956 'attempt':816,862 'auto':201 'auto-gener':200 'avoid':929 'await':465,532,579,607,613,620,673,688,700,712,719,726,738,790,797,801 'backoff':859 'bad':884 'base':774 'base64':733 'bash':375 'bcc':420 'bill':796 'bodi':68,125,184,395,515,539,544,556,892,903 'brand':81 'break':306,930 'buffer':612,623,724 'buffer.from':725 'buffer.tostring':732 'call':62,180,518,631,845,900,943 'cc':419 'code':372 'complet':643 'configur':112,154,207,983 'conflict':249 'console.log':536,540,545,589,591,598 'const':450,463,467,529,576,585,605,611,658,671,675,685,697,710,717,723,783 'contain':57,390 'content':122,146,179,398,432,511,560,603,684,731 'content/attachments':19 'context':75 'context-independ':74 'creat':402 'critic':258 'crucial':345 'custom':103,167,220 'dashboard':213,242,359,994 'data':411,530,577,686,698,885 'design':551 'determin':92 'develop':367 'dns':206 'domain':13,48,101,104,108,156,161,168,189,196,221,235,300,330,767,922 'download':573,601,625,706,842,853,936 'e.g':283 'els':793,800 'email':7,18,23,27,29,30,36,38,45,67,214,269,302,308,326,353,394,412,502,510,514,531,547,564,642,650,683,687,737,764,826,870,925,932,977,992,995 'email.headers':546 'email.html':537,750 'email.received':15,175,358,364,401,499,528,680,782 'email.subject':748 'email.text':541,752 'emailid':581,702 'encod':708 'endpoint':55,384,986 'ensur':909 'environ':553 'error':804,823 'etc':619 'even':978 'event':52,176,444,468,676,960 'event.data':792,799,803 'event.data.email':534,582,690,703 'event.data.to':785 'event.type':498,527,679,781 'exampl':309 'exclud':513 'exhaust':868 'exist':296,307,924,931 'expect':137,891 'expir':627,844,935,939 'exponenti':858 'export':457,665 'fail':811 'failur':806,864 'fake':959 'fastest':197 'fetch':608,720 'field':778 'file':82,142,188 'filenam':430,729 'find':209 'fix':890 'forget':961 'forward':21,374,641,648,735 'fresh':637,850,948 'from/subject':872 'function':459,667 'fwd':747 'generat':202 'get':523,569,682,693 'googl':284 'grace':876 'handl':567,805 'handlebillingemail':798 'handler':116,141 'handlesupportemail':791 'handleunknownemail':802 'header':471,517,548 'highest':916 'host':233 'hour':597,630,942 'html':538,749 'http':377 'id':413,427,474,478,535,583,691,704,827 'ident':85 'implement':114 'import':388,446,624,654 'inbound':3,12,26,33,352 'incom':35,44 'independ':76 'isn':981 'json':399 'key':456,664 'larg':563 'limit':558 'local':366 'log':814,824,863,873 'logic':124,132,150,840 'lowest':245,265,907,914 'mailbox':342 'malform':869 'manag':100,195 'max':860 'metadata':58,391,571,898 'microsoft':286 'miss':871 'mistak':889 'multipl':134,253 'must':61,262 'mx':164,223,232,260,281,317,904,912,919 'need':96,128,208,640 'never':817 'new':452,504,660,755 'ngrok':370,376 'non':969 'note':973 'number':246,915 'ok':506,757,965 'option':191,218 'order':425 'output':138 'overview':41 'pattern':888 'payload':386,389,464,470,672,895 'plain':542 'port':373 'post':460,668 'prevent':442,880 'prioriti':243,266,908,917 'process':14,34,43,500,618,818 'process.env.resend':454,494,662 'project':88 'promise.all':713 'provid':239,297 'question':422 'queue':833 'quick':151 'receipt.pdf':431 'receiv':6,22,28,37,155,181,215,216,226,310,325,520,646,976,991,996 'recipi':135,762,784 'recipient.includes':788,795 'recommend':276,292 'record':165,224,261,282,905 'req':461,669 'req.headers.get':475,482,489 'req.text':466,674 'request':462,555,670 'res':718 'res.arraybuffer':727 'resend':2,9,32,40,42,99,158,194,241,274,294,305,323,337,447,449,451,453,655,657,659,661,881,910,966,974 'resend-inbound':1 'resend-manag':98,193 'resend.app':160,204 'resend.emails.receiving.attachments.list':580,701,846 'resend.emails.receiving.get':533,689,821,901 'resend.emails.send':739 'resend.webhooks.verify':469,677 'respons':505,606,756,971 'response.arraybuffer':614 'result':289 'retri':835,839,856,867,882,967 'retriev':17,66,123,147,178,509,561 'return':503,728,754,812,822,828,877,963 'root':299,329,921 'rout':131,149,272,303,335,760,773 'save':615 'savetostorag':621 'secret':493,496 'select':363 'send':50,958 'sender@example.com':416 'separ':63,559 'serverless':552 'servic':933 'set':10,170,229,321 'setup':109,190,355 'signatur':118,144,437,440,488,492,809,953 'size':557 'skill':72 'skill-resend-inbound' 'skip':875 'source-moizibnyousaf' 'spoof':443 'start':152 'status':507,758 'storag':617,972 'store':975 'structur':387 'subdomain':237,275,291,350,927 'subject':421,746 'subscrib':173,356 'support':562,741,789 'support.acme.com':293,312,316 'support@acme.com':418 'svix':473,477,480,484,487,491 'svix-id':472,476 'svix-signatur':486,490 'svix-timestamp':479,483 'system':742 'system@acme.com':743 't23':407 'tab':997 'team@acme.com':745 'text':543,751 'time':854 'timestamp':481,485 'topic-agent-skills' 'topic-ai-agents' 'topic-ai-marketing' 'topic-brand-memory' 'topic-brand-voice' 'topic-claude-code' 'topic-claude-code-skills' 'topic-cli' 'topic-cmo' 'topic-marketing-automation' 'topic-marketing-cli' 'topic-seo' 'traffic':332 'trigger':24 'tunnel':369 'type':231,400,433 'typescript':445,525,575,604,653,779 'typic':251 'unverifi':819 'url':574,593,610,626,638,722,843,851,937,938,949 'use':4,80,157,198,257,290,298,348,368,379,926,934 'user':95 'valid':594 'valu':230,238,267 'verif':119,145,810 'verifi':435,439,951,955 'via':836 'view':989 'vs':371 'walk':106 'webhook':16,31,51,56,111,115,140,172,354,360,362,385,436,495,512,772,808,820,832,894,896,952,980,985 'win':247 'without':313 'won':270 'work':84 'workflow':644 'workspac':285 'yet':984 'yourdomain.com':228 'z':410","prices":[{"id":"2fef125b-2d19-4d54-9911-987e0c5ca5d2","listingId":"2a99975f-7ae7-465f-b5fe-822e0a1b5433","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"MoizIbnYousaf","category":"marketing-cli","install_from":"skills.sh"},"createdAt":"2026-05-04T19:06:15.682Z"}],"sources":[{"listingId":"2a99975f-7ae7-465f-b5fe-822e0a1b5433","source":"github","sourceId":"MoizIbnYousaf/marketing-cli/resend-inbound","sourceUrl":"https://github.com/MoizIbnYousaf/marketing-cli/tree/main/skills/resend-inbound","isPrimary":false,"firstSeenAt":"2026-05-04T19:06:15.682Z","lastSeenAt":"2026-05-18T19:05:51.967Z"}],"details":{"listingId":"2a99975f-7ae7-465f-b5fe-822e0a1b5433","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"MoizIbnYousaf","slug":"resend-inbound","github":{"repo":"MoizIbnYousaf/marketing-cli","stars":18,"topics":["agent-skills","ai-agents","ai-marketing","brand-memory","brand-voice","claude-code","claude-code-skills","cli","cmo","marketing-automation","marketing-cli","seo","skills-sh","typescript"],"license":"mit","html_url":"https://github.com/MoizIbnYousaf/marketing-cli","pushed_at":"2026-05-13T21:06:05Z","description":"Agent-native marketing cli: 51 skills, 5 research agents, brand memory that compounds, plus a local Studio dashboard (beta). single agent-native cli, then /cmo in ur coding agent..","skill_md_sha":"10764bee58ffbb430f3ddbc1b23c7a30d561dfa2","skill_md_path":"skills/resend-inbound/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/MoizIbnYousaf/marketing-cli/tree/main/skills/resend-inbound"},"layout":"multi","source":"github","category":"marketing-cli","frontmatter":{"name":"resend-inbound","description":"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\"."},"skills_sh_url":"https://skills.sh/MoizIbnYousaf/marketing-cli/resend-inbound"},"updatedAt":"2026-05-18T19:05:51.967Z"}}