e452838d27
Instead, fetch alternates from within the feed on the demand. This makes alternates marginally more expensive to retrieve, but saves on storage (which we were running out on the DigitalOcean deployment), and is a cleaner architecture overall: no need to keep the feeds and alternates in sync. Here’s a script to migrate existing feeds: // Call me with, for example: env "BASE_URL=https://kill-the-newsletter.com" npx ts-node migrate.ts // I’m idempotent and reentrant, you may call me multiple times if necessary (for example, if the migration fails in the middle for whatever reason) import { promises as fs } from "fs"; import path from "path"; import { JSDOM } from "jsdom"; const BASE_URL = process.env.BASE_URL ?? "http://localhost:8000"; const FEEDS_PATH = "static/feeds"; (async () => { await fs.rmdir("static/alternate", { recursive: true }); for (const feedPath of (await fs.readdir(FEEDS_PATH)).filter((feedPath) => feedPath.endsWith(".xml") )) { const text = await fs.readFile(path.join(FEEDS_PATH, feedPath), "utf-8"); const feed = new JSDOM(text, { contentType: "text/xml" }); const document = feed.window.document; const feedIdentifier = document .querySelector("id")! .textContent!.split(":")[2]; for (const entry of document.querySelectorAll("entry")) { const entryIdentifier = entry .querySelector("id")! .textContent!.split(":")[2]; entry .querySelector(`link[rel="alternate"]`) ?.setAttribute( "href", `${BASE_URL}/alternate/${feedIdentifier}/${entryIdentifier}.html` ); } await fs.writeFile( path.join(FEEDS_PATH, feedPath), `<?xml version="1.0" encoding="utf-8"?>${feed.serialize()}`.trim() ); console.log(feedIdentifier); } })(); |
||
---|---|---|
.github/workflows | ||
.vscode | ||
static | ||
.dockerignore | ||
.gitignore | ||
.prettierignore | ||
CODE_OF_CONDUCT.md | ||
Caddyfile | ||
Dockerfile | ||
LICENSE | ||
README.md | ||
index.ts | ||
package-lock.json | ||
package.json | ||
test.ts | ||
tsconfig.json |
README.md
Kill the Newsletter!
Convert email newsletters into Atom feeds
Watch the Code Review!
Deploy Your Own Instance (Self-Host)
-
Create accounts on GitHub, Namecheap, and DigitalOcean.
-
Fork this repository.
-
Create a deployment SSH key pair:
$ ssh-keygen
Private key (
id_rsa
): Add to your fork under Settings > Secrets as a new secret calledSSH_PRIVATE_KEY
.Public key (
id_rsa.pub
): Add to your fork under Settings > Deploy keys and to your DigitalOcean account under Account > Security > SSH keys. -
Buy a domain on Namecheap.
-
Create a DigitalOcean droplet:
Image Ubuntu 18.04.3 (LTS) x64 Plan Starter Standard $5/mo Additional options Monitoring Authentication Your Deployment SSH Key Hostname <YOUR DOMAIN, FOR EXAMPLE, “kill-the-newsletter.com”>
Backups Enable -
Assign the new droplet a Firewall:
Name <YOUR DOMAIN, FOR EXAMPLE, “kill-the-newsletter.com”>
Inbound Rules ICMP SSH 22 Custom 25 (SMTP) HTTP 80 HTTPS 443 -
Assign the new droplet a Floating IP.
-
Configure the DNS in Namecheap:
Type Host Value A
@
<FLOATING IP>
CNAME
www
<YOUR DOMAIN, FOR EXAMPLE, “kill-the-newsletter.com”>
MX
@
<YOUR DOMAIN, FOR EXAMPLE, “kill-the-newsletter.com”>
-
Configure the deployment on
package.json
, particularly under the following keys:apps.env.BASE_URL
.apps.env.EMAIL_DOMAIN
.apps.env.ISSUE_REPORT
.deploy.production.host
.deploy.production.repo
.
-
Setup the server:
$ ssh-add $ npm run deploy:setup
-
Migrate the existing feeds (if any):
$ ssh-add $ ssh -A root@<YOUR DOMAIN, FOR EXAMPLE, “kill-the-newsletter.com”> root@<YOUR DOMAIN, FOR EXAMPLE, “kill-the-newsletter.com”> $ rsync -av <path-to-previous-feeds> /root/kill-the-newsletter.com/current/static/feeds/ root@<YOUR DOMAIN, FOR EXAMPLE, “kill-the-newsletter.com”> $ rsync -av <path-to-previous-alternate> /root/kill-the-newsletter.com/current/static/alternate/
-
Push to your fork, which will trigger the GitHub Action that deploys the code and starts the server.
Run Locally
Install Node.js and run:
$ npm install
$ npm run develop
The web server will be running at http://localhost:8000
and the email server at smtp://localhost:2525
.
Run Tests
Install Node.js and run:
$ npm install-test
Docker Support (Experimental)
Install Docker and run:
$ docker build -t kill-the-newsletter .
$ docker run kill-the-newsletter
The web server will be running at http://localhost:8000
and the email server at smtp://localhost:2525
.
For use in production, start with the example Dockerfile
.