Introduction
Sending e-mail from Perl-based
CGIs appears to be one of most difficult tasks for newbies. Probably most
asked question about sending e-mail is: 'how do I send e-mail from my CGI
script, plain one or along with attachment?' This question becomes so important
these times, whether you have to send automated sign-up confirmation or
newsletter.
As with almost any task,
there are already some solutions around. To send e-mail, you could use
wide range of tools, like using appropriate Perl module or send message
directly using server's MTA (Mail Transport Agent, such as 'sendmail' for
Un*x or 'blat' for NT), or even consider to write low-level SMTP subroutine
yourself.
In this article I'll concern
on common usage of MIME::Lite module to send e-mails, because it easy to
use, takes much less requirements than full-weight Big Brother called MIME::Tools
and doesn't require lot of wrapping.
Availability
First, make sure you have MIME::Lite
module installed at your host. Unfortunately, at this time not so many
webhosts have MIME::Lite pre-installed. Contact your tech support service
or host administrator regarding these matters. If installation is impossible,
you still be able to use the module, but you need obtain it first.
Obtaining the MIME::Lite
The easiest way to get the latest
version of MIME::Lite is to get it from CPAN, or Comprehensive Perl Archive
Network (http://www.cpan.org). If you'll do, you'll have so-called 'module
distribution' - gzipped tar archive file (*.tar.gz or *.tgz). Once you've
got it, you are ready to install.
Module installation
You can install MIME::Lite in
two ways. First way assumes that you have shell access to your host. Second
way is much more simple and is recommended if you have non-Un*x host, or
have no idea what the Un*x shells about, or just won't spend the time.
Installation via shell
Ok, so you want to install MIME::Lite
using your shell access. I assume that you have no root access to remote
host (I bet it your case unless you rent the dedicated server ;). Upload
the module distribution to your host, somewhere outside of the website
tree (e.g. outside of 'public_html' or 'www') directory, log into the shell,
change to directory where you have upload the distribution then type:
tar zxf module_name
or, if your 'tar' does not
support 'z' option, try
gzip -dc module_name | tar xf -
, where 'module_name' is
the full filename of distribution, including file extension. Do not forget
about the case-sensitivity. When tar finished, you should have there newly
created directory, which has the name similar to those of distribution
filename but without extension. Change to the this directory. There should
be a file called Makefile.PL or like. Type:
mkdir /myaccount/final_dir
perl Makefile.PL PREFIX=/myaccount/final_dir
make install
, where '/myaccount/final_dir'
stands for full path to directory where you want install module to. Module
should be installed. If you have any problems, please read module documentation,
contact your support staff or just make the plain installation instead.
In case if you have the root
access or, more generally, you are granted to install module in system-wide
directories and want to do so, just don't provide PREFIX in example above.
Plain installation
Plain installation is the simplest
way to use MIME::Lite if it isn't installed yet. Unzip the module distribution
using archiver like WinZip, take the Lite.pm and upload it (don't forget
to use FTP in ASCII mode) along with your CGI scripts. Make sure the permissions
for Lite.pm are 0644 (rw-r--r--).
Using the Module
As with any Perl module, in
order to use MIME::Lite from within your Perl CGI scripts, you have to
include it:
use MIME::Lite;
If module is installed by
yourself (no matter using shell or not), before this line you need either
use lib '/myaccount/final_dir';
or
use lib '/myaccount/final_dir/lib/site_perl';
Composing And Sending Simple
E-mail Messages
Before proceeding, I assume
that you already have MIME::Lite installed and ready to use. Also, I assume
that you already know some e-mail basics.
Process of sending e-mail
consists of two parts: composing e-mail then sending it. Well, let's dive:
#!/usr/bin/perl
# You may want to add 'use lib...' before
# this line, according to your situation
use MIME::Lite;
# if your Perl runs on non-Un*x OS, you may need to set $WIN_NT
to
# non-zero value here.
undef $WIN_NT;
# set up MTA (mail transfer agent, e.g. sendmail)
if($WIN_NT) {
# Suitable for non-Un*x environment
or if you just want to use SMTP
MIME::Lite->send('smtp', "smtp.myisp.net");
} else {
# set up your MTA here
(you can try just MIME::Lite->send('sendmail') )
MIME::Lite->send('sendmail', "/usr/sbin/sendmail -t -i");
}
# create the message body
my $body = qq|Hi!
This is a simple message.
Bye...
|;
# create message object and automatically compose new message
my $msg = MIME::Lite->new(
From =>'my@myhost.com',
To =>'you@yourhost.com',
Subject =>'Hi!',
Data => $body
);
# after composing, send message using MTA pre-defined above
die "Error sending e-mail: $!" unless $msg->send;
# compose another message
$msg = MIME::Lite->build(
From =>'my@myhost.com',
To =>'another@friend',
Subject =>'Hi too!',
Data => $body
);
# Save another message to the file rather than send it
open(FH, ">message.txt") or die("Couldn't create: $!");
flock(FH, 2) unless $WIN_NT;
$msg->print(\*FH);
close FH;
# print result
print "Content-Type: text/html\n\n";
print "Messages processed!";
# Gear down
exit(0);
In example above, first thing
you made is set up method of sending: using MTA or over SMTP link. For
Un*x or Linux systems, it's better to use MTA like sendmail. For NT or,
generally, non-Un*x systems you'd better rely not on existence of system-specific
MTAs (like 'blat' for NT) but instead use direct SMTP connection whenever
possible.
Some commonly used command-line
arguments for sendmail are:
-t scans message for fields
To:, Cc: and Bcc: . The Bcc: line will be deleted before transmission.
-i (or -oi) ignores dots
alone on lines by themselves in messages. (normally, such dot is message terminator.)
-oem returns back the message
on error, according to address in the header.
After you've set method of
sending, you have to construct new e-mail message object and build new
message, giving it's header data (parameters like 'From', 'To' and so on)
and message body ('Data'). This is done by calling new().
When you create or initialize
$msg object, 'Data's value should contain message body. You can feed the
string or the array of message lines. In case of array it will be concatenated.
After message object constructed,
you can send your message immediately using send() method of the message
object. To compose another message using existing MIME::Lite object, just
re-initialize it - use build() instead of new().
Composing And Sending Complex
E-mail Messages
What about sending e-mail in
non-latin or even non-ASCII characters?... What about multipart message,
e.g. with JPEG or PDF attached? In these cases, message and attachments
should be properly encoded and linked together to avoid corruption in transit.
Fortunately, MIME::Lite handles all of it, and we'll discuss these issues
below.
Message encoding
In order to transmit message
uncorrupted, you need to encode it according to standards. Following standard
common encodings available:
* '7bit', or 'send as is'.
Plain encoding for plain text. This encoding seems to be default for lot
of current mail gateways, MTAs and mail clients. Line of message cannot
exceed 1000 characters, including CRLF terminator.
* '8bit', or 'bit more advanced'
;) The same 7bit restrictions apply to this encoding too except that here
you can use 8-bit characters. It could be useful for messages contain non-latin
characters.
* 'binary', the same as 8bit
but now message body lines may exceed limit of 1000 characters. However,
message encoded that way is the first candidate to be corrupted when passed
through mail gateways.
* 'quoted-printable', another
standard encoding. It also deals with 8-bit data and has not line length
limits.
* 'base64', widely used to
encode. It fits the data into printable ASCII characters, so the message
becomes bigger in size.
Below is quick guide for
encoding:
Use encoding |
If your message contains |
7bit |
Only 7-bit text, all lines
less than 1000 characters |
8bit |
8-bit text, all lines less
than 1000 characters |
binary |
8-bit text, long lines allowed |
quoted-printable |
The same as '8bit' or 'binary'
but more reliable |
base64 |
Large non-textual data:
archives, pictures and so on |
The 'binary' encoding is
not recommended; instead, consider using one of the other encodings above.
For 7bit, 8bit and binary no real encoding done at all. Note that MIME::Lite
automatically chomps long (those over 1000 characters) lines in case of
7bit or 8bit encodings.
Message attachments
Message with attachment(s) is
'multipart' in nature, i.e. consists of several parts. Such message could
be one of type 'multipart/mixed', 'multipart/related' and so on, depending
on what you have to compose.
All the parts of multipart
message (e.g. message body or attached files) could be merged in 'inline'
or 'attachment' fashion. For 'inline' case, files are attached along with
message's body and could appear for end-user right inside the message.
Otherwise, they could appear as links to files after the message ends.
For each attachment, you
need to provide its MIME type, depending on what this file contains (e.g.
'text/plain', 'image/jpeg' etc.) To help you keep the mind clear, two special
values available in MIME::Lite, they are 'TEXT' (means 'text/plain') and
'BINARY' (means 'application/octet-stream'). If you can't guess what MIME type you need, use TEXT for text attachment,
and BINARY for any other files.
Putting it all together
So, to compose message with
JPEG picture as standalone (non-inline) attachment, you need construct
the message like that:
# compose message first
my $msg = MIME::Lite->new(
From
=> 'me@myhost.com',
To
=> 'you@yourhost.com',
Type
=> 'TEXT',
Subject => 'Picture
for you',
Data => "Picture you need is here!"
);
# Second, attach JPEG picture
$msg->attach(
Type
=> 'image/jpeg',
Path
=> '/path/to/real_file_name.jpg', = nt>
Filename => 'recommended_file_name.jpg',
Encoding => 'base64',
Disposition => 'attachment'
);
In example above, 'Encoding'
contains name of encoding schema for this file, discussed far above. 'Filename'
parameter is optional. It contains recommended filename for the recipient
saving the attachment to disk. You only need this if the filename at the
end of the "Path" is inadequate, or if you're using "Data" instead of "Path".
You should not put any path information (slashes, backslashes etc.) in
the 'Filename'.
If you want to send the text
message containing picture which should appear right inside body's text:
my $msg = MIME::Lite->new(
From
=> 'me@myhost.com',
To
=> 'you@yourhost.com',
Subject => 'A message
with 3 parts',
Type
=> 'multipart/mixed'
);
# attach first part of body
$msg->attach(
Type
=> 'TEXT',
Data
=> "Image is below this line"<
= /tt>
);
# insert GIF picture (has the same arguments as "new"):
$msg->attach(
Type
=> 'image/gif',
Path
=> 'smile.gif',
);
# complete the body - add the last part
$msg->attach(
Type
=> 'TEXT',
Data
=> "Image is above this line"<
= /tt>
);
If you provide no 'Disposition',
default will be "inline", like in last example.
Another common task is to
send HTML page with pictures linked to it. Here's how to compose such a
message:
# Create the message object and initialize it
my $msg = MIME::Lite->new(
From
=> 'me@myhost.com',
To
=> 'you@yourhost.com',
Subject => 'HTML with
in-line images!',
Type
=> 'multipart/related'
);
# Attach HTML page
$msg->attach(
Type => 'text/html',
Data => qq|
<body>
Here's the image:
<img src="cid:theimage.gif">
</body>
|
);
# Attach GIF image
$msg->attach(
Type => 'image/gif',
Id => 'theimage.gif',
Path => '/path/to/somefile.gif',
);
Note that in example above,
optional parameter 'Id' been introduced. In this example, for reference
to the GIF you should mention its 'Id'.
Other MIME::Lite Abilities
You have also many other options
for e-mails created with MIME::Lite. It has rich set of features to help
you deal with most real-life situations. You can specify your own custom
header fields, compose the message using existing files or filehandles,
and output the message or its parts to string, file or filehandle, rather
than send it.
Specifying custom header fields
In addition to lot of pre-defined
standard header fields, you may specify your own custom field in the header.
To do that, just append trailing ':' to its name, like this:
my $msg = MIME::Lite->new(
From
=> 'my@myhost.com',
To
=> 'you@yourhost.com',
'Custom:' => 'custom-field-content',
Data
=> "Just a message"
);
Using existing files or filehandles for composition
Instead of 'Data', you may provide
'Path' with path to file which contains message body:
my $msg = MIME::Lite->new(
From
=> 'my@myhost.com',
To
=> 'you@yfourhost.com',
Path
=> '/home/johnsmith/message.txt' = t>
);
, or already opened filehandle
as value of 'FH' (which in this case goes instead of 'Path' or 'Data'):
my $msg = MIME::Lite->new(
From => 'my@myhost.com',
To => 'you@yourhost.com',
Cc => 'some@other.com, some@more.com',
Subject => 'Mail from opened file...',
FH => \*HANDLE
);
In case of filehandle, DO
NOT close it before you make any mail output. Why? Because when you specify
message bodies by 'FH' or 'Path', MIME::Lite does not try to open and read
files or filehandles right now (as well as it doesn't try to encode any
data) until print() method is invoked. This is why I told you do not close
input filehandles. However, it does some kind of verification to ensure
there's no broken paths around. If you won't use that pre-flight check,
just set $MIME::Lite::AUTO_VERIFY variable to 0 (false) before any construction
of mail object.
If you need read the content
of file right now, not waiting for print() to commit, you should use ReadNow
parameter:
my $msg = MIME::Lite->build(
From
=> 'my@myhost.com',
To
=> 'you@yourhost.com',
Type
=> 'x-gzip',
Subject => 'Tar
archive, gzipped on-the-fly before sending',
Path
=> "gzip < /path/to/archive/thebal
= l.tar |",
ReadNow => 1
);
If ReadNow is '1' (true),
it will open the path and suck the contents in right now. You may need
this feature when you work with external executable instead of plain file,
i.e. one of Un*x commands (like 'gzip'), and you won't execute the command
over and over again to output the messages. Be careful, however; several
really big message objects constructed with ReadNow will consume lot of
memory.
If you need to read file's
contents but have the message object already constructed, consider read_now()
method:
$msg->read_now();
It works like ReadNow, i.e.
forces data from the path or filehandle (as specified by build()) to be
read immediately, but should be used for already existing message objects.
Regardless of what method
you prefer, you'll get fatal exception if the open fails. Handling Perl's
fatal exceptions is out of scope of this article; there are already many
information around related to this topic.
Output message to string, file or filehandle
Instead of sending, you could
output whole message (including all the headers) to a string:
my $str = $msg->as_string;
You can output just the header
to string:
my $str = $msg->header_as_string;
Or message's encoded body:
my $str = $msg->body_as_string;
Also you can output the message
to existing filehandle (either plain file or even pipe to MTA):
$msg->print(\*HANDLE);
Again, you can output just
the header:
$msg->print_header(\*HANDLE);
or just the encoded body:
$msg->print_body(\*HANDLE);
Be aware that if you give
the message body via 'FH' then try to print() a message two or more times,
every next print() will confuse; only header will be printed. To avoid
this, you have to explicitly rewind the filehandle before any subsequent
print():
$msg->print(\*OUT_HANDLE);
# first print
$msg->resetfh(\*IN_HANDLE);
# rewind input filehandle
$msg->print(\*OUT_HANDLE);
# another print
If resetfh() fails for buffered
streams, consider to use sysseek():
sysseek(\*IN_HANDLE,
0, 0);
Conclusion
In this article, I've tried
to explain MIME::Lite and to describe most of its abilites. However, this
is not complete description of MIME::Lite; for that, you should read accompanying
documentation. As to other possibilites for sending e-mail (besides using
of MIME::Lite), they are, probably, subject for my other articles. I appreciate
your feedback and also I really need to know what the another topics you've
interested in.
P.S. Sorry for possible HTML garbage, looks like that's a problem of server-side parser...
Written by Serge (serge@bestwebscripts.com)
CGI Scripts, Programming and Services
http://bestwebscripts.com
|