email-notifications
Email and notification architecture: transactional email with Resend/SendGrid, React Email templates, notification preferences (channel, frequency, opt-out), delivery tracking, in-app notifications, and push notifications. Covers the full notification stack.
Best use case
email-notifications is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Email and notification architecture: transactional email with Resend/SendGrid, React Email templates, notification preferences (channel, frequency, opt-out), delivery tracking, in-app notifications, and push notifications. Covers the full notification stack.
Teams using email-notifications should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/email-notifications/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How email-notifications Compares
| Feature / Agent | email-notifications | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Email and notification architecture: transactional email with Resend/SendGrid, React Email templates, notification preferences (channel, frequency, opt-out), delivery tracking, in-app notifications, and push notifications. Covers the full notification stack.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Email & Notifications Skill
## When to Activate
- Sending transactional emails (welcome, reset password, receipts)
- Building notification preferences and opt-out flows
- Adding in-app notifications or activity feeds
- Setting up push notifications for web or mobile
- Email deliverability issues (landing in spam)
- Choosing between Resend, SendGrid, and Postmark for a new project
- Designing a queue-backed email pipeline that retries on failure
- Implementing GDPR-compliant unsubscribe flows with List-Unsubscribe headers
---
## Technology Selection
| Layer | Recommended | Alternative |
|-------|-------------|-------------|
| Transactional email API | Resend | SendGrid, Postmark |
| Email templates | React Email | MJML, Handlebars |
| In-app notifications | DB-backed (custom) | Novu, Courier |
| Push (web) | Web Push API | OneSignal |
| Push (mobile) | FCM / APNs | Expo Notifications |
| Notification orchestration | Novu | Courier, MagicBell |
---
## Pattern 1: Transactional Email with Resend + React Email
```tsx
// emails/WelcomeEmail.tsx
import {
Html, Head, Body, Container, Heading, Text, Button, Hr, Img,
} from '@react-email/components';
interface WelcomeEmailProps {
userName: string;
loginUrl: string;
}
export function WelcomeEmail({ userName, loginUrl }: WelcomeEmailProps) {
return (
<Html>
<Head />
<Body style={{ fontFamily: 'sans-serif', backgroundColor: '#f9fafb' }}>
<Container style={{ maxWidth: '560px', margin: '0 auto', padding: '24px' }}>
<Img src="https://yourdomain.com/logo.png" width={120} alt="Logo" />
<Heading>Welcome, {userName}!</Heading>
<Text>
Your account is ready. Click below to get started.
</Text>
<Button
href={loginUrl}
style={{
backgroundColor: '#3b82f6',
color: '#fff',
padding: '12px 24px',
borderRadius: '6px',
textDecoration: 'none',
}}
>
Get started
</Button>
<Hr />
<Text style={{ fontSize: '12px', color: '#9ca3af' }}>
You're receiving this because you created an account.
{/* Always include unsubscribe link, even for transactional */}
</Text>
</Container>
</Body>
</Html>
);
}
// Preview at http://localhost:3001 with: npx react-email dev
```
```typescript
// services/email.ts
import { Resend } from 'resend';
import { render } from '@react-email/render';
import { WelcomeEmail } from '../emails/WelcomeEmail';
const resend = new Resend(process.env.RESEND_API_KEY);
export async function sendWelcomeEmail(user: { email: string; name: string }) {
const html = await render(
WelcomeEmail({
userName: user.name,
loginUrl: `${process.env.APP_URL}/login`,
})
);
const { data, error } = await resend.emails.send({
from: 'Acme <noreply@acme.com>', // Must use verified domain
to: user.email,
subject: `Welcome to Acme, ${user.name}!`,
html,
// Idempotency: safe to retry
headers: { 'X-Entity-Ref-ID': `welcome-${user.id}` },
tags: [{ name: 'type', value: 'welcome' }],
});
if (error) {
throw new Error(`Failed to send welcome email: ${error.message}`);
}
// Log for audit
await db.insert(emailLogs).values({
userId: user.id,
type: 'welcome',
emailId: data!.id,
sentAt: new Date(),
});
}
```
---
## Pattern 2: Notification Preferences
```typescript
// schema: notification preferences per user per channel
// notifications_preferences table:
// user_id | type | channel | enabled | frequency
type NotificationType =
| 'order.shipped'
| 'order.delivered'
| 'comment.reply'
| 'mention'
| 'weekly.digest';
type NotificationChannel = 'email' | 'push' | 'in_app';
// Default preferences (applied on signup)
const DEFAULT_PREFERENCES: Record<NotificationType, Record<NotificationChannel, boolean>> = {
'order.shipped': { email: true, push: true, in_app: true },
'order.delivered': { email: true, push: true, in_app: true },
'comment.reply': { email: true, push: true, in_app: true },
'mention': { email: true, push: true, in_app: true },
'weekly.digest': { email: true, push: false, in_app: false },
};
async function shouldNotify(
userId: string,
type: NotificationType,
channel: NotificationChannel
): Promise<boolean> {
const pref = await db.query.notificationPreferences.findFirst({
where: and(
eq(notificationPreferences.userId, userId),
eq(notificationPreferences.type, type),
eq(notificationPreferences.channel, channel)
),
});
// Fall back to defaults if no explicit preference set
return pref?.enabled ?? DEFAULT_PREFERENCES[type][channel];
}
// Send notification through all enabled channels
async function notify(
userId: string,
type: NotificationType,
data: Record<string, unknown>
) {
const [emailOk, pushOk, inAppOk] = await Promise.all([
shouldNotify(userId, type, 'email'),
shouldNotify(userId, type, 'push'),
shouldNotify(userId, type, 'in_app'),
]);
await Promise.allSettled([
emailOk && sendNotificationEmail(userId, type, data),
pushOk && sendPushNotification(userId, type, data),
inAppOk && createInAppNotification(userId, type, data),
]);
}
```
---
## Pattern 3: In-App Notifications
```typescript
// In-app notifications: simple DB-backed approach
// schema: id, user_id, type, title, body, data, read_at, created_at
async function createInAppNotification(
userId: string,
type: string,
data: { title: string; body: string; link?: string; metadata?: unknown }
) {
const notification = await db.insert(notifications).values({
userId,
type,
title: data.title,
body: data.body,
link: data.link,
metadata: data.metadata,
}).returning();
// Push to connected WebSocket clients
notifyUser(userId, 'notification:new', notification[0]);
return notification[0];
}
async function markAsRead(userId: string, notificationId: string) {
await db
.update(notifications)
.set({ readAt: new Date() })
.where(
and(
eq(notifications.id, notificationId),
eq(notifications.userId, userId), // Security: users can only mark their own
isNull(notifications.readAt)
)
);
}
// API: unread count for badge
app.get('/api/v1/notifications/unread-count', authenticate, async (req, res) => {
const count = await db.$count(
notifications,
and(
eq(notifications.userId, req.user.id),
isNull(notifications.readAt)
)
);
res.json({ count });
});
```
---
## Pattern 4: Email Queuing (never send synchronously in request)
```typescript
// Never call Resend/SendGrid directly in an HTTP handler
// Queue email jobs — fast response + retry on failure
// WRONG:
app.post('/auth/register', async (req, res) => {
const user = await createUser(req.body);
await sendWelcomeEmail(user); // Blocks response, no retry on failure
res.json({ user });
});
// CORRECT:
app.post('/auth/register', async (req, res) => {
const user = await createUser(req.body);
await emailQueue.add('welcome', { userId: user.id }); // Queue it
res.json({ user }); // Respond immediately
});
// Worker processes the queue
emailQueue.process('welcome', async (job) => {
const user = await db.query.users.findFirst({ where: eq(users.id, job.data.userId) });
await sendWelcomeEmail(user!);
});
```
---
## Email Deliverability Essentials
```bash
# DNS records required for deliverability
# SPF: which servers can send email for your domain
TXT @ "v=spf1 include:_spf.resend.com ~all"
# DKIM: cryptographic signature (Resend/SendGrid generate this)
TXT resend._domainkey "v=DKIM1; k=rsa; p=..."
# DMARC: policy for failed SPF/DKIM
TXT _dmarc "v=DMARC1; p=quarantine; rua=mailto:dmarc@yourdomain.com"
```
```typescript
// Deliverability rules:
// 1. Always use a subdomain for transactional (notifications.yourdomain.com)
// — protects main domain reputation
// 2. Never send to unverified addresses — validate with a library
// 3. Honor unsubscribes immediately (CAN-SPAM, GDPR require this)
// 4. List-Unsubscribe header for one-click unsubscribe
const headers = {
'List-Unsubscribe': `<mailto:unsubscribe@yourdomain.com?subject=unsubscribe-${token}>`,
'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click',
};
```
---
## Notification Preference UI
```tsx
// components/NotificationPreferences.tsx
function NotificationPreferences() {
const { data: prefs } = useQuery({ queryKey: ['notification-prefs'], queryFn: fetchPrefs });
const { mutate: updatePref } = useMutation({ mutationFn: updateNotificationPref });
const rows: { type: NotificationType; label: string }[] = [
{ type: 'order.shipped', label: 'Order shipped' },
{ type: 'comment.reply', label: 'Replies to my comments' },
{ type: 'mention', label: 'Mentions' },
{ type: 'weekly.digest', label: 'Weekly digest' },
];
return (
<table>
<thead>
<tr>
<th>Notification</th>
<th>Email</th>
<th>Push</th>
<th>In-app</th>
</tr>
</thead>
<tbody>
{rows.map(row => (
<tr key={row.type}>
<td>{row.label}</td>
{(['email', 'push', 'in_app'] as NotificationChannel[]).map(channel => (
<td key={channel}>
<input
type="checkbox"
checked={prefs?.[row.type]?.[channel] ?? true}
onChange={e => updatePref({ type: row.type, channel, enabled: e.target.checked })}
aria-label={`${row.label} via ${channel}`}
/>
</td>
))}
</tr>
))}
</tbody>
</table>
);
}
```
---
## Checklist
- [ ] Emails sent via queue (never synchronously in request handler)
- [ ] SPF, DKIM, DMARC DNS records configured on sending domain
- [ ] Sending from a subdomain (not bare domain) for transactional
- [ ] Unsubscribe link in every email (even transactional)
- [ ] `List-Unsubscribe` header set for one-click unsubscribe
- [ ] Unsubscribe honored within 10 business days (CAN-SPAM) / immediately (GDPR)
- [ ] Notification preferences UI with per-type, per-channel granularity
- [ ] Failed email delivery retried with exponential backoff
- [ ] Email logs stored (for support: "did my email send?")
- [ ] React Email preview server for local template development
- [ ] HTML + plain text versions of every email (deliverability + accessibility)Related Skills
zero-trust-patterns
Zero-Trust security patterns — mTLS between microservices (Istio/SPIFFE), SPIRE workload identity, OPA/Envoy authorization, NetworkPolicy default-deny-all, short-lived credentials, service mesh security, and Kubernetes RBAC hardening.
wireframing
Wireframing and prototyping workflow: fidelity levels (lo-fi sketch → mid-fi wireframe → hi-fi prototype), tool selection (Figma, Excalidraw, Balsamiq), user flow diagrams, wireframe annotation standards, information architecture (IA) mapping, and the handoff from wireframe to visual design. For developers who need to communicate UI structure before writing code.
webrtc-patterns
WebRTC patterns — peer connection setup, ICE/STUN/TURN configuration, signaling server design, SFU vs mesh topology, screen sharing, media track management, and reconnect/ICE restart handling.
webhook-patterns
Webhook patterns for receiving, verifying (HMAC), and idempotently processing third-party events. Covers Stripe, GitHub, and generic webhook patterns, delivery guarantees, retry handling, and testing.
web-performance
Web performance optimization: Core Web Vitals (LCP, CLS, INP), Lighthouse CI with budget configuration, bundle analysis (webpack-bundle-analyzer, vite-bundle-visualizer), hydration performance, network waterfall reading, image optimization (WebP/AVIF, srcset), and font performance.
wasm-performance
WebAssembly performance: wasm-opt binary optimization, size reduction (panic=abort, LTO, strip), profiling WASM in Chrome DevTools, memory management (linear memory, avoiding GC pressure), SIMD, and multi-threading with SharedArrayBuffer.
wasm-patterns
WebAssembly patterns: wasm-pack, wasm-bindgen (JS↔Wasm interop), WASI, Component Model, wasm-opt, Rust-to-WASM compilation, JS integration (web workers, streaming instantiation), and production deployment (CDN, Content-Type headers).
visual-testing
Visual Regression Testing: tool comparison (Chromatic/Percy/Playwright screenshots/BackstopJS), pixel-diff vs AI-based comparison, baseline management, flakiness strategies (masks, tolerances, waitForLoadState), CI integration with GitHub Actions, and Storybook integration.
visual-identity
Brand identity development: color palette construction (primary/secondary/semantic/neutral), logo concept brief writing, typeface pairings, brand voice definition, mood board direction, and Brand Guidelines document structure. Use when establishing or evolving a visual brand — not for implementing existing tokens.
ux-micro-patterns
UX micro-patterns for every product state: Empty States, Loading States (skeleton screens, spinners, optimistic UI), Error States, Success States, Confirmation Dialogs, Onboarding Flows, and Progressive Disclosure. These patterns apply to every feature — done wrong, they're the biggest source of user confusion.
typography-design
Typography as a creative discipline: typeface selection criteria, type pairing (serif + sans, display + body), modular scale systems, line-height and tracking ratios, hierarchy construction, and web/mobile rendering considerations. The decisions behind design tokens, not the tokens themselves.
typescript-testing
TypeScript testing patterns: Vitest for unit/integration, Playwright for E2E, MSW for API mocking, Testing Library for React components. Core TDD methodology for TypeScript/JavaScript projects.