| Design, build, and send beautiful HTML email campaigns via Resend. Handles asset hosting (images + GIFs on your own CDN), the pre-built email HTML component library (frost-glass cards, hero image, video blocks with blurred backgrounds, CTA buttons, footer), GIF optimization from MP4 source video, and sending via the Resend API. Use when asked to "create an email", "draft a marketing email", "send a campaign", "make an email sequence", or "build an email with Resend". Proactively suggest when the user mentions sending marketing emails, launching a feature via email, or designing a newsletter.
npx skills add https://github.com/irinabuht12-oss/email-campaigns-claude --skill email-campaigns
Build modern, on-brand marketing emails and send them via Resend. This skill encodes a complete pattern: asset hosting, reusable HTML components, GIF tooling, and the Resend send pipeline.
Setup once. In a Next.js or any static-hosted project, create a folder served at the site root:
<project>/public/email-assets/
<campaign-name>/
hero.png
demo.gif
Anything in public/ is served at https://yourdomain.com/email-assets/.... Commit + push = instant CDN. No Cloudinary, no S3.
Rules for email-safe assets:
hero-image.jpg ✅, Hero Image.jpg ❌)<video> — always use GIF thumbnail linking to MP4hero-v2.jpg) — email clients cache aggressivelyBefore sending: swap all local paths to absolute URLs (https://yourdomain.com/email-assets/...). Relative paths work in browser preview but break in recipients' inboxes.
Use this building-block inventory. Copy from a previous campaign, don't redesign from scratch.
<body style="margin:0; padding:0; background-color:#ffffff;
font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;">
<div style="display:none; max-height:0; overflow:hidden; font-size:1px; line-height:1px; color:#fff;">
[PREHEADER — 1 sentence, shown in inbox preview]
</div>
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:#ffffff;">
<tr><td align="center" style="padding:32px 16px;">
<table width="560" cellpadding="0" cellspacing="0" border="0"
style="max-width:560px; width:100%;">
<!-- MAIN CARD -->
<tr><td>
<table width="100%" cellpadding="0" cellspacing="0" border="0"
style="background-color:#f7fbfe;
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48ZmlsdGVyIGlkPSJuIj48ZmVUdXJidWxlbmNlIHR5cGU9ImZyYWN0YWxOb2lzZSIgYmFzZUZyZXF1ZW5jeT0iMC44NSIgbnVtT2N0YXZlcz0iMiIgc3RpdGNoVGlsZXM9InN0aXRjaCIvPjwvZmlsdGVyPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbHRlcj0idXJsKCNuKSIgb3BhY2l0eT0iMC4wNiIvPjwvc3ZnPg=='),
linear-gradient(90deg, #edf6fd 0%, #f6fbfe 50%, #fcfdfe 100%);
border-radius:4px;
box-shadow: 0 2px 8px rgba(24,24,27,0.06), 0 8px 24px rgba(24,24,27,0.04);
overflow:hidden;">
<!-- sections go here -->
</table>
</td></tr>
</table>
</td></tr>
</table>
</body>
Key tokens:
90deg = left→right.Full-width image flush with card top. Crop the top 3/4 of a wide image (1376×576 cropped from 1376×768) for hero; use the bottom 1/3 (256px) as a footer image inside the card.
Five logos distributed across full card width via width="20%" columns. Each card 72×72, light fill (#f4f4f7 gradient), soft shadow, 3px radius.
<h1 style="font-size:26px; font-weight:700; color:#18181b; letter-spacing:-0.5px;">...</h1>
<p style="font-size:15px; line-height:1.65; color:#3f3f46;">...</p>
Keep bullets black • in a 20px cell, text 15px. Avoid green check pills — they look dated.
Pre-blur your brand hero image with ffmpeg to use as a colorful bed behind videos:
ffmpeg -y -i header.png -vf "boxblur=luma_radius=30:luma_power=3" header_blur.png
Then:
<a href="LANDING-URL">
<p>Video title</p>
<table width="100%">
<tr><td style="background-image:url('https://yourdomain.com/email-assets/header_blur.png');
background-size:cover; border-radius:3px; padding:14px;
box-shadow:0 4px 14px rgba(24,24,27,0.1);">
<table width="100%">
<tr><td style="background-color:rgba(255,255,255,0.55);
backdrop-filter:blur(12px);
-webkit-backdrop-filter:blur(12px);
border:1px solid rgba(255,255,255,0.6);
border-radius:3px; padding:10px;
box-shadow:inset 0 1px 0 rgba(255,255,255,0.5);">
<img src="GIF-URL" width="475" style="display:block; width:100%;
max-width:475px; border-radius:3px;">
</td></tr>
</table>
</td></tr>
</table>
</a>
In Apple Mail/iOS: backdrop-filter renders real glass. In Gmail/Outlook: the pre-blurred image underneath sells the effect on its own.
Black pill, 4px radius, 14px 24px padding, optional small logo inline:
<table width="100%"><tr><td align="center"
style="border-radius:4px; background-color:#18181b;">
<a href="LANDING-URL" style="display:block; padding:14px 24px;
font-size:14px; font-weight:600; color:#fff;
text-decoration:none;">
<img src="LOGO" width="20" height="20"
style="display:inline-block; vertical-align:middle;
margin-right:8px; border-radius:3px;" alt="">
<span style="vertical-align:middle;">Connect your accounts →</span>
</a>
</td></tr></table>
Place one CTA immediately after the first proof video (highest conversion moment) and one at the bottom.
Stick to this. No more than 3 sizes.
For each screen recording:
# Convert MP4 → optimized GIF (720px, 15fps, 2x speed, trim to 8s)
ffmpeg -y -i source.mp4 -t 8 \
-vf "setpts=0.5*PTS,fps=15,scale=720:-1:flags=lanczos,split[s0][s1];\
[s0]palettegen=max_colors=256[p];\
[s1][p]paletteuse=dither=sierra2_4a" \
-loop 0 output.gif
# Check file size — should be under 1.5 MB
ls -lh output.gif
If too big:
dither=bayer:bayer_scale=5If too low quality:
sierra2_4a dithernpm install resend
Env var: RESEND_API_KEY=re_... (get from resend.com dashboard)
From domain: verify your sending domain in Resend settings first (DNS records take ~15 min to propagate). Use [email protected], not a free provider.
// send.mjs
import { Resend } from 'resend';
import fs from 'fs';
const resend = new Resend(process.env.RESEND_API_KEY);
const html = fs.readFileSync('./email.html', 'utf-8');
const { data, error } = await resend.emails.send({
from: 'Ira <[email protected]>',
to: '[email protected]',
subject: 'Claude Connector for Google/Meta Ads is live',
html,
// Optional plain-text fallback
text: 'We released the Claude connector. Connect in 1 click: https://...',
});
console.log(error ?? data);
Run: node send.mjs. Always send to yourself first.
Resend rate-limits at 10 req/sec (free) or 100 req/sec (paid). For larger lists use their batch API:
await resend.batch.send(
recipients.map(email => ({
from: 'Ira <[email protected]>',
to: email,
subject: '...',
html,
}))
);
Up to 100 emails per batch call. Chunk your list accordingly.
For a 3-email sequence (day 0, day 3, day 7), use a cron + a sent-state table in Postgres/Supabase:
// Pseudo
const dueToday = await db.campaigns.findMany({
where: { scheduled_date: today, status: 'pending' }
});
for (const c of dueToday) {
await resend.emails.send({ ...c });
await db.campaigns.update({ where: { id: c.id }, data: { status: 'sent' } });
}
Resend has webhooks for email.delivered, email.opened, email.clicked — use them to trigger the next email in the sequence rather than fixed timers.
Before you hit send on a real list:
https://...), not relative (/email-assets/...)alt texttarget="_blank"from address is on a verified domain in ResendTo bootstrap a new campaign, copy the most recent working HTML from your repo (e.g. public/email-assets/preview/claude-connector.html) and edit in place. Keep the preview file checked in alongside the campaign folder:
public/email-assets/2026-05-launch/
hero.png
demo.gif
preview.html
Preview locally at http://localhost:3000/email-assets/2026-05-launch/preview.html.
<video> tags — blocked in Gmail/Outlook<table>s.<body> if you need them on mobile Gmail — it ignores body bg. Put color inside the main card.Take irinabuht12-oss/email-campaigns from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference npm.
Without those the skill loads but fails at the first command.