Email Delivery Skill
Comprehensive patterns and templates for implementing robust email delivery with Resend, covering single emails, batch operations, scheduled delivery, and attachment handling.
Use When
-
Building transactional email systems (confirmations, notifications, alerts)
-
Implementing batch email campaigns (up to 100 recipients per request)
-
Setting up scheduled/delayed email delivery
-
Handling file attachments, buffers, or URL-based attachments
-
Adding reply-to, CC, and BCC functionality
-
Creating email templates with variables and dynamic content
-
Implementing retry logic and delivery error handling
Core Patterns
- Single Email Sending
Transactional emails for immediate delivery with minimal latency:
import { Resend } from 'resend';
const resend = new Resend('your_resend_key_here');
async function sendTransactionalEmail() { const { data, error } = await resend.emails.send({ from: 'notifications@example.com', to: 'user@example.com', subject: 'Welcome to Example', html: '<h1>Welcome!</h1><p>Thank you for signing up.</p>', });
if (error) { console.error('Failed to send email:', error); return null; }
return data; }
- Batch Email Sending
Bulk operations for sending up to 100 emails in a single request:
async function sendBatchEmails(recipients: Array<{email: string; name: string}>) {
const emails = recipients.map(recipient => ({
from: 'newsletter@example.com',
to: recipient.email,
subject: Hello ${recipient.name}!,
html: <p>Welcome ${recipient.name}</p>,
}));
const { data, error } = await resend.batch.send(emails);
if (error) { console.error('Batch send failed:', error); return null; }
return data; }
- Scheduled Email Delivery
Time-based delivery for emails sent at specific times:
async function scheduleEmail(scheduledAt: Date) { const { data, error } = await resend.emails.send({ from: 'marketing@example.com', to: 'user@example.com', subject: 'Scheduled Message', html: '<p>This was scheduled!</p>', scheduled_at: scheduledAt.toISOString(), });
if (error) { console.error('Failed to schedule email:', error); return null; }
return data; }
- Attachment Handling
File attachments from files, buffers, or URLs:
File-based Attachment
import fs from 'fs'; import path from 'path';
async function sendWithFileAttachment(filePath: string) { const fileContent = fs.readFileSync(filePath); const fileName = path.basename(filePath);
const { data, error } = await resend.emails.send({ from: 'documents@example.com', to: 'recipient@example.com', subject: 'Your Document', html: '<p>Please find attached your document.</p>', attachments: [ { filename: fileName, content: fileContent, }, ], });
return { data, error }; }
Buffer-based Attachment
async function sendWithBufferAttachment(buffer: Buffer, filename: string) { const { data, error } = await resend.emails.send({ from: 'reports@example.com', to: 'user@example.com', subject: 'Monthly Report', html: '<p>Your monthly report is attached.</p>', attachments: [ { filename: filename, content: buffer, }, ], });
return { data, error }; }
URL-based Attachment
async function sendWithUrlAttachment(fileUrl: string) { const response = await fetch(fileUrl); const buffer = await response.arrayBuffer();
const { data, error } = await resend.emails.send({ from: 'notifications@example.com', to: 'user@example.com', subject: 'Download Your File', html: '<p>Your file is ready.</p>', attachments: [ { filename: 'document.pdf', content: Buffer.from(buffer), }, ], });
return { data, error }; }
- Reply-To and CC/BCC
Message routing with multiple recipients and reply addresses:
async function sendWithRouting(mainRecipient: string) { const { data, error } = await resend.emails.send({ from: 'support@example.com', to: mainRecipient, reply_to: 'support-team@example.com', cc: ['manager@example.com'], bcc: ['archive@example.com'], subject: 'Support Ticket #12345', html: '<p>We received your support request.</p>', });
return { data, error }; }
Python Patterns
Single Email (Python)
import os from resend import Resend
client = Resend(api_key=os.environ.get("RESEND_API_KEY"))
def send_email(): email = { "from": "notifications@example.com", "to": "user@example.com", "subject": "Welcome", "html": "<h1>Welcome!</h1>", }
response = client.emails.send(email)
return response
Batch Email (Python)
def send_batch_emails(recipients): emails = [ { "from": "newsletter@example.com", "to": recipient["email"], "subject": f"Hello {recipient['name']}", "html": f"<p>Welcome {recipient['name']}</p>", } for recipient in recipients ]
response = client.batch.send(emails)
return response
File Attachment (Python)
def send_with_attachment(file_path): with open(file_path, 'rb') as f: file_content = f.read()
email = {
"from": "documents@example.com",
"to": "recipient@example.com",
"subject": "Your Document",
"html": "<p>Document attached.</p>",
"attachments": [
{
"filename": "document.pdf",
"content": file_content,
}
],
}
response = client.emails.send(email)
return response
Template Variables
Environment Variables Required
RESEND_API_KEY=your_resend_key_here RESEND_FROM_EMAIL=your-verified-email@example.com
Email Template Variables
interface EmailPayload { from: string; // Verified sender email to: string | string[]; // Recipient(s) cc?: string[]; // Carbon copy recipients bcc?: string[]; // Blind carbon copy reply_to?: string; // Reply-to address subject: string; // Email subject html?: string; // HTML content text?: string; // Plain text fallback attachments?: Array<{ filename: string; content: Buffer | string; }>; scheduled_at?: string; // ISO 8601 datetime for scheduling tags?: Array<{ name: string; value: string; }>; }
Best Practices
Error Handling
Always implement retry logic for transient failures:
async function sendWithRetry(emailPayload, maxRetries = 3) { for (let attempt = 1; attempt <= maxRetries; attempt++) { const { data, error } = await resend.emails.send(emailPayload);
if (!error) return { data, success: true };
if (error.message?.includes('rate_limit') && attempt < maxRetries) {
const delay = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
return { error, success: false };
} }
Rate Limiting
Resend has rate limits. For batch operations over 100 emails:
async function sendLargeBatch(emails: EmailPayload[]) { const batchSize = 100; const results = [];
for (let i = 0; i < emails.length; i += batchSize) { const batch = emails.slice(i, i + batchSize); const { data, error } = await resend.batch.send(batch);
if (error) {
console.error(`Batch ${Math.floor(i / batchSize) + 1} failed:`, error);
results.push({ success: false, error });
} else {
results.push({ success: true, data });
}
// Rate limit handling - wait between batches
if (i + batchSize < emails.length) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
return results; }
Template Best Practices
-
Use responsive HTML templates
-
Always provide text fallback
-
Test across email clients
-
Include unsubscribe links for marketing emails
-
Avoid large attachments (keep under 25MB)
Examples Directory Structure
-
single-email/
-
Basic transactional email patterns
-
batch-emails/
-
Bulk sending with 50-100 recipients
-
attachments/
-
File, buffer, and URL attachment handling
-
scheduled/
-
Time-based delivery scheduling
See individual example README files for complete code and usage patterns.
Related Skills
-
email-templates - HTML template management and rendering
-
email-validation - Recipient address validation
-
email-webhooks - Delivery event tracking and bounce handling
Resources
-
Resend API Documentation
-
Email Authentication (SPF, DKIM, DMARC)
-
Batch Email API
-
Scheduled Emails
Security Notes
-
API keys should be stored in environment variables (never hardcoded)
-
Use RESEND_API_KEY from secure secret management
-
Verify sender emails before using in production
-
Implement authentication for email submission endpoints
-
Log email events for compliance and debugging