Search

How To Send Email With Perl, Part III

5 min read
1 views

Sending Dual‑Format Emails with Perl

Perl has long been a favorite for building automated mail systems because it offers low‑level control over message construction while still staying readable. In the first two parts of this series you learned how to send a plain‑text message and how to switch between plain text and HTML. Part III builds on those foundations and shows how to bundle both representations into a single message so that every client - whether it can only read plain text or can render rich HTML - gets the best possible view of your content.

Why bother with two formats? Imagine you’re sending product information to a broad audience. Some recipients use email clients that strip away HTML for security reasons; others enjoy the visual flair that CSS and images provide. By packaging both versions you guarantee readability without sacrificing style. The trade‑offs are modest: the message grows a little larger, and a handful of clients might display raw HTML tags if they don’t understand the multipart structure. Most modern mailers handle multipart/alternative cleanly, but it’s worth keeping these caveats in mind when crafting your final template.

Below we walk through the entire process - from the header, through the boundary markers, to the Perl subroutine that stitches everything together. Keep the code snippets handy, as they’ll serve as a reference when you adapt the logic to your own scripts or to Master Form email templates.

1. Constructing the MIME header

The first thing you’ll notice is that a dual‑format message starts with a standard header block. Each header line follows the pattern Field: Value and ends with a carriage‑return/line‑feed pair. The crucial additions for multipart messages are MIME-Version and Content-Type. For a multipart/alternative message you’ll also include a boundary parameter that will separate the text and HTML parts.

Here’s an example header you might use:

Prompt
From: webmaster@example.com</p> <p>To: customer@example.org</p> <p>MIME-Version: 1.0</p> <p>Content-Type: multipart/alternative; boundary="_part_123456"</p> <p>Subject: Your Widget Update</p>

Notice that the boundary string is surrounded by double quotes and that it is unique within the message. The boundary acts like a marker that the email client searches for to find where one part ends and the next begins. A good practice is to generate a random string that is unlikely to appear in the body of your email. In our example we’ll hard‑code _part_123456 for clarity.

2. Building the multipart body

After the header, a blank line signals the end of the header block. The body then begins with the first boundary marker. For multipart/alternative the order matters: clients that only understand plain text will display the first part, while richer clients will pick the last part that they can render. Therefore, put the plain‑text part first, followed by the HTML part.

Below is the full body you can paste into a Perl variable or send directly via print. Each boundary is prefixed with two hyphens, and the final boundary is followed by two additional hyphens to indicate the end of the message.

Prompt
--_part_123456</p> <p>Content-Type: text/plain; charset="iso-8859-1"</p> <p>Hello,</p> <p>Thank you for your interest in our widgets.</p> <p>Please find the details below.</p> <p>--_part_123456</p> <p>Content-Type: text/html; charset="iso-8859-1"</p> <p><html></p> <p><body></p> <p>Hello,</p> </body></p> <p></html></p> <p>--_part_123456--</p>

Notice the blank line that follows each Content-Type header; that blank line separates the headers of the part from its content. Each part also ends with a blank line before the next boundary. This simple layout ensures every mail client recognises where each section starts and ends.

3. Adjusting the Perl send routine

In Part II the SendEmail subroutine accepted a flag that told it whether to send plain text or HTML. For multipart/alternative we need to drop that flag and always send both. The subroutine now constructs the entire message as shown above.

Below is a trimmed‑down version of the updated SendEmail. The script uses IO::Socket::SMTP for transport; you can replace it with Net::SMTP or any other library you prefer.

Prompt
use IO::Socket::SMTP;</p> <p>sub SendEmail {</p> <p> my ($to, $subject, $plain, $html) = @_;</p> <p> my $boundary = "_part_123456";</p> <p> my $msg = <p>From: webmaster@example.com</p> <p>To: $to</p> <p>MIME-Version: 1.0</p> <p>Content-Type: multipart/alternative; boundary="$boundary"</p> <p>Subject: $subject</p> <p>--$boundary</p> <p>$plain</p> <p>--$boundary</p> <p>$html</p> <p>--$boundary--</p> <p>EOM</p> <p> my $smtp = IO::Socket::SMTP->new('smtp.example.com')</p> <p> or die "Could not connect to SMTP server: $!";</p> <p> $smtp->mail('webmaster@example.com')</p> <p> or die "Mail command failed: $!";</p> <p> $smtp->to($to)</p> <p> or die "Recipient command failed: $!";</p> <p> $smtp->data($msg)</p> <p> or die "Data transmission failed: $!";</p> <p> $smtp->quit;</p> <p>}</p>

To use the routine you simply call it with both the plain‑text and HTML bodies:

Prompt
my $plain_text = "Hello, Thank you for your interest.";</p> <p>my $html_text = "<html><body><p>Hello,</p><p>Thank you for your interest.</p></body></html>";</p> <p>SendEmail('customer@example.org', 'Your Widget Update', $plain_text, $html_text);</p>

Because the boundary is hard‑coded, the script is straightforward to understand. If you need to send many emails in a loop, consider generating a unique boundary per message to avoid accidental collisions.

4. Common pitfalls and how to avoid them

1. Missing blank lines. Forgetting a blank line after a Content-Type header or between parts will confuse the recipient client and can cause the entire body to appear as a single block of text. Always double‑check that there is a newline after the last header in each part.

2. Incorrect charset. If you include non‑ASCII characters and you set the charset to us-ascii, those characters will be garbled. Use utf-8 or iso-8859-1 as appropriate for your audience.

3. Boundary conflicts. The boundary string must not appear anywhere in your email content. If you use user‑generated data that could contain that string, generate the boundary at runtime using a random token.

4. SMTP authentication. The example above assumes a local, open SMTP server. In a production environment you’ll likely need to provide a username and password or use an authenticated SMTP relay. Most socket libraries offer a auth method you can call before sending data.

5. Applying the pattern to other tools

Once you’ve mastered this pattern in a standalone script, you can integrate it into larger frameworks. Master Form, for example, allows you to define custom email templates. Copy the boundary structure and body formatting into the template editor, and use template variables for the subject, plain text, and HTML snippets. The same multipart logic will work across any system that respects MIME standards.

For developers working in other languages, the same principles apply: construct a header with Content-Type: multipart/alternative, separate the parts with unique boundaries, and ensure proper blank lines. The only difference is the syntax for string interpolation or file handling in the host language.

By following these steps you’ll produce clean, accessible email messages that look great in rich clients while remaining fully readable in plain‑text environments. The dual‑format approach is a small addition to your toolkit that delivers a professional touch without extra effort.

Suggest a Correction

Found an error or have a suggestion? Let us know and we'll review it.

Share this article

Comments (0)

Please sign in to leave a comment.

No comments yet. Be the first to comment!

Related Articles