Skip to main content

Using mail(1)

Intro

CONGRATULATIONS! Your OpenBSD install has been successfully completed!

When you login to your new system the first time, please read your mail
using the 'mail' command.
OpenBSD's post-install message

Oh, no! You have a fresh install and the only way to read your welcome message from Theo requires using mail. Or perhaps you've exited some long-running program and your shell informs you

You have mail in /var/mail/$USER

However, upon launching mail, most users find that the interface has all the friendliness of ed(1).

For decades, Unix-like systems have offered mail(1) as a way to read the system mailbox and send mail. While not as flashy or featureful as more modern MUAs like s-nail, Mutt, NeoMutt, Alpine, or Aerc (or even graphical MUAs like Claws Mail, Thunderbird, KMail, or Gnome Evolution), it provides a surprising bit of functionality hidden under the hood.

History

Over the years, a variety of CLI programs have called themselves some form of "mail", including mail, Mail, mailx, heirloom-mail, and s-nail, with subtle differences in each of them. For purposes of this post, I'll try to stick mostly to mail/mailx, as defined by POSIX which means

  • no IMAP/POP/SMTP support
  • no Maildir or MH support, only classic mbox format
  • no built-in spam filtering
  • no threading
  • no native support for MIME types other than text/plain (so no text/html)
that you might find in some iterations such as heirloom mailx. Specifically, all examples here should work in mail as provided by FreeBSD & OpenBSD base systems.

This post assumes some basic system configuration and will discuss the three essential modes of operation:

  1. send mode
  2. interactive mail reading mode, and
  3. mail composition mode
Additionally, this post will cover some common configuration settings as well as some tips & tricks.

System configuration expectations

By default, most Linux & BSD systems provide local mail receipt & delivery out of the box on a fresh install, whether with sendmail, exim, Postfix, or my favorite, OpenSMTPD.

To exchange mail with external systems, you'll have to configure your MTA as well as possibly modify your DNS to point your MX record at your server, set up SPF & DMARC and you might want some sort of spam-filtering solution in place. However, since MTA choice & configuration fall outside the scope of this post, use whatever you prefer as long as your MDA delivers to the user's system mbox (usually /var/mail/$USER or /var/spool/mail/$USER) and your MTA has compatibility with sendmail (usually done through a binary or link at /usr/sbin/sendmail).

Finally, mail assumes that it can lock mailboxes at the file-system level. On local file-systems, this doesn't generally pose a problem. However, if your system mbox resides on an NFS share with unreliable locking, you may encounter issues/conflicts if more than one process attempts to modify the mailbox.

Essential modes of operation

Send mode

The most simple of the modes, this expects the email message body on stdin, an optional subject passed with the -s option, CC & BCC fields with the -c & -b options, followed by the list of recipient(s).

echo "ask about surgery" |
  mail -s "Call Mom" $USER

calendar |
mail -s "Our calendar for $(date +"%Y-%m-%d")" \
me@example.net spouse@example.net

git log -10 |
mail -s "What I've done this week" \
-b owner@example.com \
-c boss@example.com \
coworker@example.com
Examples of using mail in command pipelines

Send mode works well in scripts to email output of commands, and often cron & at use mail to send the results of each task to the owner.

If mail reads from an interactive terminal, it affords tilde-escape operations for editing the message. When finished with the message, use an EOF with ctrl+d to send the message.

Mail reading mode

Starting mail

If you currently have mail in your system mailbox, POSIX shells should inform you (roughly) every $MAILCHECK seconds (default=600, or 10 minutes) if the last-modified time of the system mailbox (as specified by $MAIL) has changed. Launching mail with no arguments opens mail, displays a screenful of message headers, and leaves you at an "&" prompt.


You have mail in /var/mail/demo
mail
Mail version 8.1.2 01/15/2001.  Type ? for help.
"/var/mail/demo": 2 messages 2 new
>N  1 root@example.com  Thu Dec 15 01:30   29/931   example.com daily
 N  2 root@example.com  Fri Dec 23 01:30   32/963   example.com daily

Example of how mail greets you

Exiting mail

To quit back to your shell without modifying the mailbox, use the x/exit command.

x

quitting

If you use the q/quit command or type ctrl+d to send an EOF, it will change the message status of unread messages from N (new) to U (unread). By default, if you have read any of the messages in your system mailbox, they will get moved to ~/mbox upon quitting with q. The hold setting prevents mail from moving messages out of the system mailbox unless you explicitly (re)move them.

Listing messages

By default, when mail starts up, it shows you a screenful of message-summaries, one per line. Each message has an identifying number/index that should remain valid for the entire session. The header-listing consists of the following fields:

  • flags (New, Unread, Preserved, M send to mbox, and * saved)
  • message index
  • sender
  • date
  • size (in lines and bytes)
  • subject

If you have more than one screenful of messages, you can use the z command to list the next screenful, z- to list the previous screenful, and h to re-display the current screenful. When you first start mail, it displays the first page of message-summaries as if you had typed h.

To list message summaries matching particular criteria, you can use the f/from command with a message-list filter. Message-list filters can consist of a single message number,

f 5
 N  5 root@example.com  Thu Dec 15 01:30   29/931   example.com daily
show summary for message 5

multiple message numbers,

f 5 7 9
 N  5 root@example.com  Thu Dec 15 01:30   29/931   example.com daily
 N  7 root@example.com  Sat Jan  7 00:08   22/1191  Cron <upload@example.com>
 N  9 root@example.com  Sat Jan  7 00:17 13789/971549 Cron <upload@example.com>
show summary for messages 5, 7, and 9

or a range of numbers:

f 5-8
 N  5 root@example.com  Thu Dec 15 01:30   29/931   example.com daily
 N  7 root@example.com  Sat Jan  7 00:08   22/1191  Cron <upload@example.com>
 N  8 bob@example.edu   Sat Jan  7 00:08   32/1625  Comments on your thesis
show summary for messages 5–8

You can use a $ to indicate the last message in the mailbox:

f 7-$
 N  7 root@example.com  Sat Jan  7 00:08   22/1191  Cron <@example.com>
 N  8 bob@example.edu   Sat Jan  7 00:08   32/1625  Comments on your thesis
 N  9 root@example.com  Sat Jan  7 00:17 13789/971549 Cron <upload@example.com>
show summary for messages 7 through the last one

These can combine to list particular messages by number & range:

f 2 4-6 8-$
    2 root@example.com  Wed Dec 14 01:30   29/931   example.com daily
 U  4 tim               Wed Dec 14 21:11   16/597   Call Mom
 N  5 root@example.com  Thu Dec 15 01:30   29/931   example.com daily
 N  8 bob@example.edu   Sat Jan  7 00:08   32/1625  Comments on your thesis
 N  9 root@example.com  Sat Jan  7 00:17 13789/971549 Cron <upload@example.com>
show summary for messages 2, 4–6, and 8 through the last one

If you want to refer to all messages in the mailbox, you can use *

f *

show summary for every message in the mailbox

though this may scroll past if you have more than one screenful of messages.

Additionally, you can list messages by category of message

:d
deleted messages
:n
new messages
:o
old messages
:r
read messages
:u
unread messages
There's a subtle difference between "new" and "old" messages that might matter to some users, but that distinction has never mattered much to me.
f :r
    2 root@example.com  Wed Dec 14 01:30   29/931   example.com daily
show summary for messages that you have read

Finally, you can list messages by searching. If you use a regular string, mail will search the "From" header for mail from that recipient:

f bob@example.edu
 N  8 bob@example.edu   Sat Jan  7 00:08   32/1625  Comments on your thesis
show summary for messages from bob@example.edu

You can use partial matching as well:

f bob
 N  8 bob@example.edu   Sat Jan  7 00:08   32/1625  Comments on your thesis
show summary for messages from senders containing "bob"

If you prefix the search string with a /, it will search messages' Subject field:

f /thesis
 N  8 bob@example.edu   Sat Jan  7 00:08   32/1625  Comments on your thesis
show summary for messages where the subject contains "thesis"

Displaying messages

To display a message, use the p/print command. Without a message-list following the p/print command, mail will print the message headers and the body of the current message. If the length of the message exceeds the height of the screen (or the value of the crt setting, if set), the headers and body will get sent to your $PAGER.

p 8
Message 8:
From bob@example.edu Sat Jan  7 00:08:53 2023
Return-Path: <bob@example.edu>
X-Original-To: demo@example.net
Delivered-To: demo@example.net
Received: by example.net (Postfix, from userid 1234)
        id 533AC0FFEE; Sat,  7 Jan 2023 00:08:53 +0000 (UTC)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Message-Id: <20230125170817.533AC0FFEE@example.edu>
Date: Sat Jan  7 00:08:53 2023 -0600 (CST)
From: Professor Bob <bob@example.edu>
To: demo@example.net
Subject: Comments on your thesis

Wow, I've never seen such rubbish.

printing message #8

A lot of headers clutter that output, so I often use retain (or ignore) to winnow these to what I care about. I retain only those headers I want to see:

retain cc date subject
p 8
Message 8:
From bob@example.edu Sat Jan  7 00:08:53 2023
Date: Sat Jan  7 00:08:53 2023 -0600 (CST)
Subject: Comments on your thesis

Wow, I've never seen such rubbish.

setting retained headers

Much better. Well, except for the quality of the thesis.

After using retain, if you subsequently want to see all the headers, you can use the P/Print (with a capital "P") command.

Deleting (and undeleting) messages

The d/delete command deletes the current message, while u/undelete undeletes a deleted message. Both accept a list of message-IDs like the print command. You can undelete messages as long as you haven't closed the current mailbox file. If you accidentally delete a bunch of messages remember you can always use x to quit safely without writing any changes,

d 3 8 5
u /thesis
deleting & undeleting messages

Replying to messages

After reading a message, you can use the r/reply command to reply to all the recipients, or the R/Reply command to reply only to the author of the message. By default, mail doesn't include the original message, so you may want to use the ~m command to read the current message into the reply, prefixed with indentprefix.

The section on mail composition has more details on how to edit your reply.

Composing new messages

To compose a new message from within mail, use the m/mail command, specifying the recipient.

mail customerservice@example.com
Subject: broken frobniculator
I bought a new frobniculator last week
but it broke within the first 10 minutes
of use.

How can I obtain a replacement?

Thanks!
.
composing a new message

The section on mail composition has more details on how to compose your message.

Filing messages

Once you have read a message, rather than delete it, you may prefer to file it in a separate mbox archive so it doesn't clutter your main inbox. Use the s/save command to specify an mbox file. You can specify a message-list between the save command and the mbox filename to save multiple messages in one command.

Alternatively, if you would like to save a copy of the message, while keeping the original in the current mailbox, use the c/copy command which has the same syntax as save.

p /thesis

s thesis
"thesis" [New file]
copy 10 12 bills
"bills" [Appended]
filing messages in another folder

Changing mailboxes

Great. Now you can read mail from your system mailbox. And you can file mail in other mbox stores. How do you access those folders? You can either specify it on the shell command-line with -f or within mail using the file command. The folder setting allows you to prefix a folder-name with "+" to refer to mbox stores in that folder regardless of your current working-directory. Additionally, you can use "%" to refer to your system mailbox, "#" to refer to the previous folder you had open, and "&" to refer to your MBOX folder.

set folder="mail"
copy 8 +thesis
folders
bills   thesis
mail -f thesis

folders
bills   thesis
file +bills

file &

opening different mailboxes

Mail composition mode

When creating a new message, either via the shell

mail -s "My subj" user@example.com
or mail command-line
m user@example.com
Subject: My subject
by default mail lets you enter your message one line at time and terminate it with a period or EOF:
m bob@example.edu
Subject: thesis feedback
Thanks for your helpful feedback.
.

Tilde escapes

While convenient to type your message and send it, sometimes you want to edit the message in your $EDITOR, revise the list of recipients, change the Subject line, or include a message's contents for a point-by-point reply. By entering a message-body line beginning with a tilde followed by a command-letter, mail lets you make such modifications. And if you want to abandon the message, use the ~q command to save the draft in ~/dead.letter, or use the ~x to abandon the message completely. If you saved a draft, you can read it back in with ~d.

Using ~? will give you the full list of allowed tilde commands.

Use ~t recipient, ~c recipient, and ~b recipient to add recipients to the "To:", "CC:", and "BCC:" recipient-lists respectively. The ~s lets you edit your "Subject:" header. Or use ~h to edit each header in turn.

reply /thesis
~c dean@example.edu
Hey, Bob,
Copying the dean to get her input.

adding to the CC list

Most usefully, the ~e and ~v commands let you edit the current message in your $EDITOR and your $VISUAL editors respectively. When you return from your editor, you can continue to append to the message where you left off. To display the message as it currently stands, use the ~p tilde command.

As with other mailers, sometimes you want to include the content of other messages. The ~f message-list & ~m message-list commands let you read one or more messages from your current mailbox into the current message. If you omit the message-list, mail uses the current message. If you have set the indentprefix option (I recommend the internet standard "> "), the ~m will prefix each line with the corresponding value:

set indentprefix="> "
m bob@example.edu
Subject: thesis feedback
~m /thesis

Thanks for your helpful feedback.
.
as commonly used for replies. By default, only the retained headers get included. If you want all the headers, use the uppercase versions ~F & ~M instead.

Alternatively, you might want to read output from an external file (with ~r filename) or command (with ~! command)

m jsmith@example.net
Subject: having trouble with my Acme frobniculator interface
I had trouble loading the firmware.
Here's my dmesg output:
~! dmesg

and my config file:
~r /etc/acme-frobniculator

Could you point me in the right direction?

Thanks!
including files and command-output in a message

Finally, sometimes you want to pass your message through an external utility like rot13(6), b1ff(6), or a spell-check utility. The ~| command command lets you filter the message.

m unclejoe@example.mil
Subject: dirty joke

mud, mud, mud
~| rot13
(continue)
~p
-------
Message contains:
To: unclejoe@example.mil
Subject: joke

zhq, zhq, zhq
(continue)
including files and command-output in a message

Beware: if editing over ssh Both mail & ssh use tilde-escapes at the beginning of the line. To send a tilde over ssh you may have to hit ~ twice to send the tilde to mail.

Address book

Not having the best memory, I prefer to let my computer keep track of contacts. I want to type "john" as the recipient and let my mail program figure out who I mean. Do I want to forget any family members when sending out important family-wide updates? No way! The "alias" file contains tuples beginning with the keyword alias followed by the nickname you want to use, and one or more addresses to expand into.

cat ~/aliases
alias mom mom@example.net
alias dad dad@example.net
alias john john@example.com
alias joe unclejoe@example.mil
alias family john, joe, mom, dad
alias bob bob@example.edu
alias dean dean@example.edu
alias thesis bob, dean
a sample alias file

This lets me compose a message to "john" or "family" and have mail expand to the correct addresses.

mail family
Subject: Family crisis
Joe ate all the ice cream again!
.

using an alias

Settings

Your ~/.mailrc file can contain startup commands & settings to tweak the behavior of mail so this section covers the ones I find useful. I've included my ~/.mailrc below as an example.

As listed above, I set my retain to a very small subset of headers that I actually want to see, usually "Subject", "Date", and "CC".

crt
By default, mail determines the number of lines based on the display device and uses this to determine when to pass the output through $PAGER for display. Setting crt lets you override this. For instance, on one machine, I set this 120 lines and commonly read my 3–4 system messages by issuing p * to display all the messages. Any one message falls below that threshold, letting me see an individual message without paging. But I find it handy to read all of them together in $PAGER in one quick skim.
folder
By default, mail assumes all your mail-folders reside in your $HOME directory. If you use the folders command to list available mbox files, it may return lots of non-mbox files. Instead, if you store your mail in a subdirectory, you can set that here. Additionally, regardless of your current working-directory, you can prefix folder-names with a "+" to refer to folders in your folder directory
header
Each time you open a folder (whether at startup or using the file command), mail displays a screen-full of headers. If you don't want this behavior, set noheader.
hold
When quitting and leaving your system mailbox, by default mail will move every read-message into your MBOX store (~/mbox by default). By setting hold, mail will leave messages in your system mail-store until you manually move or delete them.
indentprefix
When replying to a message, common convention suggests that you prefix each quoted line with the string "> " The indentprefix setting controls this.
MBOX
This specifies your default "mbox" local mail store, defaulting to ~/mbox but I prefer to set this to a file within my ~/mail hierarchy
record
When sending a message, often times you want to keep that message around. The record setting lets you specify the folder in which to put a copy. Unfortunately, there's no folder-name for the current folder, but you can use something like +sent to keep them all in the same folder.
searchheaders
Normally when specifying a message-list by searching with /, mail will only search the Subject header. However, if you enable searchheaders, you can prefix your search term with a header-name to search. This lets you do things like f /To:dean@example.edu (to search for all messages sent to the dean) or f /Date:Jul (to search for messages sent in July). If you omit the header, it defaults to searching the Subject header as usual.

As promised, an excerpt of my ~/.mailrc

cat ~/.mailrc
# I prefer to keep my mbox files 
# in a mail/ subdirectory
# to keep from cluttering my
# home directory.
set folder=mail
set MBOX=+inbox
set record=+sent
set hold
set searchheaders
set indentprefix="> "
set crt=120
retain cc
retain date
retain subject
source ~/aliases
My ~/.mailrc file

Tips & tricks

Deleting all-but XYZ

I receive a number of messages from system-administration processes. Some come daily, some weekly, some monthly. Some have security advisories, while others give general system information. So I usually want to delete the non-security-related ones. However, the subject lines make this more challenging. The trick? Delete all of them, then undelete the security-related ones:

d root@myhost.example.com
u /insecurity
deleting all system messages, then undeleting security-related ones

I can then review these more-relevant messages without the noise of the other messages.

Quickly processing messages

On some machines, I want to read all the mail in my $PAGER pressing space to show a screen-full at a time and then delete all the messages unless something surprising shows up.

p *

d *
displaying all messages, then deleting them

Other times, I want to read each message and process as I go. After displaying a message, I can hit enter (with no command) to display the next message automatically. Alternatively, the dp command will delete the current message and print the next message in one go. It may only save one keypress, but I find it useful

1
Message 1:


Message 2:

d p
displaying sequential messages; deleting and displaying in one command