---
title: "Building a No-Tracking Newsletter from Markdown to Distribution"
description: "Build a privacy-focused newsletter with Python, Cloudflare Workers KV, and Resend API. Zero tracking, zero cost, full control. Open source code included."
date: 2025-12-24
updated: 2026-05-04
author: "Philipp D. Dubach"
categories:
  - "Tech"
keywords:
  - "self-hosted newsletter"
  - "Cloudflare Workers newsletter"
  - "no tracking newsletter email"
  - "Resend API email tutorial"
  - "markdown to HTML email Python"
type: "Project"
canonical_url: "https://philippdubach.com/posts/building-a-no-tracking-newsletter-from-markdown-to-distribution/"
source_url: "https://philippdubach.com/posts/building-a-no-tracking-newsletter-from-markdown-to-distribution/index.md"
content_signal: search=yes, ai-input=yes, ai-train=yes
---

# Building a No-Tracking Newsletter from Markdown to Distribution

*Philipp D. Dubach · Published December 24, 2025 · Updated May 4, 2026*


## Key Takeaways

- The entire newsletter pipeline runs at zero cost using Cloudflare Workers KV for subscribers, R2 for hosting, and Resend's free tier for 3,000 emails per month.
- The system sends no tracking pixels, no click tracking, and no external analytics, just rendered HTML from Markdown with table-based layout for email client compatibility.
- A Python engine fetches OpenGraph metadata, generates LinkedIn-style preview cards, and optimizes images at 240px width for retina displays, all automated from a single .md file.


---

![Screenshot of rendered newsletter showing article preview cards with images and descriptions](https://static.philippdubach.com/cdn-cgi/image/width=1600,quality=85,format=auto/Newsletter_Overview2.jpg)

Friends have been asking how they can stay up to date with what I'm working on and keep track of the things I read, write, and share. RSS feeds don't seem to be en vogue anymore, apparently. So I built a mailing list. What else would you do over the Christmas break?

From a previous marketing job I knew Mailchimp. Also, every newsletter I unsubscribe from is Mailchimp. I no longer wish to receive these emails.
![Unsubscribe confirmation from Mailchimp newsletters](https://static.philippdubach.com/cdn-cgi/image/width=1600,quality=85,format=auto/unsubscribe2.png)

Or obviously Substack. I read [Simon Willison's Newsletter](https://simonw.substack.com) sometimes. And obviously [Michael Burry's $379 Substack](/posts/michael-burrys-379-newsletter/). Those are solid options, but I had a clear picture in mind of what I wanted. I wanted only HTML, no tracking (also why I use [GoatCounter](https://www.goatcounter.com/) on my site and not Google Analytics), and full control of the creation and distribution chain from end to end. So I sat down and drew into my notebook, what I always do when I have an idea after a long walk or a hot shower.
![Hand-drawn notebook sketch of newsletter architecture showing markdown to HTML to distribution flow](https://static.philippdubach.com/cdn-cgi/image/width=1600,quality=85,format=auto/newsletter_scetch3.jpg)

I then went over to Illustrator (actually [Affinity Designer](https://affinity.serif.com/en-us/designer/), which I have been happily using since my Creative Cloud subscription ran out, sorry Adobe) and built a quick mockup of my drawing. I fed the mockup to Claude to generate pure HTML. After a few iterations it more or less looked like I wanted it to be.

The architecture: write the newsletter in Markdown (as I do for all of [my blog](/about)). Render it as HTML. Fetch OpenGraph images from my Cloudflare CDN at the lowest feasible resolution and pull descriptions automatically. Format links with preview cards. Keep some space for freetext at the top and bottom.
![Flowchart showing newsletter pipeline: Write Markdown, Render HTML, Host on R2, Fetch KV for subscribers, Send via Resend API](https://static.philippdubach.com/cdn-cgi/image/width=1600,quality=85,format=auto/newsletter_architecture2.png)

I built a [Python engine](https://github.com/philippdubach/newsletter-generator) that renders my `.md` files to email-safe HTML. The script handles several things automatically: (1) It fetches OpenGraph metadata for every link using Beautiful Soup, caching results to avoid repeated requests. (2) optimizes images using Cloudflare's image transformation service. For email, I use 240px width (2x the display size of 120px for retina displays). (3) It generates LinkedIn-style preview cards with images on the left and text on the right. The output is table-based HTML because email clients from 2003 still exist and they're apparently immortal.
![Screenshot of rendered newsletter showing article preview cards with images and descriptions](https://static.philippdubach.com/cdn-cgi/image/width=1600,quality=85,format=auto/Newsletter_Overview.png)

Originally I intended to manually copy-paste the HTML into an email and send it out since I did not expect many subscribers at first (or at all). But I had another challenge at hand: how do people sign up?

*Related: [The Tech behind this Site](https://philippdubach.com/posts/the-tech-behind-this-site/)*

Since I had already been using [Cloudflare Workers KV](https://developers.cloudflare.com/kv/) to build an API with historic values of my temperature and humidity sensor at home, I resorted to that. The API is simple. POST to `/api/subscribe` with an email address, and it gets stored in KV with a timestamp and some metadata.

After some Copilot iterations (I'm not a security guy, so not sure how I feel about handing all the security and testing to an agent, please reach out if you can help) the Worker includes rate limiting, honeypot fields for spam protection, proper CORS headers, and RFC-compliant email validation. 

I then wanted to get a confirmation email every time someone signed up. Since SMTP sending over my domain did not work reliably at first, I had to look for other options. Even though I wanted everything self-hosted, I ended up using the [Resend API](https://resend.com/). The API is straightforward:

```typescript
async function sendWelcomeEmail(subscriberEmail: string, env: Env) {
    const response = await fetch('https://api.resend.com/emails', {
        method: 'POST',
        headers: {
            'Authorization': `Bearer ${env.RESEND_API_KEY}`,
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({
            from: 'Philipp Dubach <noreply@notifications.philippdubach.com>',
            to: [subscriberEmail],
            subject: 'Welcome to the Newsletter',
            html: `<p>Thanks for subscribing!</p>`,
        }),
    });
    return response.ok;
}
```
<br>

After implementing this, I figured: why not send a confirmation to the subscriber and a copy to me? Why not use Resend for the whole distribution? (This is not a paid advertisement.) The HTML newsletter I generate goes straight into the email body. No images hosted elsewhere (except for the optimized preview thumbnails). No tracking pixels. No click tracking. The email is just HTML.

I also looked at [Mailgun](https://www.mailgun.com/) and [SendGrid](https://sendgrid.com/) before settling on Resend. Mailgun has better deliverability monitoring but a more complex API. SendGrid has more features but felt overengineered for what I needed. Resend's free tier and simple API won. If you have strong opinions on email APIs, I'm curious to hear them.

*Related: [The Bicycle Needs Riding to be Understood](https://philippdubach.com/posts/the-bicycle-needs-riding-to-be-understood/)*

The total cost of running this: zero. Cloudflare Workers has a generous free tier. Cloudflare R2 (where the HTML newsletters are hosted) has 10GB free storage. Resend gives 3,000 emails per month. The Python script runs locally or on my Azure instance.

You can find [my first newsletter here](https://static.philippdubach.com/newsletter/newsletter-2025-12.html). The full code for both the [newsletter generator](https://github.com/philippdubach/newsletter-generator) and the [subscriber API](https://github.com/philippdubach/newsletter-api) is on GitHub. 


---

## Frequently Asked Questions


### How do I build a no-tracking newsletter?

Build a Python script that renders Markdown to email-safe HTML, use Cloudflare Workers KV for subscriber storage, and send via Resend API. The email contains no tracking pixels and no click tracking. The HTML goes straight into the email body with only preview thumbnails hosted externally.


### What does a self-hosted newsletter cost?

Zero with free tiers. Cloudflare Workers has a generous free tier, Cloudflare R2 offers 10GB free storage, and Resend provides 3,000 emails per month free. The Python script runs locally or on any server you control.


### How do I convert Markdown to HTML email?

Use Python libraries like markdown2 and Beautiful Soup to render Markdown, fetch OpenGraph metadata for links, and generate table-based HTML (required for email client compatibility). Inline all CSS and optimize images for email (240px width for retina displays).


### How do I store newsletter subscribers without a database?

Cloudflare Workers KV provides key-value storage ideal for subscriber lists. POST to a /api/subscribe endpoint, validate the email address (RFC-compliant), apply rate limiting and honeypot spam protection, and store the email with timestamp in KV.



---

Canonical: https://philippdubach.com/posts/building-a-no-tracking-newsletter-from-markdown-to-distribution/
Content-Signal: search=yes, ai-input=yes, ai-train=yes
This file is the canonical machine-readable variant of https://philippdubach.com/posts/building-a-no-tracking-newsletter-from-markdown-to-distribution/. Author: Philipp D. Dubach (https://philippdubach.com/).
