Plunk
Transactional emails + weekly newsletter delivery via Plunk.
Plunk handles both transactional emails and campaign emails (weekly newsletter).
Setup
- Sign up at useplunk.com
- Add + verify your sending domain via DNS
- Get your Secret and Public API keys + set your verified sending email
PLUNK_SECRET_KEY=sk_...
PLUNK_PUBLIC_KEY=pk_...
PLUNK_FROM_EMAIL=[email protected]Without PLUNK_SECRET_KEY, lib/plunk.ts returns null from send() and logs a warning — callers fail gracefully, nothing breaks.
Emails shipped
All templates are React Email components in emails/:
| File | When it fires |
|---|---|
welcome.tsx | First account creation |
verify-email.tsx | Sign-up (email verification link) |
reset-password.tsx | Password reset request |
submission-received.tsx | Free-tier submission created |
submission-accepted.tsx | Admin accepts a free-tier submission |
submission-rejected.tsx | Admin rejects any submission |
submission-revision.tsx | Admin requests revision |
submission-published.tsx | User's product goes live on batch publish |
payment-confirmed.tsx | Stripe payment completes (Boost / Highlight) |
sponsor-confirmed.tsx | Stripe payment completes (sponsor booking) |
weekly-newsletter.tsx | Weekly batch publishes |
template-granted.tsx | Sales-site only — excluded from customer repo |
Sending an email
Helper signature in lib/plunk.ts:
await send({ to, subject, body, subscribed })to— recipient emailsubject— email subjectbody— rendered HTML string (render from a React Email component with@react-email/components)subscribed—true|false— whether Plunk should treat this address as a subscriber
Typical usage in lib/emails.ts:
const body = await render(<WelcomeEmail name={user.name} />)
await send({ to: user.email, subject: "Welcome", body, subscribed: true })Newsletter (campaign)
For the weekly newsletter, the cron uses Plunk's /campaigns endpoint to create and send. The subscribed audience is managed entirely by Plunk — Launchy doesn't maintain a subscribers table. See Newsletter.
DNS / deliverability
Plunk auto-handles SPF + DKIM once you verify your domain. For DMARC (recommended):
Type: TXT
Host: _dmarc
Value: v=DMARC1; p=none; rua=mailto:[email protected]Start with p=none to observe; upgrade to p=quarantine or p=reject after a few weeks.
Preview templates locally
bun run email:devReact Email's dev server renders every template in emails/ with hot reload. Open the browser preview and iterate.
Testing real sends
bun scripts/send-test-newsletter.tsRequires PLUNK_SECRET_KEY and TEST_EMAIL set. Renders the current week's batch and sends to you.
Custom emails
- Create
emails/my-email.tsx:import { EmailWrapper, Text } from "./components" export default function MyEmail({ name }: { name: string }) { return <EmailWrapper preview="Hi"><Text>Hi {name}</Text></EmailWrapper> } - Send from anywhere:
const body = await render(<MyEmail name={user.name} />) await send({ to, subject: "Hi", body, subscribed: false })
Switching providers
To swap Plunk for Resend / SendGrid / Postmark, rewrite the body of send() in lib/plunk.ts. The call sites don't change.
Common issues
Emails not delivering — Check Plunk dashboard → Sends. Bounces or spam complaints suppress future sends.
"From address not verified" — Finish domain verification in Plunk.
Dev sees no email but no error — PLUNK_SECRET_KEY empty → the lib silently returns null. Set the key, or check logs for the "PLUNK_SECRET_KEY is not set" warning.