Intro
I have spent a few hours today making sure that my emails get delivered to my offline inbox as soon as they appear on the server, and that this happens for each of my email accounts.
Tools
I assume that you already have your favorite offline email management tool (mu, notmuch etc.) set up and your emails are synced with something like mbsync (isync). So you've got the basics covered and are ready for the advanced stuff.
If you're not there yet, here are a couple of great articles to help you get started in different ways:
Mailboxes
For the sake of this tutorial let's assume that you have a .mbsyncrc
file with two mailboxes set up: work and private. This means that you can get all mail from the server using mbsync -a
or selectively sync just work or private with mbsync work
and mbsync private
.
At the moment you either start the sync manually, or more probably have something like a homebrew service for isync running and checking for emails every few minutes.
We're going to make this experience as seamless as it is on the smartphones with new emails appearing in your inbox as soon as they hit the server.
Setup
First let's install goimapnotify. It's not available on brew and will require golang to be installed so:
brew install golang
go install gitlab.com/shackra/goimapnotify@latest
- Add the target folder for go installs to
PATH
, normally its~/go/bin
, so add this to.zshrc
:export PATH=$HOME/go/bin:$PATH
Ok, the easy part is over. Now let's make config files.
Configuration
For some unknown reason goimapnotify
thinks that it's config files should have a .conf
extension, even though the content is simple json. We will not follow this strange pattern, and will create two json config files work.json
and private.json
.
Let's assume that your work email is on Gmail, and your private is on Protonmail (as it should be, preferably with a custom domain name, so that you could leave for another provider if you wish).
work.json
The simplest version of work.json
for gmail would look like this:
{
"host": "imap.gmail.com",
"port": 993,
"tls": true,
"username": "your.work.email@gmail.com`,
"password": "YOUR_PASSWORD",
"onNewMail": "mbsync work",
"onNewMailPost": "mu index --lazy-check",
"boxes": ["INBOX"],
"wait": 1
}
In this file we instruct goimapnotify
to connect to our work Gmail inbox and wait for new messages. As soon as a new message hits, we wait
1 second and then run the onNewMail
command mbsync work
. This does the normal run of downloading new messages.
Once this is done we run a onNewMailPost
command to index new emails.
If you want this to be more secure, you can substitute getting your password with a command like pass
or op
(1Password CLI):
{
"host": "imap.gmail.com",
"port": 993,
"tls": true,
"username": "your.work.email@gmail.com`,
"passwordCmd": "pass email/work", // [!code focus]
"onNewMail": "mbsync work",
"onNewMailPost": "mu index --lazy-check",
"boxes": ["INBOX"],
"wait": 1
}
Instead of password we provide a passwordCmd
to get the password from pass. We can also do the same for the username, substituting username
with usernameCmd
and even with the host
to hostCmd
. For the most paranoid.
private.json
With Protonmail you would need to set up a Protonmail Bridge, so the config would look like this:
{
"host": "127.0.0.1",
"port": 1143,
"tls": false,
"username": "your.private.email@proton.me",
"passwordCmd": "pass email/private",
"onNewMail": "mbsync private",
"onNewMailPost": "mu index --lazy-check",
"boxes": ["INBOX"],
"wait": 10
}
So, now you're ready to test it out. Put these two config files anywhere, for example into ~/.config/goimapnotify/
and test out your Work email integration with:
goimapnotify -conf ~/.config/goimapnotify/work.json -debug
The -debug
would make sure you get enough log output to understand that everything works.
Now all you need to do is send an email to your work email and watch the logs. If everything works fine, after the email appears in your Gmail inbox you should get a log message, and then mbsync
and the mu
will kick in, and email should be readily available in your offline email client of choice.
Launch as Startup
If everything works you're ready for the last step - make all of this launch at startup. On MacOS we need to use launchd
to run things at startup, and getting those pesky plist files is always a hassle. So in case you need to debug use something nice like LaunchControl, I used the free version. There is also a very neat online plist generator launched., I haven't used it but it can definitely come in handy.
For work create a file called com.example.workemail.plist
and put it to ~/Library/LaunchAgents
:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.example.workemail.plist</string>
<key>ProgramArguments</key>
<array>
<string>/Users/USERNAME/go/bin/goimapnotify</string>
<string>-conf</string>
<string>/Users/USERNAME/.config/goimapnotify/work.json</string>
</array>
<key>KeepAlive</key>
<true/>
<key>RunAtLoad</key>
<true />
<key>StandardErrorPath</key>
<string>/Users/USERNAME/Library/Logs/CheckMailBlaster.err</string>
<key>StandardOutPath</key>
<string>/Users/USERNAME/Library/Logs/CheckMailBlaster.log</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/homebrew/bin</string>
</dict>
</dict>
</plist>
Replace USERNAME
with your user name.
The most tricky part that took a good hour to debug was figuring out that you need to provide the PATH
variable to the running process, as it doesn't have access to the system wide one. Until I did, goimapnotify
would start and connect normally, and once the new email hit I got an error on the mbsync
step.
So, as I said this simple file took two hours of debugging, so say thank you and create similar file for private email like com.example.privateemail.plist
in the same directory.
Now all that's left is launch:
launchctl load ~/Library/LaunchAgents/com.example.workemail.plist
launchctl load ~/Library/LaunchAgents/com.example.privateemail.plist
Make sure that they're running with
launchctl list | grep com.example
Or better yet use LaunchControl
to monitor their status. If everything worked you should get the services started as soon as you log in, and the mail will appear in your inbox instantly.
Please note, that the sync will start only when a new email appears, so if your computer was off for the night and you turn it on, the syncing will not begin until the first new email that day. To avoid that you can create a similar plist that launches once and runs
mbsync -a
I leave that as an exercise for the reader.
Are you looking for a comments section? I would love to hear your feedback, but managing a comments section is a separate job. You can reach me on Mastodon or send me an email to public@tarka.dev