Handle transactional mail + newsletters (broadcasts) with just Resend. Free tier: $0, 3,000/month, 100/day, 1 domain — enough for a small newsletter for free. (Verified: resend.com/pricing, 2026-07-16)
Hard rules
- Sending is user-gated — "write it for me" is not approval to send. Broadcasts must always be created as a draft and sent after user confirmation.
- Broadcasts require
{{{RESEND_UNSUBSCRIBE_URL}}}— mandatory — marketing mail without an unsubscribe link violates spam laws (CAN-SPAM/Personal Information Protection Act). - Self-test before the real send — send 1 copy to your own address to check rendering/links/merge tags.
- 100/day limit (Free) — for lists over 100, tell the user to split by day or upgrade to Pro ($20/mo, 50,000).
- No cold outreach — Resend is permission-based. Cold mail needs a separate domain + dedicated tool (b2b-cold-email).
- On Windows, call the API with Python urllib + utf-8 (curl+cp1252 crashes on emoji).
- Subject lines assume A/B — for each broadcast, create 3 subject candidates (different axes: numbers/questions/direct) for the user to choose from, and if the list is 1,000+, split the audience into segments and do a real A/B send → make the open-rate winner the default axis for the next issue. If there's a hero image, make 2–3 variants with the image-gen skill.
Setup (1회)
- Sign up at resend.com → API Keys → save to env
RESEND_API_KEY(re_...). - Domain verification: Dashboard → Domains → Add (or
POST /domains) → add the issued SPF/DKIM DNS records to your domain DNS → Verify.fromcan only use addresses on a verified domain (뉴스레터 <news@yourdomain.com>). - Base
https://api.resend.com,Authorization: Bearer $RESEND_API_KEY. - VERIFY:
GET /domains에 status "verified".
A. Transactional (1:1)
POST /emails
{ "from": "Acme <hello@yourdomain.com>", "to": ["user@example.com"],
"subject": "...", "html": "..." }
- Bulk individual sending:
POST /emails/batch— up to 100 objects at a time. reply_to,cc,bcc,attachmentssupported.
B. Newsletter (Audience + Broadcast)
# 1) 오디언스 생성 (리스트당 1개)
POST /audiences { "name": "Newsletter" }
# 2) 구독자 추가
POST /audiences/{id}/contacts
{ "email": "user@example.com", "first_name": "Jane", "unsubscribed": false }
# 3) 브로드캐스트 생성 (초안)
POST /broadcasts
{ "audience_id": "aud_...",
"from": "News <news@yourdomain.com>",
"name": "2026-07 뉴스레터",
"subject": "이번 달 소식",
"html": "Hi {{{contact.first_name|there}}}, ...
<a href='{{{RESEND_UNSUBSCRIBE_URL}}}'>수신거부</a>" }
# 4) 유저 승인 후 발송 (즉시 또는 예약)
POST /broadcasts/{id}/send # 즉시
POST /broadcasts/{id}/send { "scheduled_at": "2026-07-20T09:00:00+09:00" }
- Merge tags:
{{{contact.first_name|fallback}}}— what's after the pipe is the default value. - On creation,
"send": true+"scheduled_at": "in 1 hour"(natural language allowed) lets you schedule in one go — but because of rule 1, the default is draft → approve → send, a 2-step process. - Unsubscribes are handled automatically by Resend (marked as
unsubscribed: true, then automatically excluded from broadcasts).
C. Verification · Operations
GET /emails/{id}— delivery status (delivered/bounced/complained).- Clean bounces/complaints from the list — also used for filtering fake leads (automatic welcome email right after lead collection → hard bounce = immediately delete the fake DB entry).
- Deliverability basics: complete SPF/DKIM verification, fixed from address, no clickbait subject lines, confirm the text version is auto-generated.
When migrating from MailerLite etc.
Export subscribers as CSV → unsubscribed import into contacts while preserving status (never re-subscribe unsubscribers) → for the first broadcast, check deliverability with a small segment before sending to everyone.