An open API service indexing awesome lists of open source software.

https://github.com/vinta/laughing-man

Turn your Markdown into a self-hosted newsletter
https://github.com/vinta/laughing-man

Last synced: about 2 months ago
JSON representation

Turn your Markdown into a self-hosted newsletter

Awesome Lists containing this project

README

          


Laughing Man logo

# laughing-man

[![npm Version](https://img.shields.io/npm/v/laughing-man-cli?style=for-the-badge)](https://www.npmjs.com/package/laughing-man-cli)
[![CI](https://img.shields.io/github/actions/workflow/status/vinta/laughing-man/ci.yml?style=for-the-badge&label=Build)](https://github.com/vinta/laughing-man/actions/workflows/ci.yml)
[![Agent Friendly](https://img.shields.io/badge/Agent-Friendly-blue?style=for-the-badge)](https://github.com/vinta/laughing-man/blob/main/skills/laughing-man/SKILL.md)

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.

## Installation

Requires Node.js 22+ (for npx) and a domain name.

```bash
npm install -g laughing-man-cli
```

Or run without installing:

```bash
npx laughing-man-cli --help
```

## Usage

If you're the type who skips the manual, just copy this prompt to your agent:

```prompt
How do I use this tool? Read https://raw.githubusercontent.com/vinta/laughing-man/main/skills/laughing-man/SKILL.md
```

### Initiate

Generate `laughing-man.yaml` in any folder:

```bash
cd /path/to/your/markdown/folder/
laughing-man init
```

### Preview

Preview your newsletter website (and the email template) with the local server:

```bash
laughing-man preview
```

### Configure

```yaml
name: Your Newsletter Name
description: A newsletter by [Your Name](https://blog.example.com)
issues_dir: .
attachments_dir: .

theme: laughing-man
syntax_highlight_theme: material-theme-lighter

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 "
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](https://dash.cloudflare.com/profile/api-tokens)
- Permissions:
- `Account | Cloudflare Pages | Edit`
- `Zone | DNS | Edit`
- Account Resources
- `Include | your account name`
- Zone Resources:
- `Include | Specific zone | example.com`
- Get your Resend API key from [resend.com/api-keys](https://resend.com/api-keys)
- Permission: **Full access**
- Because the subscribe function creates contacts, not just sends email

### Deploy

Set up Cloudflare Pages (project + custom domain + DNS) and deploy:

```bash
laughing-man setup web # Create Cloudflare Pages project + custom domain + DNS
laughing-man deploy # Deploy to Cloudflare Pages
```

Set up Resend and send an issue:

```bash
laughing-man setup newsletter # Verify Resend API key + sender domain + DNS
laughing-man send # Send an issue via Resend Broadcast
laughing-man send status # Show delivery status for all sent broadcasts
```