diff --git a/package-lock.json b/package-lock.json index a4236d0..178a41d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -887,6 +887,15 @@ } } }, + "@types/nodemailer": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.0.tgz", + "integrity": "sha512-KY7bFWB0MahRZvVW4CuW83qcCDny59pJJ0MQ5ifvfcjNwPlIT0vW4uARO4u1gtkYnWdhSvURegecY/tzcukJcA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/prop-types": { "version": "15.7.3", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", @@ -928,6 +937,16 @@ "@types/mime": "*" } }, + "@types/smtp-server": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@types/smtp-server/-/smtp-server-3.5.3.tgz", + "integrity": "sha512-15g2q1C2F3iiVOQlEjChndNb9wAZZiNBLIO0Xg4+JGc9FvdiD1p6DaLVkbU1I8eHd0g624xz0ateamo3nY0ENw==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/nodemailer": "*" + } + }, "@types/stack-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", @@ -1320,6 +1339,11 @@ } } }, + "base32.js": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/base32.js/-/base32.js-0.1.0.tgz", + "integrity": "sha1-tYLexpPC8R6JPPBk7mrFthMaIgI=" + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -2866,6 +2890,11 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, + "ipv6-normalize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ipv6-normalize/-/ipv6-normalize-1.0.1.tgz", + "integrity": "sha1-GzJYKQ02X6gyOeiZB93kWS52IKg=" + }, "is-accessor-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", @@ -4924,6 +4953,11 @@ } } }, + "nodemailer": { + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.4.5.tgz", + "integrity": "sha512-NH7aNVQyZLAvGr2+EOto7znvz+qJ02Cb/xpou98ApUt5tEAUSVUxhvHvgV/8I5dhjKTYqUw0nasoKzLNBJKrDQ==" + }, "nodemon": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.2.tgz", @@ -6051,6 +6085,16 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, + "smtp-server": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/smtp-server/-/smtp-server-3.6.0.tgz", + "integrity": "sha512-DVEVWzL4s1GWzAs4+6rbhNZpAn61+V8l4b7R8zHLAW2jmlwKz9IKQmdgm5sNruCRnS01BYyitI98vJo7LDnXfg==", + "requires": { + "base32.js": "0.1.0", + "ipv6-normalize": "1.0.1", + "nodemailer": "6.4.5" + } + }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", diff --git a/package.json b/package.json index 7ebecc8..12b7a30 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "express": "^4.17.1", "react": "^16.13.0", "react-dom": "^16.13.0", + "smtp-server": "^3.6.0", "xml2js": "^0.4.23" }, "devDependencies": { @@ -17,6 +18,7 @@ "@types/node-fetch": "^2.5.5", "@types/react": "^16.9.23", "@types/react-dom": "^16.9.5", + "@types/smtp-server": "^3.5.3", "@types/xml2js": "^0.4.5", "concurrently": "^5.1.0", "jest": "^25.1.0", diff --git a/src/index.tsx b/src/index.tsx index 275a9ca..954aabf 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,5 +1,6 @@ import express from "express"; import { Server } from "http"; +import { SMTPServer } from "smtp-server"; import React from "react"; import ReactDOMServer from "react-dom/server"; import { Builder } from "xml2js"; @@ -11,7 +12,7 @@ const webApp = express() .use(express.urlencoded({ extended: true })) .get("/", (req, res) => res.send( - renderHtml( + renderHTML(
@@ -20,9 +21,9 @@ const webApp = express() ) .post("/", (req, res) => { const inbox: Inbox = { name: req.body.name, token: newToken() }; - fs.writeFileSync(feedPath(inbox.token), renderXml(Feed(inbox))); + fs.writeFileSync(feedPath(inbox.token), renderXML(Feed(inbox))); res.send( - renderHtml( + renderHTML( @@ -30,6 +31,8 @@ const webApp = express() ); }); +export const emailServer = new SMTPServer(); + export let developmentWebServer: Server; if (process.env.NODE_ENV === "production") { @@ -48,8 +51,10 @@ if (process.env.NODE_ENV === "production") { .use(webApp); productionWebApp.listen(80); productionWebApp.listen(443); + emailServer.listen(25); } else { developmentWebServer = webApp.listen(8000); + emailServer.listen(2525); } type Inbox = { @@ -144,7 +149,7 @@ function Created({ inbox: { name, token } }: { inbox: Inbox }) {

Subscribe to the Atom feed at
- {feedUrl(token)} + {feedURL(token)}

Don’t share these addresses. @@ -176,7 +181,7 @@ function Feed(inbox: Inbox) { $: { rel: "self", type: "application/atom+xml", - href: feedUrl(token) + href: feedURL(token) } }, { @@ -237,7 +242,7 @@ export function feedPath(token: string) { return `static/feeds/${token}.xml`; } -function feedUrl(token: string) { +function feedURL(token: string) { return `https://www.kill-the-newsletter.com/feeds/${token}.xml`; } @@ -249,10 +254,10 @@ function id(token: string) { return `urn:kill-the-newsletter:${token}`; } -function renderHtml(component: React.ReactElement): string { +function renderHTML(component: React.ReactElement): string { return `\n${ReactDOMServer.renderToStaticMarkup(component)}`; } -function renderXml(xml: object): string { +function renderXML(xml: object): string { return new Builder().buildObject(xml); } diff --git a/src/test.ts b/src/test.ts index 2095ee5..b99982e 100644 --- a/src/test.ts +++ b/src/test.ts @@ -1,4 +1,4 @@ -import { developmentWebServer, feedPath } from "."; +import { developmentWebServer, emailServer, feedPath } from "."; import fetch from "node-fetch"; import fs from "fs"; @@ -16,4 +16,5 @@ test("create feed", async () => { afterAll(() => { developmentWebServer.close(); + emailServer.close(() => {}); });