Claude-skill-registry email-service
Production email service with templates, queuing, and delivery tracking. Supports transactional emails, marketing campaigns, and webhooks.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/email-service" ~/.claude/skills/majiayu000-claude-skill-registry-email-service && rm -rf "$T"
manifest:
skills/data/email-service/SKILL.mdsource content
Email Service
Send transactional and marketing emails reliably.
When to Use This Skill
- User signup confirmations
- Password reset emails
- Order notifications
- Marketing campaigns
- Digest/summary emails
Architecture
┌─────────────────────────────────────────────────────┐ │ Application │ │ │ │ emailService.send({ │ │ to: "user@example.com", │ │ template: "welcome", │ │ data: { name: "John" } │ │ }) │ └─────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ Email Queue │ │ │ │ - Deduplication │ │ - Rate limiting │ │ - Retry logic │ │ - Priority handling │ └─────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ Email Provider (SendGrid/SES) │ │ │ │ - Template rendering │ │ - Delivery │ │ - Bounce/complaint handling │ └─────────────────────────────────────────────────────┘
TypeScript Implementation
Email Service
// email-service.ts import { Queue } from 'bullmq'; import { Redis } from 'ioredis'; interface EmailOptions { to: string | string[]; subject?: string; template: string; data: Record<string, unknown>; priority?: 'high' | 'normal' | 'low'; scheduledAt?: Date; tags?: string[]; } interface EmailTemplate { subject: string; html: string; text?: string; } class EmailService { private queue: Queue; private templates: Map<string, EmailTemplate> = new Map(); constructor(redis: Redis) { this.queue = new Queue('emails', { connection: redis }); this.loadTemplates(); } async send(options: EmailOptions): Promise<string> { const template = this.templates.get(options.template); if (!template) { throw new Error(`Template not found: ${options.template}`); } const jobId = `email-${Date.now()}-${Math.random().toString(36).slice(2)}`; const priority = { high: 1, normal: 5, low: 10 }[options.priority || 'normal']; await this.queue.add( 'send', { to: options.to, subject: options.subject || this.renderString(template.subject, options.data), html: this.renderString(template.html, options.data), text: template.text ? this.renderString(template.text, options.data) : undefined, tags: options.tags, }, { jobId, priority, delay: options.scheduledAt ? options.scheduledAt.getTime() - Date.now() : 0, attempts: 3, backoff: { type: 'exponential', delay: 60000 }, } ); return jobId; } async sendBulk(recipients: Array<{ email: string; data: Record<string, unknown> }>, template: string): Promise<string[]> { const jobIds: string[] = []; for (const recipient of recipients) { const jobId = await this.send({ to: recipient.email, template, data: recipient.data, priority: 'low', }); jobIds.push(jobId); } return jobIds; } private renderString(template: string, data: Record<string, unknown>): string { return template.replace(/\{\{(\w+)\}\}/g, (_, key) => String(data[key] || '')); } private loadTemplates(): void { this.templates.set('welcome', { subject: 'Welcome to {{appName}}!', html: ` <h1>Welcome, {{name}}!</h1> <p>Thanks for signing up. Get started by exploring your dashboard.</p> <a href="{{dashboardUrl}}">Go to Dashboard</a> `, }); this.templates.set('password-reset', { subject: 'Reset your password', html: ` <h1>Password Reset</h1> <p>Click the link below to reset your password. This link expires in 1 hour.</p> <a href="{{resetUrl}}">Reset Password</a> <p>If you didn't request this, ignore this email.</p> `, }); this.templates.set('order-confirmation', { subject: 'Order #{{orderId}} confirmed', html: ` <h1>Order Confirmed</h1> <p>Thanks for your order, {{name}}!</p> <p>Order ID: {{orderId}}</p> <p>Total: {{total}}</p> <a href="{{orderUrl}}">View Order</a> `, }); } } export { EmailService, EmailOptions };
Email Worker
// email-worker.ts import { Worker, Job } from 'bullmq'; import { SESClient, SendEmailCommand } from '@aws-sdk/client-ses'; interface EmailJob { to: string | string[]; subject: string; html: string; text?: string; tags?: string[]; } const ses = new SESClient({ region: process.env.AWS_REGION }); const worker = new Worker<EmailJob>( 'emails', async (job: Job<EmailJob>) => { const { to, subject, html, text } = job.data; const recipients = Array.isArray(to) ? to : [to]; const command = new SendEmailCommand({ Source: process.env.EMAIL_FROM!, Destination: { ToAddresses: recipients }, Message: { Subject: { Data: subject }, Body: { Html: { Data: html }, Text: text ? { Data: text } : undefined, }, }, }); const result = await ses.send(command); // Log for tracking await logEmailSent({ jobId: job.id, messageId: result.MessageId, to: recipients, subject, tags: job.data.tags, }); return { messageId: result.MessageId }; }, { connection: redis, concurrency: 10, limiter: { max: 100, duration: 1000 }, // 100 emails/second } ); worker.on('failed', (job, err) => { console.error(`Email job ${job?.id} failed:`, err); }); export { worker };
Python Implementation
# email_service.py from dataclasses import dataclass from typing import Optional import boto3 from redis import Redis from rq import Queue @dataclass class EmailOptions: to: str | list[str] template: str data: dict subject: Optional[str] = None priority: str = "normal" tags: Optional[list[str]] = None class EmailService: def __init__(self, redis: Redis): self.queue = Queue("emails", connection=redis) self.templates = self._load_templates() def send(self, options: EmailOptions) -> str: template = self.templates.get(options.template) if not template: raise ValueError(f"Template not found: {options.template}") subject = options.subject or self._render(template["subject"], options.data) html = self._render(template["html"], options.data) job = self.queue.enqueue( send_email_task, options.to, subject, html, options.tags, ) return job.id def _render(self, template: str, data: dict) -> str: for key, value in data.items(): template = template.replace(f"{{{{{key}}}}}", str(value)) return template def _load_templates(self) -> dict: return { "welcome": { "subject": "Welcome to {{app_name}}!", "html": "<h1>Welcome, {{name}}!</h1>", }, "password-reset": { "subject": "Reset your password", "html": "<a href='{{reset_url}}'>Reset Password</a>", }, } def send_email_task(to: str | list[str], subject: str, html: str, tags: list[str] = None): ses = boto3.client("ses") recipients = [to] if isinstance(to, str) else to ses.send_email( Source=os.environ["EMAIL_FROM"], Destination={"ToAddresses": recipients}, Message={ "Subject": {"Data": subject}, "Body": {"Html": {"Data": html}}, }, )
Webhook Handling (Bounces/Complaints)
// email-webhooks.ts import { Router } from 'express'; const router = Router(); // SES webhook (via SNS) router.post('/webhooks/ses', async (req, res) => { const message = JSON.parse(req.body.Message); switch (message.notificationType) { case 'Bounce': await handleBounce(message.bounce); break; case 'Complaint': await handleComplaint(message.complaint); break; case 'Delivery': await handleDelivery(message.delivery); break; } res.sendStatus(200); }); async function handleBounce(bounce: any) { for (const recipient of bounce.bouncedRecipients) { await db.emailSuppressions.upsert({ where: { email: recipient.emailAddress }, create: { email: recipient.emailAddress, reason: 'bounce', bounceType: bounce.bounceType, }, update: { reason: 'bounce', bounceType: bounce.bounceType }, }); } } async function handleComplaint(complaint: any) { for (const recipient of complaint.complainedRecipients) { await db.emailSuppressions.upsert({ where: { email: recipient.emailAddress }, create: { email: recipient.emailAddress, reason: 'complaint' }, update: { reason: 'complaint' }, }); } }
Best Practices
- Always queue emails - Never send synchronously
- Handle bounces/complaints - Maintain suppression list
- Use templates - Consistent branding, easier updates
- Include unsubscribe links - Legal requirement (CAN-SPAM)
- Track delivery metrics - Monitor bounce rates
Common Mistakes
- Sending emails synchronously (blocks requests)
- Ignoring bounces (damages sender reputation)
- No rate limiting (provider throttling)
- Missing unsubscribe mechanism
- Not validating email addresses before sending