MAILING-LIST(7) Miscellaneous Information Manual MAILING-LIST(7) NAME Mailing List – a small-scale approach DESCRIPTION When I initially published some software I expected other people to use, I just asked that patches be mailed directly to me, but I figured that if more people were interested, it would be better to have a mailing list. Unfortunately email software, mailing list options in particular, are quite daunting. I wanted a light-weight option that would require me to host as little software as possible. My regular email is hosted by Fastmail, and I poked around its settings to see what I could do. It turns out Fastmail lets you configure address aliases to “also send to all contacts in” a contacts group. That's a mailing list! I created a group called “List” and an alias called list@causal.agency configured to deliver to that group. So it's really just an alias for my regular address that happens to also deliver to another group of people. It's easier to just configure and manage one mailing list, so what I do is ask patches and feedback to be sent to list+catgirl@causal.agency, for example. Fastmail treats any +suffix the same as the base address, but the full address can be used by subscribers to filter mail by topic if they wish. To subscribe someone to the list, I add their contact to the group. For a long time I was planning to write some software to manage these subscriptions. It should be possible to process subscription requests from IMAP and manipulate the contact group with CardDAV. When I went to start implementing this, however, I found CardDAV (and WebDAV in general) completely inscrutable. It's the kind of protocol that is split across like 20 different RFCs and you can't understand anything by just reading the one you actually care about. So I've given up on that and will keep manually subscribing people on request. The only thing missing, then, is a way for people to read mail sent to the list while they aren't subscribed. All the existing mailing list archive software I know of expects to have the mail locally, but I'd rather keep all my mail in IMAP. First, in order to make sure I keep a complete archive of the mailing list in IMAP, I added a small amount of Sieve code to my Fastmail filters configuration: if address :matches ["To", "Cc"] "list*@causal.agency" { fileinto :copy :flags "\\Seen" "INBOX.List"; } Sieve is a small standard language specifically for filtering mail. This bit of code matches anything sent to the list and adds a copy of it (the original is going into my inbox) to the “List” folder and marks the copy as read. With a pristine IMAP mailbox to export from, I wrote a new archive generator. It's called bubger(1) kirg (have it in a way). My goal was to render directly from IMAP and produce only static files as output, making it not only easy to serve, but also to run in one place and copy the files elsewhere. That's important to me because it has access to my email, so I'd rather run it on my local network and rsync(1) its output into The Cloud. The static files are in HTML, Atom and mboxrd formats. The architecture of bubger(1) is that for each piece of mail, identified by its UID in the mailbox, HTML and Atom fragments are exported along with the mboxrd. Those fragments are then stitched together using the IMAP SORT and THREAD extensions to make full pages and feeds for each thread. The fragments act as a cache for subsequent runs. I admit I did some pretty questionable things to achieve this. Namely, I wrote a small string templating engine in C. I use it to produce the HTML and XML for Atom, as well as to generate URLs and paths. I'm really happy with how it works, actually. This is also where I really started using one of my favourite C hacks: #define Q(...) #__VA_ARGS__ I quote all my HTML/XML templates with this and it's lovely. I've been working on bubger(1) on and off for almost a year now, and it's been interesting. I learned a lot about how email works from having to deal with all the ways a message can be. Thankfully a lot of that dealing is done by the IMAP server. As for running it, I initially just ran it with cron(8), and that's still a good way to go. To hook it up to rsync(1), pipe it like so: bubger -C list [...] | rsync -a --files-from=- list remote:list Later, I got a little annoyed with having to wait for the next run if I wanted to link to some mail I just received. I added an option to use IMAP IDLE to wait for new mail continuously and I started running it under my process supervisor, catsitd(8). The setup is a little more complex to feed the list of updated files to rsync(1). I added the catsit-watch(1) utility to run a command when a file changes, and in my catsit.conf(5) I have the following: bubger ~/.local/libexec/bubger rsync catsit-watch -i -f ~/list/UIDNEXT ~/.local/libexec/rsync The ~/.local/libexec/bubger script runs bubger(1), writing the list of updated paths to ~/list/FILES: exec bubger -i -C ~/list [...] >~/list/FILES And the ~/.local/libexec/rsync script gets run each time a bubger(1) update completes (UIDNEXT is always the last file written) and copies the listed files to the remote host: exec rsync -a --files-from=$HOME/list/FILES ~/list remote:list I haven't tagged any bubger(1) releases yet because it hasn't gotten a huge amount of testing, and I'm not sure anyone but me would even want to use it. But I'm happy with how it's working right now, so I might tag 1.0 soon just for fun. SEE ALSO https://causal.agency/list/ https://git.causal.agency/bubger/about https://git.causal.agency/catsit/about AUTHORS june BUGS Almost every time I try to type “mailing list” I instead type “mailist list”. Causal Agency March 4, 2021 Causal Agency