Beefy Boxes and Bandwidth Generously Provided by pair Networks
go ahead... be a heretic
 
PerlMonks  

Sending Multi file types as e-mail attachments

by merrymonk (Hermit)
on Nov 15, 2015 at 21:38 UTC ( [id://1147753]=perlquestion: print w/replies, xml ) Need Help??

merrymonk has asked for the wisdom of the Perl Monks concerning the following question:

The Perl below is my current attempt to be able to attach a number of different types of documents to a list of e-mail address.
It does seem to work except that I have had some comments that the jpg file is not received in a good condition.
This did not surprise me since I copied the section which set the ‘$content_type’ and subsequent lines from an internet example without really understanding what I was doing.
I also added the $content_type ‘definition ’ for ms-word and used the ms-excel definition for both .xls and .xlsx files (which did seem to work).
Therefore I would appreciate any comments about the validity of my code and/or better and more robust ways of adding various file types as attachments.
I have added tests for both the ‘auth’ and ‘dataend’. It seems that if 1 the test is successful.
It would be good to know if this is the best way of making sure the e-mail and its attachments has been sent.
Finally I am still looking to see how I can set the options for ‘Return Receipt’ and Delivery Status Notification’ can be set. Clues for this would be more than welcome.
use strict; use warnings; use Net::SMTP; use MIME::Base64 qw( encode_base64 ); my ($host_em, $to_em, $cc_em, $from_em, $password_em, @to_email_list, +$jt, $jt_max, $smtp, $attachBinaryFile, @attach_list, $ja, $ja_max, $ +buf); my ($content_type, $plength, $command_ok, $auth_ok); # host name required $host_em = 'mail.xxxx'; # valid e-mail address from which e-mail is to be sent and password fo +r this e-mail address required $from_em = 'xxx@xxx'; $password_em = 'xxxx'; # valid e-mail address to which e-mail is to be sent $to_email_list[0] = 'yyy@yyy'; # list of attachments for the e-mail $attach_list[0] = 'Bach.jpg'; $attach_list[1] = 'workshop notes.pdf'; $attach_list[2] = 'Booking form.pdf'; $attach_list[3] = 'testaaa.xls'; $attach_list[4] = 'testx.xlsx'; $attach_list[5] = 'wordtest.docx'; $ja_max = scalar(@attach_list); my $boundary = 'frontier'; $jt_max = scalar(@to_email_list); for($jt = 0; $jt < $jt_max; $jt ++) { $to_em = $to_email_list[$jt]; $smtp = Net::SMTP->new($host_em, SSL => 1); # test that auth is OK $auth_ok = $smtp->auth($from_em, $password_em); print "jt <$jt> command_ok <$auth_ok>\n"; $smtp->mail($from_em); $smtp->to($to_em); $smtp->data; $smtp->datasend("From: " . $from_em); $smtp->datasend("\n"); $smtp->datasend("To: " . $to_em); $smtp->datasend("\n"); $smtp->datasend('Subject: Test of e-mail list'); $smtp->datasend("\n"); $smtp->datasend("Content-type: multipart/mixed;\n\tboundary=\"$bou +ndary\"\n"); $smtp->datasend("\n"); for($ja = 0; $ja < $ja_max; $ja ++) { $attachBinaryFile = $attach_list[$ja]; if ( $attachBinaryFile =~ /\.gif$/i ){ $content_type ='image/gif'} if ( $attachBinaryFile =~ /\.jpg$/i ){ $content_type ='image/jpeg' +} if ( $attachBinaryFile =~ /\.zip$/i ){ $content_type ='application +/zip'} if ( $attachBinaryFile =~ /\.html$/i ){ $content_type ='text/html' +} if ( $attachBinaryFile =~ /\.pdf$/i ){ $content_type ='application +/pdf'} if ( $attachBinaryFile =~ /\.xls$/i ){ $content_type ='application +/vnd.ms-excel'} if ( $attachBinaryFile =~ /\.doc$/i ){ $content_type ='application +/vnd.ms-word'} if ( $attachBinaryFile =~ /\.log$/i ){ $content_type ='application +/octet-stream'} $smtp->datasend("--$boundary\n"); $smtp->datasend("Content-Type: $content_type; name=\"$attachBinary +File\"\n"); $smtp->datasend("Content-Transfer-Encoding: base64\n"); $smtp->datasend("Content-Disposition: attachment; filename=\"$atta +chBinaryFile\"\n"); $smtp->datasend("\n"); print "$ja <$ja> attachment <$attachBinaryFile> type <$content_typ +e>\n"; $buf = '';; open(DAT, "$attachBinaryFile") || die("Could not open binary file! +"); binmode(DAT); local $/=undef; while (read(DAT, my $picture, 4096)) { $buf = &encode_base64( $picture ); $smtp->datasend($buf); } } close(DAT); $smtp->datasend("--$boundary\n"); # test that e-mail has been sent ok $command_ok = $smtp->dataend(); print "jt <$jt> command_ok <$command_ok>\n"; $smtp->quit; } print "\n\n\nFinished\n";

Replies are listed 'Best First'.
Re: Sending Multi file types as e-mail attachments
by Athanasius (Archbishop) on Nov 16, 2015 at 03:36 UTC

    Hello merrymonk,

    I also added the $content_type ‘definition ’ for ms-word and used the ms-excel definition for both .xls and .xlsx files (which did seem to work).

    I suspect that the code “worked” for the .xlsx file rather by accident than by design. The problem is that the value of $content_type is set correctly only when one of the regex matches succeeds. In the case of files ending in .xlsx and .docx, there is no possible regex match, so $content_type retains whatever value it had in the previous loop iteration. Since the file testx.xlsx happens to follow the file testaaa.xls, $content_type still has the value 'application/vnd.ms-excel'. But when it comes to the file wordtest.docx, the regex tests again all fail and so $content_type still has the value 'application/vnd.ms-excel', which produces a garbled attachment.

    There are two deficiencies in the code which here combine to produce the problem. First, lexical variables should be declared at the point of first use, to limit their scope as much as possible. By declaring $content_type outside the inner loop, you allow it to retain a spurious value from one iteration to the next. Second, a series of matches should always be terminated by a “no match” clause, handling the case where a match failed to occur. This is good practice even when (as here) it seems as though one of the matches is bound to succeed.

    So, here is a suggested improvement (untested):

    ... for my $ja (0 .. $ja_max - 1) { my $attachBinaryFile = $attach_list[$ja]; my $content_type; if ($attachBinaryFile =~ /\.gif$/i) { $content_type = 'image/g +if' } elsif ($attachBinaryFile =~ /\.jpg$/i) { $content_type = 'image/j +peg' } elsif ($attachBinaryFile =~ /\.zip$/i) { $content_type = 'applica +tion/zip' } elsif ($attachBinaryFile =~ /\.html$/i) { $content_type = 'text/ht +ml' } elsif ($attachBinaryFile =~ /\.pdf$/i) { $content_type = 'applica +tion/pdf' } elsif ($attachBinaryFile =~ /\.xls$/i) { $content_type = 'applica +tion/vnd.ms-excel' } elsif ($attachBinaryFile =~ /\.doc$/i) { $content_type = 'applica +tion/vnd.ms-word' } elsif ($attachBinaryFile =~ /\.log$/i) { $content_type = 'applica +tion/octet-stream' } else { warn "Unrecognized file +type: $attachBinaryFile"; next; } $smtp->datasend("--$boundary\n"); ...

    ...and then you will need to add code for the cases where the file extension is .xlsx or .docx.

    (Actually, a better implementation of the above would be to use a hash to associate file extensions with their corresponding content types.)

    Hope that helps,

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

      Thank you - I appreciate your suggestions. Below is modified version where I have implemented using a hash for the content_type. This fails when the content type is not 'known'.
      Does anyone know where I can find what the content type description should be for:
      1. in general - any file type;
      2. specifically - for ms word and excel having extensions .docx and .xlsx respectively?
      use strict; use warnings; use Net::SMTP; use MIME::Base64 qw( encode_base64 ); my ($host_em, $to_em, $cc_em, $from_em, $password_em, @to_email_list, +$jt, $jt_max, $smtp, $attachBinaryFile, @attach_list, $ja, $ja_max, $ +buf); my ($content_type, $plength, $command_ok, $auth_ok, %file_content, $fi +le_split_tot, @file_split); # set the file contant definition $file_content{gif} ='image/gif'; $file_content{jpg} ='image/jpeg'; $file_content{zip} ='application/zip'; $file_content{html} ='text/html'; $file_content{pdf} ='application/pdf'; $file_content{xls} ='application/vnd.ms-excel'; $file_content{doc} ='application/vnd.ms-word'; $file_content{log} ='application/octet-stream'; # host name required $host_em = 'mail.xxxx'; # valid e-mail address from which e-mail is to be sent and password fo +r this e-mail address required $from_em = 'xxx@xxx'; $password_em = 'xxxx'; # valid e-mail address to which e-mail is to be sent $to_email_list[0] = 'yyy@yyy'; # list of attachments for the e-mail $attach_list[0] = 'Bach.jpg'; $attach_list[1] = 'workshop notes.pdf'; $attach_list[2] = 'Booking form.pdf'; $attach_list[3] = 'testaaa.xls'; $attach_list[4] = 'testx.xlsx'; $attach_list[5] = 'wordtest.docx'; $ja_max = scalar(@attach_list); my $boundary = 'frontier'; $jt_max = scalar(@to_email_list); for($jt = 0; $jt < $jt_max; $jt ++) { $to_em = $to_email_list[$jt]; $smtp = Net::SMTP->new($host_em, SSL => 1); # test that auth is OK $auth_ok = $smtp->auth($from_em, $password_em); print "jt <$jt> command_ok <$auth_ok>\n"; $smtp->mail($from_em); $smtp->to($to_em); $smtp->data; $smtp->datasend("From: " . $from_em); $smtp->datasend("\n"); $smtp->datasend("To: " . $to_em); $smtp->datasend("\n"); $smtp->datasend('Subject: Test of e-mail list'); $smtp->datasend("\n"); $smtp->datasend("Content-type: multipart/mixed;\n\tboundary=\"$bou +ndary\"\n"); $smtp->datasend("\n"); for($ja = 0; $ja < $ja_max; $ja ++) { $attachBinaryFile = $attach_list[$ja]; # find file type @file_split = split(/\./, $attachBinaryFile); $file_split_tot = scalar(@file_split); if(exists($file_content{$file_split[$file_split_tot - 1]})) { $content_type = $file_content{$file_split[$file_split_tot +- 1]}; print "ext <$file_split[$file_split_tot - 1] content_type +<$content_type>\n"; $smtp->datasend("--$boundary\n"); $smtp->datasend("Content-Type: $content_type; name=\"$atta +chBinaryFile\"\n"); $smtp->datasend("Content-Transfer-Encoding: base64\n"); $smtp->datasend("Content-Disposition: attachment; filename +=\"$attachBinaryFile\"\n"); $smtp->datasend("\n"); print "$ja <$ja> attachment <$attachBinaryFile> type <$con +tent_type>\n"; $buf = '';; open(DAT, "$attachBinaryFile") || die("Could not open bina +ry file!"); binmode(DAT); local $/=undef; while (read(DAT, my $picture, 4096)) { $buf = &encode_base64( $picture ); $smtp->datasend($buf); } $smtp->datasend("--$boundary\n"); close(DAT); $smtp->datasend("\n"); $smtp->datasend("--$boundary\n"); } else { print "no valid content for $file_split[$file_split_tot - +1] is available\n"; } # test that e-mail has been sent ok $command_ok = $smtp->dataend(); print "jt <$jt> command_ok <$command_ok>\n"; $smtp->quit; } } print "\n\n\nFinished\n";
        There was an error with the position of one of the } at the end of the Perl that I sent in my reply above.
        Below is the complete Perl again since I thought it would be simpler this way.
        use strict; use warnings; use Net::SMTP; use MIME::Base64 qw( encode_base64 ); my ($host_em, $to_em, $cc_em, $from_em, $password_em, @to_email_list, +$jt, $jt_max, $smtp, $attachBinaryFile, @attach_list, $ja, $ja_max, $ +buf); my ($content_type, $plength, $command_ok, $auth_ok, %file_content, $fi +le_split_tot, @file_split); # set the file contant definition $file_content{gif} ='image/gif'; $file_content{jpg} ='image/jpeg'; $file_content{zip} ='application/zip'; $file_content{html} ='text/html'; $file_content{pdf} ='application/pdf'; $file_content{xls} ='application/vnd.ms-excel'; $file_content{doc} ='application/vnd.ms-word'; $file_content{log} ='application/octet-stream'; # host name required $host_em = 'mail.xxxx'; # valid e-mail address from which e-mail is to be sent and password fo +r this e-mail address required $from_em = 'xxx@xxx'; $password_em = 'xxxx'; # valid e-mail address to which e-mail is to be sent $to_email_list[0] = 'yyy@yyy'; # list of attachments for the e-mail $attach_list[0] = 'Bach.jpg'; $attach_list[1] = 'workshop notes.pdf'; $attach_list[2] = 'Booking form.pdf'; $attach_list[3] = 'testaaa.xls'; $attach_list[4] = 'testx.xlsx'; $attach_list[5] = 'wordtest.docx'; $ja_max = scalar(@attach_list); my $boundary = 'frontier'; $jt_max = scalar(@to_email_list); for($jt = 0; $jt < $jt_max; $jt ++) { $to_em = $to_email_list[$jt]; $smtp = Net::SMTP->new($host_em, SSL => 1); # test that auth is OK $auth_ok = $smtp->auth($from_em, $password_em); print "jt <$jt> command_ok <$auth_ok>\n"; $smtp->mail($from_em); $smtp->to($to_em); $smtp->data; $smtp->datasend("From: " . $from_em); $smtp->datasend("\n"); $smtp->datasend("To: " . $to_em); $smtp->datasend("\n"); $smtp->datasend('Subject: Test of e-mail list'); $smtp->datasend("\n"); $smtp->datasend("Content-type: multipart/mixed;\n\tboundary=\"$bou +ndary\"\n"); $smtp->datasend("\n"); for($ja = 0; $ja < $ja_max; $ja ++) { $attachBinaryFile = $attach_list[$ja]; # find file type @file_split = split(/\./, $attachBinaryFile); $file_split_tot = scalar(@file_split); if(exists($file_content{$file_split[$file_split_tot - 1]})) { $content_type = $file_content{$file_split[$file_split_tot +- 1]}; print "\n$ja <$ja> attachment <$attachBinaryFile> ext <$fi +le_split[$file_split_tot - 1] content_type <$content_type>\n"; $smtp->datasend("--$boundary\n"); $smtp->datasend("Content-Type: $content_type; name=\"$atta +chBinaryFile\"\n"); $smtp->datasend("Content-Transfer-Encoding: base64\n"); $smtp->datasend("Content-Disposition: attachment; filename +=\"$attachBinaryFile\"\n"); $smtp->datasend("\n"); $buf = '';; open(DAT, "$attachBinaryFile") || die("Could not open bina +ry file!"); binmode(DAT); local $/=undef; while (read(DAT, my $picture, 4096)) { $buf = &encode_base64( $picture ); $smtp->datasend($buf); } $smtp->datasend("--$boundary\n"); close(DAT); $smtp->datasend("\n"); $smtp->datasend("--$boundary\n"); } else { print "no valid content for $file_split[$file_split_tot - +1] is available\n"; } } # test that e-mail has been sent ok $command_ok = $smtp->dataend(); print "jt <$jt> command_ok <$command_ok>\n"; $smtp->quit; } print "\n\n\nFinished\n";
Re: Sending Multi file types as e-mail attachments
by Corion (Patriarch) on Nov 16, 2015 at 09:04 UTC

    Have you looked at what existing modules, like MIME::Lite produce? I use MIME::Lite to attach files to mails a long time already and it works quite well.

Re: Sending Multi file types as e-mail attachments
by soonix (Canon) on Nov 16, 2015 at 19:54 UTC

    Let Perl do the bookkeeping:

    you can populate your arrays with push, no need for counting:
    push @attach_list, 'Bach.jpg'; push @attach_list, 'workshop notes.pdf', 'Booking form.pdf'; push @attach_list, qw(testaaa.xls testx.xlsx wordtest.docx); # qw() on +ly if the file names don't have spaces in them

    (you'll see the advantage when you shuffle the attachments, or remove one in the middle...)

    Likewise, there is no need for loop counters, if you use a "foreach" loop:
    for $to_em (@to_email_list) { ... $smtp->to($to_em); ... }

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1147753]
Approved by stevieb
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others cooling their heels in the Monastery: (3)
As of 2024-04-20 04:08 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found