all repos — site @ f676557921c8bf90c1e73921fbee6d8e871c2f8e

source for my site, found at icyphox.sh

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
---
template:
slug: building-forlater
title: How I built forlater.email
subtitle: A technical breakdown of my first big side-project
date: 2021-09-25
---

Ever since I began browsing sites like Hacker News and Lobsters, coming
across new and exciting links to check out every day, I found it hard to
keep up. On most days, I just didn't. And that's fine -- [good,
even](/blog/dont-news). But oftentimes, I'd come across a genuinely
interesting link but no time to actually read it.

I began using Pocket. It was alright -- the article view was very good;
but it stopped there. I didn't like nor use the other junk baked into
the app: discover, following/friends thing, etc. It's also proprietary,
and that irked me -- more so than the other "features".

Thus, somewhat inspired by rss2email, I began building
[forlater.email](https://forlater.email) -- a bookmarking/read-later
service that works via email. Email is the perfect tool for this
use-case: works offline; you can organize it however you like; you own
your data.

![forlater arch](https://x.icyphox.sh/JNAn4.png)

Pictured above is how forlater works. Each component is explained below.

## OpenSMTPD

Mail containing links to be saved arrive here. OpenSMTPD is beautiful
software, and its configuration is stupid simple
([smtpd.conf(5)](https://man.openbsd.org/smtpd.conf):

```conf
table blocklist file:/etc/smtpd/blocklist

action webhook mda "/home/icy/forlater/mdawh/mdawh"
match mail-from <blocklist> for any reject
match from any for rcpt-to "save@forlater.email" action webhook
```

The `filter` and `listen` directives have been snipped for brevity. The
rest, in essence, simply sends all mail to `save@forlater.email` to an
MDA program, via stdin. Any mail from an address in the blocklist file
get rejected.

[rspamd](https://rspamd.com) is used to prevent spam.

## mdawh

[mdawh](https://git.icyphox.sh/forlater/mdawh), or the MDA webhook tool.
A small Go program that processes mail coming from stdin and generates a
JSON payload that looks like so:

```json
{
  "from": "foo@bar.com",
  "date": "Fri, 1 Jan 2010 00:00:00 UTC",
  "replyto": "...",
  "body": "...",
  "parts": {
    "text/plain": "...",
    "text/html": "...",
  }
}
```

This is POSTed to a configured HTTP endpoint -- which in this case, is
navani.

## navani

[navani](https://git.icyphox.sh/forlater/navani) is forlater's primary
mail processing service[^1]. Listens for webhooks from mdawh, processes
them, and sends mail using a configured SMTP server. URLs are cached in
Redis along with the HTML content.

For the readable HTML,
[go-readability](https://github.com/go-shiori/go-readability) is used;
the output of which is rendered into a minimal [HTML email
template](https://git.icyphox.sh/forlater/navani/tree/templates/html.tpl)
-- something that I never want to write again.

The plaintext part is currently generated using `lynx -image_links -dump
-stdin`. The `-image_links` flag is handy because it generates footnote
links for images as well, instead of simply ignoring images altogether.
I plan to rewrite this; possibly using a blend of HTML-to-plaintext
libraries and handwritten rules.

## future improvements

I plan to implement some kind of `settings@` address to configure and
store user settings (dark theme? fonts?). However, this introduces state
in an otherwise mostly stateless system.

The other thing I've been thinking of is making your own newsletter of
sorts. For example: save a bunch of links during the week, and have them
all delivered over the weekend.

Neither of these "features" are confirmed to happen, primarily because
forlater is feature-complete for my use. That said, I'm happy to
consider any improvements or suggestions that you might have -- please
[email me](mailto:x@icyphox.sh).

Finally, thanks to everyone who tossed a few bucks my way -- mighty kind
of you.

[^1]: Named after [Navani Kholin](https://coppermind.net/wiki/Navani_Kholin).