Turn your Markdown into a self-hosted newsletter.
Write your newsletter in Markdown with whatever tools you like (Obsidian, Logseq, VSCode, etc.). laughing-man turns them into a static archive site and email-ready newsletter HTML. Host on Cloudflare Pages, deliver to subscribers with Resend. Fully self-hosted, fully free within their free tiers. No CMS, no database, no code.
Requires Node.js 22+ (for npx) and a domain name.
npm install -g @sadcoderlabs/laughing-manOr run without installing:
npx @sadcoderlabs/laughing-man --helpIf you're the type who skips the manual, just copy this prompt to your agent:
How do I use this tool? Read https://raw.githubusercontent.com/sadcoderlabs/laughing-man/main/skills/laughing-man/SKILL.md
Generate laughing-man.yaml in any folder:
cd /path/to/your/markdown/folder/
laughing-man initPreview your newsletter website (and the email template) with the local server:
laughing-man previewname: Your Newsletter Name
description: A newsletter by [Your Name](https://blog.example.com)
issues_dir: .
attachments_dir: .
author:
name: Your Name
url: https://example.com
x_handle: "@your_handle"
web_hosting:
provider: cloudflare-pages
project: your-newsletter-name
domain: example.com
email_hosting:
provider: resend
from: "Your Name <your-name@newsletter.example.com>"
reply_to: your-name@newsletter.example.com
env:
CLOUDFLARE_API_TOKEN: "cf_xxxxx" # or set CLOUDFLARE_API_TOKEN env var
RESEND_API_KEY: "re_xxxxx" # or set RESEND_API_KEY env var- Get your Cloudflare API token from dash.cloudflare.com/profile/api-tokens
- Permissions:
Account | Cloudflare Pages | EditZone | DNS | Edit
- Account Resources
Include | your account name
- Zone Resources:
Include | Specific zone | example.com
- Permissions:
- Get your Resend API key from resend.com/api-keys
- Permission: Full access
- Because the subscribe function creates contacts, not just sends email
- Permission: Full access
Set up Cloudflare Pages (project + custom domain + DNS) and deploy:
laughing-man setup web # Create Cloudflare Pages project + custom domain + DNS
laughing-man deploy # Deploy to Cloudflare PagesSet up Resend and send an issue:
laughing-man setup newsletter # Verify Resend API key + sender domain + DNS
laughing-man send <issue-number> # Send an issue via Resend Broadcast