I wrote a tool, [*Mailprint*](https://gnoack.github.io/mailprint/),
for printing out nice-looking emails from [Mutt](http://www.mutt.org/)
and other classic Mail user agents.

(You might remember from the [last article](https://blog.gnoack.org/post/lei/)
that I have recently spent some time to polish my e-mail setup for kernel development.
While this is not the hip thing to do any more,
some of those mails are difficult to understand,
and I occasionally *print them out*.)

This article describes *Mailprint*'s internals and design philosophy.
I'm fond of this approach, because it fits nicely into the UNIX environment and is reusable.

If you are interested in *using* *Mailprint*, you can find its homepage at https://gnoack.github.io/mailprint/.

## Design philosophy

Orthogonal software design, in the UNIX-style,
building one tool for each task, is great.
This applies in particular to side-projects,
because it is a way to achieve a lot more with less work.

Mailprint is a tool which could have existed in similar form in the 80s already.

It makes use of a pipeline of other tools to do its job:

  * GNU troff (`groff`) to format pages
  * ImageMagick's `convert` to convert profile pictures from various image formats

### Interface to the outside

**To the outside,**
Mailprint is designed to be used as part of a UNIX pipeline,
reading a plain text email and outputting PDF:

```
cat ~/.Mail/Inbox/cur/foobar123 | mailprint > out.pdf
```

```pikchr
fill = lightyellow
arrow "email" above
box "mailprint"
arrow "PDF" above
```

This makes it easy to hook up Mailprint to `mutt` and other classic MUAs.

### Internal design

> ⚠️ **This section is not up-to-date any more.**
>
> More recent versions of Mailprint generate PDF directly with a PDF generation library
> and do not start additional child processes.
{.warning}

Internally, Mailprint generates [groff source code in the "mom" dialect](http://www.schaffter.ca/mom/momdoc/toc.html) from the input email and feeds it through a UNIX pipeline of processing tools:

```pikchr
fill = bisque
linewid = 1.5*linewid

arrow "email" above
Parse: box "parse email"
arrow "headers," above "body" below
box "convert" "email" "to groff"
arrow "mom" above "(groff" below "source)" below

fill = lightblue
box "preconv"
arrow "mom" above "(sanitized" below "Unicode)" below
Groff: box "groff" "-mom -Tpdf"
arrow "PDF" above

line from Parse.n up "sender" above aligned "address" below aligned
arrow right 0.5*linewid
box "look up" "face" fill bisque
arrow "filename" above
box "ImageMagick" "convert"
arrow
File: file "PDF" "file" fill white
arrow right until even with Groff then down

$margin = 0.3cm
Mailprint: box with sw at Parse.sw-($margin,$margin) ht File.n.y-Parse.s.y+2*$margin wid Groff.e.x-Parse.w.x+2*$margin behind Parse fill lightyellow
text at 0.5*$margin left of Mailprint.ne "mailprint" rjust below
```

The steps implemented in Go are:

 * **Parsing emails** into email headers and bodies. ([code](https://github.com/gnoack/mailprint/blob/main/parse.go))
 * **Looking up the sender's profile picture:**
   This is also a separate Go library, [picon](https://github.com/gnoack/picon).
 * **Converting the email to groff source code**
   [of the "mom" flavor](http://www.schaffter.ca/mom/momdoc/toc.html),
   which is the heart of the program. ([code](https://github.com/gnoack/mailprint/blob/main/render.go))

The remaining steps are accomplished by invoking external UNIX programs:

 * **ImageMagick `convert`** converts the discovered profile pictures from various source formats into the PDF format.
 * **`preconv`** belongs to the groff suite and converts Unicode characters into input that GNU troff understands.
 * **``groff``** finally creates the PDF from the input source.

The pipeline is invoked from Mailprint's top-level `run()` function. ([code](https://github.com/gnoack/mailprint/blob/main/cmd/mailprint/main.go#L127))

### Turning it inside-out

If you want to build a more custom Mailprint pipeline, you can also make Mailprint expose its groff intermediary format using the `-output.format=mom` option, and chain it with groff yourself:

```
mailprint -face.picon=false -output.format=mom | preconv | groff -mom -Tpdf | lpr
```

```pikchr
fill = bisque
linewid = 1.5*linewid

arrow "email" above
Parse: box "parse email"
arrow "headers," above "body" below
Convert: box "convert" "email" "to groff"
arrow "mom" above "(groff" below "source)" below

fill = lightblue
box "preconv"
arrow "mom" above "(sanitized" below "Unicode)" below
box "groff" "-mom -Tpdf"
arrow "PDF" above

$margin = 0.3cm
Mailprint: box with sw at Parse.sw-($margin,$margin) ht Parse.height+3*$margin wid Convert.e.x-Parse.w.x+2*$margin behind Convert fill lightyellow
text at 0.5*$margin left of Mailprint.ne "mailprint -output.format=mom -face.picon=false" rjust below
```
