Can anyone please help with attaching two text files while sending email using Email::Simple. I am able to receive mail but without the attachments
I have tried a lot but couldn't make it work, not sure if I am having the incorrect modules. I did not want to use MIME::Lite because of the recommendation by the creator of MIME::Lite. I basically wanted to use my own SMTP details, and got Email::Sender as recommendation. Everything works except the attachment.
use strict;
use warnings;
use Email::Sender::Simple qw(sendmail);
use Email::Sender::Transport::SMTP ();
use Email::Simple ();
use Email::Simple::Creator ();
use Email::Sender::Transport::SMTP::TLS;
use Email::MIME;
use IO::All;
my $transport = Email::Sender::Transport::SMTP::TLS->new({
host => 'smtp.office365.com',
port => 587,
sasl_username => 'abcsender#abc.com',
sasl_password => 'P#ssw0rd#123',
username => 'abcsender#abc.com',
password => 'P#ssw0rd#123'
});
my #parts = (
Email::MIME->create(
attributes => {
content_type => "text/plain",
filename => "/tmp/ERROR1493720941.log",
charset => "US-ASCII",
disposition =>"attachment",
},
body => io( "/tmp/ERROR1493720941.log" )->all,
),
Email::MIME->create(
attributes => {
content_type => "text/plain",
filename => "/tmp/FAILED1493720941.log",
charset => "US-ASCII",
disposition =>"attachment",
},
body => io( "/tmp/FAILED1493720941.log" )->all,
),
);
my $email = Email::Simple->create(
header => [
To => 'gsrivastava#abc.com',
From => 'abcsender#abc.com',
Subject => 'Hi!',
],
body => "Hello",
parts => [ #parts ],
);
sendmail($email, { transport => $transport });
As asked by #DaveCross in comment, here is the output of $email->as_string
$VAR1 = 'To: gsrivastava#abc.com^M
From: abcsender#abc.com^M
Subject: Hi!^M
Date: Sun, 7 May 2017 07:58:46 -0400^M
^M
Hello^M
Turns out that this is a pretty simple mistake to make. You are creating a MIME email, but when you get to creating the actual email object, you use this code:
my $email = Email::Simple->create(
header => [
To => 'gsrivastava#abc.com',
From => 'abcsender#abc.com',
Subject => 'Hi!',
],
body => "Hello",
parts => [ #parts ],
);
Email::Simple isn't intended for MIME mail messages, so it doesn't understand the parts attribute and ignores it. To create a MIME email, you need to use Email::MIME.
my $email = Email::MIME->create(
header => [
To => 'gsrivastava#abc.com',
From => 'abcsender#abc.com',
Subject => 'Hi!',
],
parts => [ #parts ],
);
Note that I've removed the body attribute. MIME emails can't have both parts and a body. The solution is to add another element to #parts that contains your body text.
my #parts = (
Email::MIME->create(
attributes => {
content_type => 'text/plain',
disposition => 'attachment',
charset => 'US-ASCII',
encoding => 'quoted-printable',
},
body_str => 'Hello',
),
...
);
Related
This is driving me nuts. I must be missing something stupid. I have the following sub:
sub send_email {
use MIME::Lite;
use MIME::Base64;
use Encode;
my $to = 'support#foobar.co.uk'; #$rec{'Email'};
my $from = $admin_email;
my $subject = "webform $html_title";
my $html = "some test <b>message</b> foo bar test";
my $text = "some test message some plain version";
# $html = decode( 'utf-8', $html );
# $text = decode( 'utf-8', $text );
my ($status,$attach,$newfile);
use Email::MIME;
use Email::Address::XS;
use Email::Sender::Simple qw(sendmail);
use IO::All;
use GT::MIMETypes;
# multipart message
my #alternative_parts = (
Email::MIME->create(
body_str => $text,
attributes => {
encoding => 'quoted-printable',
content_type => "text/plain",
disposition => "inline",
charset => "UTF-8",
}
),
Email::MIME->create(
body_str => $html,
attributes => {
encoding => 'quoted-printable',
charset => "UTF-8",
content_type => "text/html",
disposition => "inline",
}
)
);
my #attachment_parts;
my $attach = "/path/to/file/tables.cgi";
if ($attach) {
my $filename = (reverse split /\//, $attach)[0]; # also change
+d in body => below
my $content;
my $mime = GT::MIMETypes::guess_type($filename);
push #parts, Email::MIME->create(
attributes => {
filename => $filename,
content_type => $mime,
encoding => "base64",
name => $filename,
attachment => "attachment"
},
body => io( $attach )->binary->all,
)
}
my $email = Email::MIME->create(
header_str => [
From => $from,
To => [ $to ],
Subject => $subject
],
parts => \#parts,
attributes => {
encoding => 'base64',
charset => "UTF-8",
content_type => "multipart/multipart",
#disposition => "inline",
}
);
sendmail($email->as_string);
print "EMAIL: " . $email->as_string. "\n\n"; # print for andy
}
What it needs to do is include both a plain text and HTML body of the email. Then, also attached is a file (a .cgi just for testing :)).
While the emails come through fine on Gmail - it buggers up on Outlook/Thunderbird. I have a feeling its the way I'm breaking up the "parts". From my understanding, you need a "main" body part, which can be split into a plain text and HTML version - and then the attachment as another part of the main "part". I'm not too sure how to achieve this though?
This is how the "debug_structure" comes out:
Structure: + multipart/multipart; boundary="15846317930.c94ff7.26547"
+ text/plain; charset="UTF-8"
+ text/html; charset="UTF-8"
+ text/plain; attachment="attachment"; name="tables.cgi"
UPDATE: As suggested, I'm now trying nested parts:
# multipart message
my #message_parts = (
Email::MIME->create(
body_str => $text,
attributes => {
encoding => 'quoted-printable',
content_type => "text/plain",
disposition => "inline",
charset => "UTF-8",
}
),
Email::MIME->create(
body_str => $html,
attributes => {
encoding => 'quoted-printable',
charset => "UTF-8",
content_type => "text/html",
disposition => "inline",
}
)
);
my #all_parts;
push #all_parts, Email::MIME->create(
parts => [\#message_parts], # add all the message parts into here...
attributes => {
content_type => "multipart/alternative"
}
);
my $attach = "/home/user/web/public_html/cgi-bin/admin/tables.cgi";
if ($attach) {
my $filename = (reverse split /\//, $attach)[0]; # also changed in body => below
# better to use GT::MIMETypes if you have it with Fileman (pretty sure you do?)
my $mime = GT::MIMETypes::guess_type($filename);
push #all_parts, Email::MIME->create(
attributes => {
filename => $filename,
content_type => $mime,
encoding => "base64",
name => $filename
},
body => io( $attach )->binary->all,
)
}
my $email = Email::MIME->create(
header_str => [
From => $from,
To => [ $to ],
Subject => $subject
],
parts => [\#all_parts],
attributes => {
encoding => 'base64',
content_type => "multipart/mixed"
}
);
print qq|Structure: | . $email->debug_structure. "\n\n";
But I get an error:
Can't call method "as_string" on unblessed reference at
/usr/local/share/perl/5.22.1/Email/MIME.pm line 771.
Line 771 is in parts_set in Email::MIME - so I must be doing something wrong setting?
UPDATE 2: Thanks Steffen for your help! So this is the final working code, with the correct structure:
use Email::MIME;
use Email::Address::XS;
use Email::Sender::Simple qw(sendmail);
use IO::All;
use GT::MIMETypes;
my $to = 'support#foo.co.uk'; #$rec{'Email'};
my $from = $admin_email;
my $subject = "some title";
my $html = "some test <b>message</b> foo bar test";
my $text = "some test message some plain version";
$html = decode( 'utf-8', $html );
$text = decode( 'utf-8', $text );
# multipart message
my #message_parts = (
Email::MIME->create(
body_str => $text,
attributes => {
encoding => 'quoted-printable',
content_type => "text/plain",
disposition => "inline",
charset => "UTF-8",
}
),
Email::MIME->create(
body_str => $html,
attributes => {
encoding => 'quoted-printable',
charset => "UTF-8",
content_type => "text/html",
disposition => "inline",
}
)
);
my #all_parts;
push #all_parts, Email::MIME->create(
parts => \#message_parts, # add all the message parts into here...
attributes => {
content_type => "multipart/alternative"
}
);
my $attach = "/home/user/web/foo.co.uk/public_html/cgi-bin/admin/tables.cgi";
if ($attach) {
my $filename = (reverse split /\//, $attach)[0]; # also changed in body => below
# better to use GT::MIMETypes if you have it with Fileman (pretty sure you do?)
my $mime = "plain/text"; # hard coded in this example, but you want to set the correct type for the attachment type
push #all_parts, Email::MIME->create(
attributes => {
filename => $filename,
content_type => $mime,
encoding => "base64",
name => $filename
},
body => io( $attach )->binary->all,
)
}
my $email = Email::MIME->create(
header_str => [
From => $from,
To => [ $to ],
Subject => $subject
],
parts => \#all_parts,
attributes => {
encoding => 'base64',
content_type => "multipart/mixed"
}
);
print qq|Structure: | . $email->debug_structure. "\n\n";
sendmail($email->as_string);
The structure now comes out correctly as:
Structure: + multipart/mixed; boundary="15846944601.d6aF.12245"
+ multipart/alternative; boundary="15846944600.d79D2A2.12245"
+ text/plain; charset="UTF-8"
+ text/html; charset="UTF-8"
+ text/plain; name="tables.cgi"
There is no such thing as a multipart/multipart which you use. Your mail should have the following structure instead:
multipart/mixed
|- multipart/alternative << mail client will choose which of the parts to display
| | text/plain << the mail as plain text
| | text/html << the mail as HTML
|- text/plain << the attachment
As for the attachment it might be useful to choose a content-type which better matches the attachment type. If the attachment is actually plain text then text/plain might be fine but if it is an image, office document, archive ... different content-type should be used.
Apart from that neither encoding nor charset nor disposition make any sense in a multipart definition. These are only relevant for final parts (text/plain etc), not for container parts (multipart/whatever)
attributes => {
encoding => 'base64',
charset => "UTF-8",
content_type => "multipart/multipart",
#disposition => "inline",
}
I have adapted a script from the Perl Cookbook. I am testing it to send mail to myself in gmail.
#!/usr/bin/perl
use strict;
use warnings;
use MIME::Lite;
my $msg;
$msg = MIME::Lite->new(From => 'zmumba#gmail.com',
To => 'zmumba#gmail.com',
Subject => 'My office photo',
Type => 'multipart/mixed');
$msg->attach(Type => 'image/png',
Path => '/home/zmumba/ZMD_Proj/Docs/Reporting',
Filename => 'office_lyout.png');
$msg->attach(Type => 'TEXT',
Data => 'I hope you can use this!');
$msg->send( );
When I run this script, I get the message "/home/zmumba/ZMD_Proj/Docs/Reporting" not readable.
From here How can I send mail through Gmail with Perl? , I now understand that I have to send mail through a mailserver to use MIME::Lite. So I replaced
$msg = MIME::Lite->new(From => 'zmumba#gmail.com
with
$msg = Email::Send::Gmail->new(From => 'zmumba#gmail.com
and I get the error "Can't locate object method "new" via package Email::Send::Gmail".
Then I tried
$msg = Net::IMAP::Simple::SSL->new(From => 'zmumba#gmail.com',
and I get "Odd number of elements in hash assignment at /home/zmumba/perl5/lib/perl5/Net/IMAP/Simple.pm line 25.
Can't call method "attach" on an undefined value at ...".
Any assistance on how to go about it?
Thanks in anticipation.
The Perl Cookbook is 20 years old and its recommendations will be out of date. Using MIME::Lite is discouraged.
MIME::Lite is not recommended by its current maintainer. There are a number of alternatives, like Email::MIME or MIME::Entity and Email::Sender, which you should probably use instead. MIME::Lite continues to accrue weird bug reports, and it is not receiving a large amount of refactoring due to the availability of better alternatives. Please consider using something else.
You should probably follow their recommendation and use Email::Sender.
"Can't locate object method "new" via package Email::Send::Gmail"
You need to load Email::Send::Gmail with use Email::Send::Gmail.
You may need to install the Email::Send::Gmail module. It's simplest to do this using either cpanminus or install a fresh Perl with perlbrew and then use cpanminus.
Then I tried
$msg = Net::IMAP::Simple::SSL->new(From => 'zmumba#gmail.com',
and I get "Odd number of elements in hash assignment at /home/zmumba/perl5/lib/perl5/Net/IMAP/Simple.pm line 25.
MIME::Lite, Email::Send::Gmail, and Net::IMAP::Simple::SSL are different libraries with different interfaces that take different arguments differently. Refer to their documentation for how to use them.
As mentioned before, both MIME::Lite and Email::Send is discouraged -
Email::Send is going away... well, not really going away, but it's
being officially marked "out of favor." It has API design problems
that make it hard to usefully extend and rather than try to deprecate
features and slowly ease in a new interface, we've released
Email::Sender which fixes these problems and others
I have created a script which uses Email::MIME, Email::Sender::Simple, Email::Sender::Transport::SMTP for sending mail. You can take a look at https://github.com/rai-gaurav/perl-toolkit/tree/master/Mail and use it as per your requirement.
Important lines from that code are -
sub create_mail {
my ( $self, $file_attachments, $mail_subject, $mail_body ) = #_;
my #mail_attachments;
if (#$file_attachments) {
foreach my $attachment (#$file_attachments) {
my $single_attachment = Email::MIME->create(
attributes => {
filename => basename($attachment),
content_type => "application/json",
disposition => 'attachment',
encoding => 'base64',
name => basename($attachment)
},
body => io->file($attachment)->all
);
push( #mail_attachments, $single_attachment );
}
}
# Multipart message : It contains attachment as well as html body
my #parts = (
#mail_attachments,
Email::MIME->create(
attributes => {
content_type => 'text/html',
encoding => 'quoted-printable',
charset => 'US-ASCII'
},
body_str => $mail_body,
),
);
my $mail_to_users = join ', ', #{ $self->{config}->{mail_to} };
my $cc_mail_to_users = join ', ', #{ $self->{config}->{mail_cc_to} };
my $email = Email::MIME->create(
header => [
From => $self->{config}->{mail_from},
To => $mail_to_users,
Cc => $cc_mail_to_users,
Subject => $mail_subject,
],
parts => [#parts],
);
return $email;
}
sub send_mail {
my ( $self, $email ) = #_;
my $transport = Email::Sender::Transport::SMTP->new(
{
host => $self->{config}->{smtp_server}
}
);
eval { sendmail( $email, { transport => $transport } ); };
if ($#) {
return 0, $#;
}
else {
return 1;
}
}
Gmail and other mail servers will not allow unauthorized relay. In most cases you will need authorized access.
Here is what I use, after also trying many modules. Maybe this solution seems a little bit overdone, but it's easy to change for HTML with plain text as alternative or other attachments. Parameters of the transport are the most common ones.
#!perl
use strict;
use warnings;
use utf8;
use Email::Sender::Simple qw(sendmail try_to_sendmail);
use Email::Sender::Transport::SMTPS;
use Email::Simple ();
use Email::Simple::Creator ();
use Email::MIME;
send_my_mail('john.doe#hisdomain.com','Test','Test, pls ignore');
sub send_my_mail {
my ($to_mail_address, $subject, $body_text) = #_;
my $smtpserver = 'smtp.mydomain.com';
my $smtpport = 587;
my $smtpuser = 'me#mydomain.com';
my $smtppassword = 'mysecret';
my $transport = Email::Sender::Transport::SMTPS->new({
host => $smtpserver,
ssl => 'starttls',
port => $smtpport,
sasl_username => $smtpuser,
sasl_password => $smtppassword,
#debug => 1,
});
my $text_part = Email::MIME->create(
attributes => {
'encoding' => 'quoted-printable',
'content_type' => 'text/plain',
'charset' => 'UTF-8',
},
'body_str' => $body_text,
);
my $alternative_part = Email::MIME->create(
attributes => {
'content_type' => 'multipart/alternative',
},
parts => [ $text_part, ],
);
my $email = Email::MIME->create(
header_str => [
To => $to_mail_address,
From => "Website <$smtpuser>",
Subject => $subject,
],
attributes => {
'content_type' => 'multipart/mixed',
},
parts => [ $alternative_part ],
);
my $status = try_to_sendmail($email, { transport => $transport });
return $status;
}
Take a look at Email::Send::Gmail module. It might solve your problem.
I am trying to send HTML email with inline images. I will have to use native unix things and Email::Mime since those are the only things I found installed in the box i am stuck with. I am creating a Email::Mime message and sending it to sendmail. I am using cid for in-lining the image but for some reason I keep getting the image as an attachment.
Can someone help me, code snippet is below.
sub send_mail(){
use MIME::QuotedPrint;
use HTML::Entities;
use IO::All;
use Email::MIME;
$boundary = "====" . time() . "====";
$text = "HTML mail demo\n\n"
. "This is the message text\n"
. "Voilà du texte qui sera encodé\n";
$plain = encode_qp $text;
$html = encode_entities($text);
$html =~ s/\n\n/\n\n<p>/g;
$html =~ s/\n/<br>\n/g;
$html = "<p><strong>" . $html . "</strong></p>";
$html .= '<p><img src="cid:123.png" class = "mail" alt="img-mail" /></p>';
# multipart message
my #parts = (
Email::MIME->create(
attributes => {
content_type => "text/html",
encoding => "quoted-printable",
charset => "US-ASCII",
},
body_str => "<html> $html </html>",
),
Email::MIME->create(
attributes => {
content_type => "image/png",
name => "pie.png",
disposition => "Inline",
charset => "US-ASCII",
encoding => "base64",
filename => "pie.png",
"Content-ID" => "<123.png>",
path => "/local_vol1_nobackup/user/ramondal/gfxip_gfx10p2_main_tree03/src/verif/ge/tb",
},
body => io("pie.png")->binary->all,
),
);
my $email = Email::MIME->create(
header_str => [
To => 'abc#xyz.com',
Subject => "Test Email",
],
parts => [#parts],
);
# die $email->as_string;
open(MAIL, "|/usr/sbin/sendmail -t") or die $!;
print MAIL $email->as_string;
close (MAIL);
}
There are two problems with your code.
First, it should be a Content-Id: <123.png> MIME header but your code instead produces a content-id=<123.png> parameter for the Content-Type header. To fix this don't add the Content-Id to the attributes but instead as header_str:
...
Email::MIME->create(
header_str => [
"Content-ID" => "123.png",
],
attributes => {
content_type => "image/png",
...
Second, the code creates a multipart/mixed content type for the mail. But the image and the HTML are related, so it should be a multipart/related content-type:
...
my $email = Email::MIME->create(
header_str => [
To => 'abc#xyz.com',
Subject => "Test Email",
],
attributes => {
content_type => 'multipart/related'
},
parts => [#parts],
);
...
Code for attachment
$msg->attach(Type => 'document/text',
Path => "C:\\SDB_Automation\\sdb_dump.txt",
Filename => 'sdb_dump.txt',
Disposition => 'attachment'
);
$msg->send;
I have print the data in text file and save it in my local machine. But instead of that text file I want to send only the data of that file because of the subscriber kind of thing.
Perl has many modules to help. try slurp for example. Hope this help.
use Email::MIME;
use Email::Sender::Simple qw(sendmail);
use Email::Sender::Transport::SMTP::TLS;
use Path::Class qw/file/;
use File::Type;
use MIME::Base64;
$file= file(path);
$file = scalar $file->slurp;
my $newpart = Email::MIME->create(
attributes => {
content_type => type,
encoding => base64,
disposition => $attachment->{disposition}, # maybe useful rfc2387
charset => undef,
name => 'attachment',
},
body => $file,
);
push #parts , $newpart;
my $email = Email::MIME->create(
header => [
From => " ",
To => "",
Subject => "",
'Reply-To' => "",
],
parts => [#parts],
);
Email::Sender::Simple->send($email)
Why does the attachment(ca. 110KiB) split up in 10 parts(ca. 11KiB) when I send it with this script using Email::MIME?
#!/usr/bin/env perl
use warnings; use strict;
use Email::Sender::Transport::SMTP::TLS;
my $mailer = Email::Sender::Transport::SMTP::TLS->new(
host => 'smtp.my.host',
port => 587,
username => 'username',
password => 'password',
);
use Email::MIME::Creator;
use IO::All;
my #parts = (
Email::MIME->create(
attributes => {
content_type => 'text/plain',
disposition => 'inline',
encoding => 'quoted-printable',
charset => 'UTF-8',
},
body => "Hello there!\n\nHow are you?",
),
Email::MIME->create(
attributes => {
filename => "test.jpg",
content_type => "image/jpeg",
disposition => 'attachment',
encoding => "base64",
name => "test.jpg",
},
body => io( "test.jpg" )->all,
),
);
my $email = Email::MIME->create(
header => [ From => 'my#address', To => 'your#address', Subject => 'subject', ],
parts => [ #parts ],
);
eval {
$mailer->send( $email, {
from => 'my#address',
to => [ 'your#address' ],
} );
};
die "Error sending email: $#" if $#;
I had a similar case using MIME::Lite and Net::SMTP::TLS (using TLS rather than SSL because connection to smtp.gmail.com was not working with SSL) in my Perl script to send email with spreadsheet attachments through a gmail account, whereby the spreadsheet attachments were being broken up into multiple 10kb files.
Solution was to replace Net::SMTP::TLS with Net::SMTP::TLS::ButMaintained, which I hadn't initially seen. Newer TLS module works great.
I can offer you a workaround: using MIME::Lite instead