···11+---
22+template:
33+slug: building-forlater
44+title: How I built forlater.email
55+subtitle: A technical breakdown of my first big side-project
66+date: 2021-09-25
77+---
88+99+Ever since I began browsing sites like Hacker News and Lobsters, coming
1010+across new and exciting links to check out every day, I found it hard to
1111+keep up. On most days, I just didn't. And that's fine -- [good,
1212+even](/blog/dont-news). But oftentimes, I'd come across a genuinely
1313+interesting link but no time to actually read it.
1414+1515+I began using Pocket. It was alright -- the article view was very good;
1616+but it stopped there. I didn't like nor use the other junk baked into
1717+the app: discover, following/friends thing, etc. It's also proprietary,
1818+and that irked me -- more so than the other "features".
1919+2020+Thus, somewhat inspired by rss2email, I began building
2121+[forlater.email](https://forlater.email) -- a bookmarking/read-later
2222+service that works via email. Email is the perfect tool for this
2323+use-case: works offline; you can organize it however you like; you own
2424+your data.
2525+2626+
2727+2828+Pictured above is how forlater works. Each component is explained below.
2929+3030+## OpenSMTPD
3131+3232+Mail containing links to be saved arrive here. OpenSMTPD is beautiful
3333+software, and its configuration is stupid simple
3434+([smtpd.conf(5)](https://man.openbsd.org/smtpd.conf):
3535+3636+```conf
3737+table blocklist file:/etc/smtpd/blocklist
3838+3939+action webhook mda "/home/icy/forlater/mdawh/mdawh"
4040+match mail-from <blocklist> for any reject
4141+match from any for rcpt-to "save@forlater.email" action webhook
4242+```
4343+4444+The `filter` and `listen` directives have been snipped for brevity. The
4545+rest, in essence, simply sends all mail to `save@forlater.email` to an
4646+MDA program, via stdin. Any mail from an address in the blocklist file
4747+get rejected.
4848+4949+[rspamd](https://rspamd.com) is used to prevent spam.
5050+5151+## mdawh
5252+5353+[mdawh](https://git.icyphox.sh/forlater/mdawh), or the MDA webhook tool.
5454+A small Go program that processes mail coming from stdin and generates a
5555+JSON payload that looks like so:
5656+5757+```json
5858+{
5959+ "from": "foo@bar.com",
6060+ "date": "Fri, 1 Jan 2010 00:00:00 UTC",
6161+ "replyto": "...",
6262+ "body": "...",
6363+ "parts": {
6464+ "text/plain": "...",
6565+ "text/html": "...",
6666+ }
6767+}
6868+```
6969+7070+This is POSTed to a configured HTTP endpoint -- which in this case, is
7171+navani.
7272+7373+## navani
7474+7575+[navani](https://git.icyphox.sh/forlater/navani) is forlater's primary
7676+mail processing service[^1]. Listens for webhooks from mdawh, processes
7777+them, and sends mail using a configured SMTP server. URLs are cached in
7878+Redis along with the HTML content.
7979+8080+For the readable HTML,
8181+[go-readability](https://github.com/go-shiori/go-readability) is used;
8282+the output of which is rendered into a minimal [HTML email
8383+template](https://git.icyphox.sh/forlater/navani/tree/templates/html.tpl)
8484+-- something that I never want to write again.
8585+8686+The plaintext part is currently generated using `lynx -image_links -dump
8787+-stdin`. The `-image_links` flag is handy because it generates footnote
8888+links for images as well, instead of simply ignoring images altogether.
8989+I plan to rewrite this; possibly using a blend of HTML-to-plaintext
9090+libraries and handwritten rules.
9191+9292+## future improvements
9393+9494+I plan to implement some kind of `settings@` address to configure and
9595+store user settings (dark theme? fonts?). However, this introduces state
9696+in an otherwise mostly stateless system.
9797+9898+The other thing I've been thinking of is making your own newsletter of
9999+sorts. For example: save a bunch of links during the week, and have them
100100+all delivered over the weekend.
101101+102102+Neither of these "features" are confirmed to happen, primarily because
103103+forlater is feature-complete for my use. That said, I'm happy to
104104+consider any improvements or suggestions that you might have -- please
105105+[shoot them my way](mailto:x@icyphox.sh).
106106+107107+Finally, thanks to everyone who tossed a few bucks my way -- mighty kind
108108+of you.
109109+110110+[^1]: Named after [Navani Kholin](https://coppermind.net/wiki/Navani_Kholin).