[{"data":1,"prerenderedAt":265},["ShallowReactive",2],{"blog-guides/resend":3},{"id":4,"title":5,"body":6,"category":243,"date":244,"dateModified":245,"description":246,"draft":247,"extension":248,"faq":249,"featured":247,"headerVariant":250,"image":249,"keywords":251,"meta":252,"navigation":253,"ogDescription":254,"ogTitle":249,"path":255,"readTime":256,"schemaOrg":257,"schemaType":258,"seo":259,"sitemap":260,"stem":261,"tags":262,"twitterCard":263,"__hash__":264},"blog/blog/guides/resend.md","Resend Email Security Guide for Vibe Coders",{"type":7,"value":8,"toc":231},"minimark",[9,16,21,24,28,39,49,55,59,62,68,72,78,82,88,92,95,101,105,111,116,150,172,176,193,200,219],[10,11,12],"tldr",{},[13,14,15],"p",{},"Resend makes sending emails easy, but email APIs can be abused. Keep your API key server-side only. Validate and sanitize all user input before including it in emails. Rate limit email sending per user. Don't let users control the \"from\" address. Verify domain ownership and set up SPF/DKIM/DMARC to prevent spoofing.",[17,18,20],"h2",{"id":19},"why-resend-security-matters-for-vibe-coding","Why Resend Security Matters for Vibe Coding",[13,22,23],{},"Resend provides a simple API for transactional emails. When AI tools generate email code, they often create working implementations but miss abuse prevention patterns. An exposed API key or unvalidated input can lead to spam, phishing, or account suspension.",[17,25,27],{"id":26},"api-key-management","API Key Management",[29,30,35],"pre",{"className":31,"code":33,"language":34},[32],"language-text","# .env.local (never commit)\nRESEND_API_KEY=re_xxxxxxxxxxxxx\n","text",[36,37,33],"code",{"__ignoreMap":38},"",[40,41,42],"danger-box",{},[13,43,44,48],{},[45,46,47],"strong",{},"Never Expose Your API Key:"," Your Resend API key can send emails from your verified domains. If exposed, attackers can send spam or phishing emails that appear to come from you, damaging your domain reputation and potentially getting you blacklisted.",[29,50,53],{"className":51,"code":52,"language":34},[32],"import { Resend } from 'resend';\n\n// Server-side only\nconst resend = new Resend(process.env.RESEND_API_KEY);\n\n// Verify key exists\nif (!process.env.RESEND_API_KEY) {\n  throw new Error('RESEND_API_KEY is not configured');\n}\n",[36,54,52],{"__ignoreMap":38},[17,56,58],{"id":57},"input-validation","Input Validation",[13,60,61],{},"Never trust user input in emails:",[29,63,66],{"className":64,"code":65,"language":34},[32],"import { z } from 'zod';\nimport { Resend } from 'resend';\n\nconst resend = new Resend(process.env.RESEND_API_KEY);\n\n// Validate email addresses\nconst ContactFormSchema = z.object({\n  email: z.string().email().max(254),\n  name: z.string().min(1).max(100),\n  message: z.string().min(1).max(5000),\n});\n\nexport async function POST(request: Request) {\n  const body = await request.json();\n  const result = ContactFormSchema.safeParse(body);\n\n  if (!result.success) {\n    return Response.json({ error: 'Invalid input' }, { status: 400 });\n  }\n\n  const { email, name, message } = result.data;\n\n  // Sanitize for HTML email\n  const sanitizedMessage = escapeHtml(message);\n  const sanitizedName = escapeHtml(name);\n\n  await resend.emails.send({\n    from: 'Contact Form \u003Cnoreply@yourdomain.com>', // Fixed sender\n    to: 'support@yourdomain.com', // Fixed recipient\n    replyTo: email, // User's email for reply\n    subject: `Contact from ${sanitizedName}`,\n    html: `\u003Cp>From: ${sanitizedName} (${email})\u003C/p>\u003Cp>${sanitizedMessage}\u003C/p>`,\n  });\n\n  return Response.json({ success: true });\n}\n\nfunction escapeHtml(text: string): string {\n  return text\n    .replace(/&/g, '&amp;')\n    .replace(/\u003C/g, '&lt;')\n    .replace(/>/g, '&gt;')\n    .replace(/\"/g, '&quot;')\n    .replace(/'/g, '&#039;');\n}\n",[36,67,65],{"__ignoreMap":38},[17,69,71],{"id":70},"preventing-email-injection","Preventing Email Injection",[29,73,76],{"className":74,"code":75,"language":34},[32],"// DANGEROUS: User controls \"to\" address\nawait resend.emails.send({\n  from: 'noreply@yourdomain.com',\n  to: userProvidedEmail, // Could send to anyone!\n  subject: 'Hello',\n  html: content,\n});\n\n// SAFE: Only send to authenticated user's verified email\nawait resend.emails.send({\n  from: 'noreply@yourdomain.com',\n  to: session.user.email, // From your auth system\n  subject: 'Your account update',\n  html: content,\n});\n\n// DANGEROUS: User controls headers\nawait resend.emails.send({\n  from: userProvidedFrom, // Spoofing!\n  to: 'admin@yourdomain.com',\n  subject: userProvidedSubject, // Could include newlines for header injection\n  html: content,\n});\n\n// SAFE: Fixed sender, sanitized subject\nawait resend.emails.send({\n  from: 'noreply@yourdomain.com',\n  to: 'admin@yourdomain.com',\n  subject: sanitizeSubject(userInput),\n  html: sanitizedContent,\n});\n\nfunction sanitizeSubject(subject: string): string {\n  // Remove newlines and limit length\n  return subject.replace(/[\\r\\n]/g, ' ').substring(0, 200);\n}\n",[36,77,75],{"__ignoreMap":38},[17,79,81],{"id":80},"rate-limiting-email-sending","Rate Limiting Email Sending",[29,83,86],{"className":84,"code":85,"language":34},[32],"import { Ratelimit } from '@upstash/ratelimit';\nimport { Redis } from '@upstash/redis';\n\nconst emailRatelimit = new Ratelimit({\n  redis: Redis.fromEnv(),\n  limiter: Ratelimit.slidingWindow(5, '1 h'), // 5 emails per hour\n});\n\nexport async function sendPasswordReset(userId: string, email: string) {\n  // Rate limit per user\n  const { success } = await emailRatelimit.limit(`email:${userId}`);\n\n  if (!success) {\n    throw new Error('Too many emails requested. Please try again later.');\n  }\n\n  // Also rate limit per email address to prevent enumeration\n  const { success: emailSuccess } = await emailRatelimit.limit(`email:${email}`);\n\n  if (!emailSuccess) {\n    // Don't reveal if email exists - just silently succeed\n    return { success: true };\n  }\n\n  await resend.emails.send({\n    from: 'noreply@yourdomain.com',\n    to: email,\n    subject: 'Password Reset',\n    html: generateResetEmail(userId),\n  });\n}\n",[36,87,85],{"__ignoreMap":38},[17,89,91],{"id":90},"domain-security","Domain Security",[13,93,94],{},"Configure these DNS records for your sending domain:",[29,96,99],{"className":97,"code":98,"language":34},[32],"# SPF - Specify who can send email for your domain\nTXT  @  \"v=spf1 include:resend.com ~all\"\n\n# DKIM - Resend provides this record\n# Add the DKIM record from your Resend dashboard\n\n# DMARC - Policy for handling failed authentication\nTXT  _dmarc  \"v=DMARC1; p=quarantine; rua=mailto:dmarc@yourdomain.com\"\n",[36,100,98],{"__ignoreMap":38},[17,102,104],{"id":103},"webhook-security","Webhook Security",[29,106,109],{"className":107,"code":108,"language":34},[32],"import crypto from 'crypto';\n\nexport async function POST(request: Request) {\n  const body = await request.text();\n  const signature = request.headers.get('svix-signature');\n  const timestamp = request.headers.get('svix-timestamp');\n\n  if (!signature || !timestamp) {\n    return Response.json({ error: 'Missing headers' }, { status: 401 });\n  }\n\n  // Verify webhook signature\n  const signedContent = `${timestamp}.${body}`;\n  const expectedSignature = crypto\n    .createHmac('sha256', process.env.RESEND_WEBHOOK_SECRET!)\n    .update(signedContent)\n    .digest('base64');\n\n  if (!crypto.timingSafeEqual(\n    Buffer.from(signature.split(',')[1].split(' ')[1]),\n    Buffer.from(expectedSignature)\n  )) {\n    return Response.json({ error: 'Invalid signature' }, { status: 401 });\n  }\n\n  // Process webhook\n  const event = JSON.parse(body);\n\n  switch (event.type) {\n    case 'email.delivered':\n      await logDelivery(event.data);\n      break;\n    case 'email.bounced':\n      await handleBounce(event.data);\n      break;\n  }\n\n  return Response.json({ success: true });\n}\n",[36,110,108],{"__ignoreMap":38},[112,113,115],"h4",{"id":114},"resend-security-checklist","Resend Security Checklist",[117,118,119,123,126,129,132,135,138,141,144,147],"ul",{},[120,121,122],"li",{},"API key stored in environment variable, never in code",[120,124,125],{},"All email sending happens server-side only",[120,127,128],{},"\"From\" address is fixed, not user-controlled",[120,130,131],{},"Email addresses validated with proper schema",[120,133,134],{},"User input sanitized before including in emails",[120,136,137],{},"Subject lines sanitized (no newlines)",[120,139,140],{},"Rate limiting implemented per user and per email",[120,142,143],{},"SPF, DKIM, and DMARC configured for domain",[120,145,146],{},"Webhook signatures verified before processing",[120,148,149],{},"Bounce handling implemented to protect reputation",[151,152,153,160,166],"faq-section",{},[154,155,157],"faq-item",{"question":156},"Can users send emails to anyone?",[13,158,159],{},"No. Only send to verified recipients (authenticated users, confirmed subscribers). Never let users specify arbitrary \"to\" addresses. For contact forms, send to your own address with the user's email as replyTo.",[154,161,163],{"question":162},"What if my API key is exposed?",[13,164,165],{},"Immediately rotate the key in your Resend dashboard. Check your sending logs for any unauthorized emails. Monitor for complaints or bounces that could indicate abuse.",[154,167,169],{"question":168},"Should I include user content in emails?",[13,170,171],{},"Be cautious. Always sanitize/escape HTML in user content. Consider using plain text for user-provided content. Never include user content in email headers.",[17,173,175],{"id":174},"what-checkyourvibe-detects","What CheckYourVibe Detects",[117,177,178,181,184,187,190],{},[120,179,180],{},"API keys exposed in client-side code",[120,182,183],{},"User-controlled \"from\" addresses",[120,185,186],{},"Missing input validation on email parameters",[120,188,189],{},"Unsanitized user content in email bodies",[120,191,192],{},"Missing rate limiting on email endpoints",[13,194,195,196,199],{},"Run ",[36,197,198],{},"npx checkyourvibe scan"," to catch these issues before they reach production.",[201,202,203,209,214],"related-articles",{},[204,205],"related-card",{"description":206,"href":207,"title":208},"Row-level security and auth patterns","/blog/guides/supabase","Supabase Security Guide",[204,210],{"description":211,"href":212,"title":213},"Webhook verification and payment security","/blog/guides/stripe","Stripe Security Guide",[204,215],{"description":216,"href":217,"title":218},"Best practices for key management","/blog/how-to/secure-api-keys","Secure API Keys",[220,221,224,228],"cta-box",{"href":222,"label":223},"/","Start Free Scan",[17,225,227],{"id":226},"scan-your-resend-integration","Scan Your Resend Integration",[13,229,230],{},"Find exposed API keys, email injection vulnerabilities, and missing rate limits before they cause problems.",{"title":38,"searchDepth":232,"depth":232,"links":233},2,[234,235,236,237,238,239,240,241,242],{"id":19,"depth":232,"text":20},{"id":26,"depth":232,"text":27},{"id":57,"depth":232,"text":58},{"id":70,"depth":232,"text":71},{"id":80,"depth":232,"text":81},{"id":90,"depth":232,"text":91},{"id":103,"depth":232,"text":104},{"id":174,"depth":232,"text":175},{"id":226,"depth":232,"text":227},"guides","2026-01-28","2026-02-19","Secure your Resend email integration when vibe coding. Learn API key management, email injection prevention, rate limiting, and template security patterns.",false,"md",null,"blue","Resend security, email API security, vibe coding email, transactional email security, email injection",{},true,"Secure your Resend email integration with proper API key handling and input validation.","/blog/guides/resend","8 min read","[object Object]","TechArticle",{"title":5,"description":246},{"loc":255},"blog/guides/resend",[],"summary_large_image","NEPAr-spKTDG2EnCtYj7ok8gb3P2skFyU-KrR_GWeGE",1775843929599]