import express from "express"; import { SMTPServer } from "smtp-server"; import mailparser from "mailparser"; import React from "react"; import ReactDOMServer from "react-dom/server"; import xml2js from "xml2js"; import { promises as fs } from "fs"; import cryptoRandomString from "crypto-random-string"; export const webServer = express() .use(express.static("static")) .use(express.urlencoded({ extended: true })) .get("/", (req, res) => res.send( renderHTML(
) ) ) .post("/", (req, res, next) => { (async () => { const name = req.body.name; const identifier = createIdentifier(); await fs.writeFile( feedPath(identifier), renderXML(Feed({ name, identifier })) ); res.send( renderHTML(

“{name}” Inbox Created

) ); })().catch(next); }) .listen(8000); export const emailServer = new SMTPServer({ disabledCommands: ["AUTH", "STARTTLS"], onData(stream, session, callback) { (async () => { const email = await mailparser.simpleParser(stream); const { entry } = Entry({ title: email.subject, author: email.from.text, // FIXME: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/43234 / typeof email.html !== "boolean" => email.html !== false ...(typeof email.html === "boolean" ? { html: false, content: email.text ?? "" } : { content: email.html }) }); for (const { address } of session.envelope.rcptTo) { const match = address.match(/^(\w+)@kill-the-newsletter.com$/); if (match === null) continue; const identifier = match[1]; const path = feedPath(identifier); const xmlText = await fs.readFile(path, "utf8").catch(() => null); if (xmlText === null) continue; const xml = await new xml2js.Parser().parseStringPromise(xmlText); xml.feed.updated = now(); if (xml.feed.entry === undefined) xml.feed.entry = []; xml.feed.entry.unshift(entry); while (xml.feed.entry.length > 0 && renderXML(xml).length > 500_000) xml.feed.entry.pop(); await fs.writeFile(path, renderXML(xml)); } callback(); })().catch(error => { console.error(error); stream.resume(); callback(error); }); } }).listen(process.env.NODE_ENV === "production" ? 25 : 2525); function Layout({ children }: { children: React.ReactNode }) { return ( Kill the Newsletter!

Kill the Newsletter!

Convert email newsletters into Atom feeds

Convert email newsletters into Atom feeds

{children}
); } function Form() { return (

); } function Created({ identifier }: { identifier: string }) { return ( <>

Sign up for the newsletter with
{feedEmail(identifier)}

Subscribe to the Atom feed at
{feedURL(identifier)}

Don’t share these addresses.
They contain an identifier that other people could use
to send you spam and to control your newsletter subscriptions.

Enjoy your readings!

Create Another Inbox

); } function Feed({ name, identifier }: { name: string; identifier: string }) { return { feed: { $: { xmlns: "http://www.w3.org/2005/Atom" }, link: [ { $: { rel: "self", type: "application/atom+xml", href: feedURL(identifier) } }, { $: { rel: "alternate", type: "text/html", href: "https://www.kill-the-newsletter.com/" } } ], id: urn(identifier), title: name, subtitle: `Kill the Newsletter! Inbox: ${feedEmail( identifier )} → ${feedURL(identifier)}`, updated: now(), ...Entry({ title: `“${name}” Inbox Created`, author: "Kill the Newsletter!", content: ReactDOMServer.renderToStaticMarkup( ) }) } }; } function Entry({ title, author, content, html }: { title: string; author: string; content: string; html?: boolean; }) { return { entry: { id: urn(createIdentifier()), title, author: { name: author }, updated: now(), content: { ...(html === false ? {} : { $: { type: "html" } }), _: content } } }; } function createIdentifier(): string { return cryptoRandomString({ length: 20, characters: "1234567890qwertyuiopasdfghjklzxcvbnm" }); } function now(): string { return new Date().toISOString(); } function feedPath(identifier: string): string { return `static/feeds/${identifier}.xml`; } function feedURL(identifier: string): string { return `https://www.kill-the-newsletter.com/feeds/${identifier}.xml`; } function feedEmail(identifier: string): string { return `${identifier}@kill-the-newsletter.com`; } function urn(identifier: string): string { return `urn:kill-the-newsletter:${identifier}`; } function renderHTML(component: React.ReactElement): string { return `\n${ReactDOMServer.renderToStaticMarkup(component)}`; } function renderXML(xml: object): string { return new xml2js.Builder().buildObject(xml); }