all repos — site @ f27e52f97df1680b1cc9cf3e7899b1a0ee0e7127

source for my site, found at icyphox.sh

pages/blog/building-forlater.md (view raw)

  1---
  2template:
  3slug: building-forlater
  4title: How I built forlater.email
  5subtitle: A technical breakdown of my first big side-project
  6date: 2021-09-25
  7---
  8
  9Ever since I began browsing sites like Hacker News and Lobsters, coming
 10across new and exciting links to check out every day, I found it hard to
 11keep up. On most days, I just didn't. And that's fine -- [good,
 12even](/blog/dont-news). But oftentimes, I'd come across a genuinely
 13interesting link but no time to actually read it.
 14
 15I began using Pocket. It was alright -- the article view was very good;
 16but it stopped there. I didn't like nor use the other junk baked into
 17the app: discover, following/friends thing, etc. It's also proprietary,
 18and that irked me -- more so than the other "features".
 19
 20Thus, somewhat inspired by rss2email, I began building
 21[forlater.email](https://forlater.email) -- a bookmarking/read-later
 22service that works via email. Email is the perfect tool for this
 23use-case: works offline; you can organize it however you like; you own
 24your data.
 25
 26![forlater arch](https://cdn.icyphox.sh/JNAn4.png)
 27
 28Pictured above is how forlater works. Each component is explained below.
 29
 30## OpenSMTPD
 31
 32Mail containing links to be saved arrive here. OpenSMTPD is beautiful
 33software, and its configuration is stupid simple
 34([smtpd.conf(5)](https://man.openbsd.org/smtpd.conf)):
 35
 36```conf
 37table blocklist file:/etc/smtpd/blocklist
 38
 39action webhook mda "/home/icy/forlater/mdawh/mdawh"
 40match mail-from <blocklist> for any reject
 41match from any for rcpt-to "save@forlater.email" action webhook
 42```
 43
 44The `filter` and `listen` directives have been snipped for brevity. The
 45rest, in essence, simply sends all mail to `save@forlater.email` to an
 46MDA program, via stdin. Any mail from an address in the blocklist file
 47get rejected.
 48
 49[rspamd](https://rspamd.com) is used to prevent spam.
 50
 51## mdawh
 52
 53[mdawh](https://git.icyphox.sh/forlater/mdawh), or the MDA webhook tool.
 54A small Go program that processes mail coming from stdin and generates a
 55JSON payload that looks like so:
 56
 57```json
 58{
 59  "from": "foo@bar.com",
 60  "date": "Fri, 1 Jan 2010 00:00:00 UTC",
 61  "replyto": "...",
 62  "body": "...",
 63  "parts": {
 64    "text/plain": "...",
 65    "text/html": "...",
 66  }
 67}
 68```
 69
 70This is POSTed to a configured HTTP endpoint -- which in this case, is
 71navani.
 72
 73## navani
 74
 75[navani](https://git.icyphox.sh/forlater/navani) is forlater's primary
 76mail processing service[^1]. Listens for webhooks from mdawh, processes
 77them, and sends mail using a configured SMTP server. URLs are cached in
 78Redis along with the HTML content.
 79
 80For the readable HTML,
 81[go-readability](https://github.com/go-shiori/go-readability) is used;
 82the output of which is rendered into a minimal [HTML email
 83template](https://git.icyphox.sh/forlater/navani/tree/templates/html.tpl)
 84-- something that I never want to write again.
 85
 86The plaintext part is currently generated using `lynx -image_links -dump
 87-stdin`. The `-image_links` flag is handy because it generates footnote
 88links for images as well, instead of simply ignoring images altogether.
 89I plan to rewrite this; possibly using a blend of HTML-to-plaintext
 90libraries and handwritten rules.
 91
 92## future improvements
 93
 94I plan to implement some kind of `settings@` address to configure and
 95store user settings (dark theme? fonts?). However, this introduces state
 96in an otherwise mostly stateless system.
 97
 98The other thing I've been thinking of is making your own newsletter of
 99sorts. For example: save a bunch of links during the week, and have them
100all delivered over the weekend.
101
102Neither of these "features" are confirmed to happen, primarily because
103forlater is feature-complete for my use. That said, I'm happy to
104consider any improvements or suggestions that you might have -- please
105[email me](mailto:x@icyphox.sh).
106
107Finally, thanks to everyone who tossed a few bucks my way -- mighty kind
108of you.
109
110[^1]: Named after [Navani Kholin](https://coppermind.net/wiki/Navani_Kholin).