Writing Emails In Helix

This article is all about writing emails in Helix. Obviously, Helix isn’t an email client – and it’s lacking in a plugin system, so you can’t really turn it into one, either. And that’s okay! You might be wondering, then, what exactly do I mean by “writing emails in Helix”?

An email client needs to do a whole lot more than write text! It needs to connect to your mail servers, show your messages, display and allow you to interact with flags/labels, be able to send your email, etc. Only an insane person would try to hamfist this functionality into a text editor (I’m looking at you, Neovim plugin developers!).

Helix is really good at its one job, and that’s editing text. It is a text editor, after all. As it turns out, writing emails is, well, text editing, which is Helix’s forte. So why not take Helix’s amazing writing experience, and apply it to email? If you’re willing to take the plunge, you may be pleasantly surprised at how joyful writing email can actually be!

There are a few different ways to approach composing emails in Helix. This post covers what worked best for me. That is, using Helix as a composer for the aerc email client. It wasn’t straightforward to figure out at first, but all-in-all, the configuration isn’t actually that complicated, so you should be able to set it up in no time.

Aerc Integration

At the end of the day, an email is just a text file. An .eml file, to be exact. So, you could just open up a fresh buffer, manually type in your headers, and go to town. When you’re done, you can figure out some way to actually send that file (perhaps dragging and dropping it into your mail client).

However, that’s not really ideal. For starters, emails have a lot of headers, and if you mess any of them up, your message might not get delivered. You probably want to be able to, at the very least, reply to an email and have its headers be auto-filled.

Therefore, some level of integration with an actual email client is desirable. Let the client handle every other aspect of reading and sending email, and relegate writing to Helix. This is the approach I’ll be covering.

Naive Approach

The simplest approach should work with zero configuration. aerc uses your $EDITOR to compose your emails by default; so you can get started using Helix right away! However, this is missing two important features: spellchecking and formatting. To that end, we have to figure out how to get Helix to load a custom configuration for emails.

Helix Workspace

As you may know, Helix gives you an option to specify per-project configuration. This requires creating a .helix directory in the root of your project, and filling it with config.toml and languages.toml files that override your default config. However, there isn’t currently a way to specify filetype-specific options; this is where we have to get creative.

I recommend creating a folder inside your aerc config called helix-config. Its name doesn’t actually matter, so you can call it whatever you want! This directory will serve as our workspace. The basic idea is to have aerc open email files with this directory set as the current working directory — which is what induces Helix to load the workspace config.

Inside our new workspace, let’s create the .helix directory and populate config.toml with our preferences:

[editor]
text-width = 74
[editor.soft-wrap]
enable = true
wrap-at-text-width = true

The final step is the simplest. First, create a shell script inside helix-config:

#!/bin/sh

cd ~/.config/aerc/helix-config && hx "$@"

This will serve as our “email entry point”. It simply sets the CWD to our new workspace, then loads Helix normally. Then, you’ll need to set the editor option in aerc.conf:

[compose]
editor=/home/yourUserName/.config/aerc/helix-config/hx.sh

Unfortunately, you have to provide an absolute path, otherwise aerc won’t find the script.

Markdown Highlighting

When writing plain-text emails, I like to use Markdown markup. Consider the following message:

Dear John,

Thanks for sharing! I _seriously_ appreciate your help.

To proceed, we'll need to do:

- Thing A
- Thing B

Even though the recipient will receive this email in un-rendered plain text format, the semantics are still very clear. Of course, we don’t need highlighting in our editor to write markdown, but it can help make the experience a bit better.

All you need to do is create a .helix/languages.toml file inside our email workspace from before:

[[language]]
name = "markdown"
file-types = [{ glob = "/tmp/aerc-compose-*.eml" }]

Take note of the file-types entry. Email compose files opened by aerc will always match the /tmp/aerc-compose-*.eml pattern. The above snippet ensures that emails will be treated as Markdown files by Helix. The reason I recommend putting this in your workspace config and not setting it globally is to avoid complications if you ever find yourself needing to edit a regular .eml file outside of aerc.

Spellcheck

The excellent harper LSP supports Markdown out-of-the-box. You can follow the instructions in its documentation to install and configure it for Helix. Then, you can add it to your workspace languages.toml:

language-servers = ["harper-ls"]

Formatting

Many plain-text email clients don’t automatically wrap lines for readers. A soft-wrapped email may look completely fine on your end, but be totally unreadable for your recipient. Therefore, we need a way to hard-wrap our emails before sending them. This ended up being a much bigger challenge than I thought it would.

My first approach was to use the nifty wrap filter that aerc ships with by adding it to languages.toml:

formatter = { command = "/usr/lib/aerc/filters/wrap", args = ["-w", "74"] }

This worked okay, but it totally broke the markup at times. Crucially, it doesn’t respect code blocks or verbatim sections denoted by backticks. If you’re participating in technical mailing lists, you definitely want these to be preserved.

Using a regular Markdown formatter introduced another problem: Markup was preserved, but not in an email-friendly way. Consider this example:

How's it going?

Best,
Daniel

A standard Markdown formatter ignores single line breaks and produces this output:

How's it going?

Best, Daniel

It similarly mangles signature blocks:

--
Daniel
Programmer
email@address.com

Becomes:

-- Daniel Programmer email@address.com

Eventually, I realized that I’d have to write my own formatter. I wrote a Python script that hard-wraps the input, automatically reflows paragraphs, and squashes consecutive paragraph breaks — while preserving the following:

Sign-offs are preserved by following a simple heuristic. A two-line sequence is considered to be a signoff if:

This covers signoffs like these:

Best,
Daniel

Yours truly,
John Alfred Smith

I provide a number of flags and options, so you can configure the formatter to your liking. If you want to use it, format.py is available in my mail-utils repository on sourcehut. To add it to your mail workspace just requires changing one line in languages.toml:

formatter = { command = "/path/to/format.py" }
auto-format = true

Conclusion

Hopefully, this guide was helpful in getting you set up with composing your emails in Helix. A similar guide for Kakoune is in the works, so check back soon!