Mail: sending attachments

From Mailutils
Jump to navigationJump to search

This article describes how to create attachments using the mail utility. It makes special emphasis on using mail from scripts.

Attaching files

The simplest way to attach a file from command line is by using the --attach (-A) option. Its argument specifies the file to attach. For example, the following will attach the content of the file archive.tar:

 $ mail --attach=archive.tar

By default, the content type will be set to application/octet-stream, and the attachment will be encoded using the base64 encoding. To change the content type, use the --content-type option. For example, to send an HTML attachment:

 $ mail --content-type=text/html --attach=in.html

The --content-type option affects all --attach options that follow it. To change the content type, simply add another --content-type option. For example, to send both the HTML file and the archive:

 $ mail --content-type=text/html --attach=in.html \
        --content-type=application/x-tar --attach=archive.tar

Similarly, the encoding to use is set up by the --encoding option. As well as --content-type, this option affects all attachments supplied after it in the command line, until changed by the eventual next appearance of the same option. Extending the above example:

 $ mail --content-type=text/html --encoding=quoted-printable \
        --attach=in.html \
        --content-type=application/x-tar --encoding=base64 \

Each attachment can also be assigned a description and a file name. Normally, these are the same as the file name supplied with the --attach option. However, you can change either or both of them using the --content-name and --content-filename, correspondingly. Both of these options affect only the next --attach (or --attach-fd, see below) option.

All the examples above will enter the usual interactive shell, allowing you to compose the body of the message. If that's not needed, the non-interactive use can be forced by redirecting /dev/null to the standard input, e.g.:

 $ mail --attach=archive.tar < /dev/null

This will normally produce a message saying:

 mail: Null message body; hope that's ok

To suppress this message, unset the nullbodymsg variable, as shown in the example below:

 $ mail -E 'set nonullbodymsg' --attach=archive.tar < /dev/null

The option --attach=- forces mail to read the file to be attached from the standard input stream. This option disables the interactive mode and sets nonullbodymsg implicitly, so that the above example can be rewritten as:

 $ mail --attach=- < archive.tar

Reading attachment from file descriptor

Special option is provided to facilitate the use of mail in scripts. The --attach-fd=N instructs the program to read the data to be attached from the file descriptor N. The above example is equivalent to:

 $ mail --attach-fd=0 < archive.tar

Attachments created with this option have neither filename nor description set, so normally the use of --content-name and/or --content-filename is advised.

The following Perl program serves as an example of using mail from a script to construct a MIME message on the fly. It scans all mounted file systems for executable files that have setuid or setgid bits set and reports the names of those files in separate attachments. Each attachment is named after the mountpoint it describes.

The script begins with the usual prologue stating the modules that will be used:

  use strict;
  use autodie;

Then global variables are declared. The @rcpt array contains the email addresses of the recipients:

  my @rcpt= '';

The @cmd variable holds the mail command line. It will be augmented for each file system. The initial value is set as follows:

  my @cmd = ('mail',
             '-E set nonullbodymsg',

The find(1) utility will be used to locate the files. The script will start as many instances as there are mountpoints. Those instances will be run in parallel and their standard output streams will be connected to file descriptors passed to mail invocation in --attach-fd options.

The descriptors will be held in @fds array. This will prevent them from being wiped out by the garbage collector. Furthermore, care should be taken to ensure that the O_CLOEXEC flag be not set for these descriptors. This sample script takes a simplistic approach: it instructs Perl not to close first 255 descriptors when executing another programs:

  my @fds;
  $^F = 255;

The following code obtains the list of mount points:

  open(my $in, '-|', 'mount -t nonfs,noproc,nosysfs,notmpfs');
  while (<$in>) {
      if (/^\S+ on (?<mpoint>.+) type (?<fstype>.+) /) {

For each mountpoint, the find command line is constructed and launched. The file descriptor is pushed to the @fds array to prevent it from being collected by the garbage collector:

  	open(my $fd, '-|',
  	     "find $+{mpoint} -xdev -type f"
               . " \\( -perm -u+x -o -perm -g+x -o -perm -o+x \\)"
               . " \\( -perm -u+s -o -perm -g+s \\) -print");
  	push @fds, $fd;

Now, the mail command is instructed to create next attachment from that file descriptor:

  	my $mpname = $+{mpoint};
  	$mpname =~ tr{/}{%}; 
  	push @cmd,
             "--content-name=Set[ug]id files on $+{mpoint} (type $+{fstype})",
  	     '--attach-fd=' . fileno($fd);
  close $in;

Finally, the emails of the recipients are added to the command line, the standard input is closed to make sure mail won't enter the interactive mode and the constructed command is executed:

  push @cmd, @rcpt;
  close STDIN;

The entire script can be downloaded here.

Sending multipart/alternative

Normally, each new attachment represents a new piece of data. This is reflected by the Content-Type header of the message, which is set to multipart/mixed by default. If, however, the attachments you send convey the same information, but in different formats, use the --alternative command line option. This option changes the Content-Type of the message to multipart/alternative and resets the Content-Disposition header of each attachment to the value inline.

Suppose for example, that you have formatted the same text in two variants -- as a plain text, in file message.txt, and as a HTML, in file message.html. To send this information, you would use the following command:

 mail --alternative --content-type=text/plain --attach=message.txt --content-type=text/html --attach=message.html < /dev/null

Notice the < /dev/null redirection. This ensures that no other text will be sent in the body of the message. You may wish to use the -E 'set nonullbodymsg' option to suppress the null body warning message, as explained earlier.

Actually, the example above can be simplified as:

 mail --alternative --content-type=text/html --attach=message.html < message.txt

In this variant we pass message.txt as the message body (which is text/plain by default), and supply the HTML variant as attachment. Both invocations produce exactly the same message.