--- title: Writing Emails In Helix date: 2025-05-29 description: Tutorial on configuring Helix for email composition. --- 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](#aerc-integration) - [Naive Approach](#naive-approach) - [Helix Workspace](#helix-workspace) - [Markdown Highlighting](#markdown-highlighting) - [Spellcheck](#spellcheck) - [Formatting](#formatting) - [Conclusion](#conclusion) ## 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: ```toml [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`: ```bash #!/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`: ```ini [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: ```toml [[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](https://github.com/Automattic/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`: ```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`: ```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: - Any long sequence not broken by spaces. - Quoted lines (`>` prefixed email quotes). - Indented lines. - Markdown lists. - Markdown code blocks. - Signature blocks at end of file. - Sign-offs. Sign-offs are preserved by following a simple heuristic. A two-line sequence is considered to be a signoff if: - It starts with 1-5 words ending with a comma, followed by - 1-5 words that each start with capital letters. 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](https://git.sr.ht/~ficd/mail-utils) repository on sourcehut. To add it to your mail workspace just requires changing one line in `languages.toml`: ```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!