« Reached SH sub limit for today | Main | SH sub form acting like a revolving door »

How to send email with an attachment in Perl

| 2 Comments

I've been beating my head against this all day, but I think I've finally figured it out.

I gather that the best way to construct an email message in Perl is using Mail::Message, which is part of the Mail-Box system.

For the past couple years, we've been using the Mail::Message->build() method to create an email message simply and fairly easily. The message contains the plain text of a story in the body of the email, and the original RTF file as an attachment.

A couple weeks ago, Pair upgraded their servers to a newer version of Perl, and apparently a newer version of Mail-Box.

Today, half a dozen of our submissions came through completely garbled.

(Authors: Don't worry, the RTF versions of your stories are just fine. Don't resubmit; we'll just read the RTF versions of those stories instead of the plain text versions.)

It gradually became clear that when the build() method encountered an accented e (é), it got very confused; the rest of the message from that point on was random gibberish.

I guessed that there was a character-encoding issue going on; in particular, I thought the problem might be the "Content-Transfer-Encoding: quoted-printable" header, which wasn't present in older submissions. But there didn't seem to be a way to set the transfer encoding using the build() method.

So I decided to switch to the more advanced buildFromBody() method.

Which resulted in several hours of trial-and-error. Because although Mail::Message is documented, the docs leave out several important things, and are very ambiguously phrased about a bunch of other things, and the examples tend to try to cover parts of a bunch of different cases at once rather than showing simple and straightforward tasks.

(I would volunteer to rewrite the docs if (a) I understood the system, and (b) I had time. But I have too many projects going on right now.)

And I could not find usable examples of Mail::Message elsewhere on the web. It seems to me that this must be a common and obvious thing to want to do—construct an email message using Perl, with both plain text in the body of the email and an attached file—but I couldn't find any examples of anyone showing how to do it.

I eventually found an approach that appears, so far, to work. It's probably much more complicated than it needs to be, and I haven't fully tested it yet. But at least the problem stories now show up correctly.

(I also ended up not having to specify the transfer encoding after all. Maybe it's explicitly specifying charset that fixes the problem? Or explicitly specifying inline? Not sure, not going to bother to experiment to find out.)

So in case anyone else wants to do the same thing, here's the code I'm using. Comments and improvements most welcome.

I've simplified some stuff below to remove specific details of my system, so I can't guarantee that this code will work exactly as written. (In particular, I'm a little hazy on all the escaping of from and to strings; you might need an extra set of escaped quotation marks in the from string, not sure.) But it should be close.

This code assumes that you've already received an RTF file (perhaps as a CGI upload) and that the filename of that file is stored in $story_filename. The RTF code itself is stored in $rtf_string.

This code is in the public domain. Use it however you like.

The idea is that you create a Body for the attachment, put that into a Message, create a Body for the text, and then create a new Body that includes both the text Body and the attachment Message. Then you need to wrap that last Body in a new Message in order to send it.

Note that I'm doing the send via qmail, but I'm guessing that it would work the same way to send via sendmail or whatever other mailer you've got.

$to_object = Mail::Address->new("to_firstname to_lastname", "foo\@example.com");
$from_object = Mail::Address->new("from_firstname from_lastname", "bar\@example.com");
$text_content = $text_string . "\n"; # Necessary? Not sure.
$disposition_string_attachment = "attachment; filename=" . $story_filename;
$disposition_string_text = "inline";

# Create a Body containing the attachment.
$attachment_body = Mail::Message::Body->new
    (data => $rtf_string
     , charset => 'utf-8'
     , disposition => $disposition_string_attachment
     , mime_type => 'text/rtf'
     );

# Create a Message from the Body containing the attachment.
$attachment_message = Mail::Message->buildFromBody
  ( $attachment_body
   );

# Create a Body containing the plain text.
$text_body = Mail::Message::Body->new
    (data => $text_content
     , charset => 'utf-8'
     , disposition => $disposition_string_text
     , mime_type => 'text/plain'
     );

# Create a Body that includes both the text Body and the attachment Message.
$text_plus_attachment = $text_body->attach($attachment_message);

# Create a Message from the new combined Body.
$message_to_send = Mail::Message->buildFromBody
  ( $text_plus_attachment
   , From       => $from_object
   , To         => $to_object
   , Subject    => "FICTION SUB: $Normalized_Story_Title"
   );

# Send the message.
$message_to_send->send(via => 'qmail');

2 Comments

A simpler way, with Email::Stuff:

Email::Stuff->to($to) ->from($from) ->subject($subject) ->text_body($your_body) ->attach($attachment_text, filename => $name_of_attachment) ->send;

Thanks for this. I too found no useful samples in the documentation or elsewhere on the web. I keep thinking I must be missing something - maybe there's a book that has good Mail::Box examples?


Post a comment