使用Email::MIME发送带附件的多格式邮件问题排查与解决
解决带HTML/纯文本正文+附件的Perl邮件兼容性问题
我来帮你理清楚这个邮件结构的问题——你遇到的情况其实是邮件MIME结构不符合标准导致的,Outlook和Thunderbird对MIME结构的校验比Gmail更严格,只要调整嵌套结构就能解决。
问题背景
你编写的Perl子例程需要发送同时包含纯文本、HTML正文和附件的邮件,在Gmail中显示正常,但Outlook/Thunderbird里出现异常。从最初的debug结构能看到,你错误地把纯文本、HTML、附件放在了同一层级,还误用了multipart/multipart这个不存在的MIME类型,这才导致部分客户端解析失败。
正确的MIME结构应该是嵌套式的:
- 最外层用
multipart/mixed:用来容纳正文部分和附件 - 正文部分用
multipart/alternative:用来包裹纯文本和HTML版本,让邮件客户端自动选择显示格式
最初代码的问题
你最初的代码直接把三个部件(文本、HTML、附件)放在同一层级,且主类型使用错误,不符合MIME标准:
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 }
第一次修改的报错原因
你尝试嵌套部件时,错误地把@message_parts用数组引用再包了一层(parts => [\@message_parts]),但Email::MIME->create的parts参数只需要直接传数组引用(\@message_parts),额外嵌套会导致传递的是“引用的引用”,不是合法的部件对象,所以才出现Can't call method "as_string" on unblessed reference的错误。
最终可运行的修复代码
调整后的代码核心是:把纯文本和HTML放到multipart/alternative部件里,再把这个部件和附件一起放到最外层的multipart/mixed中:
use Email::MIME; use Email::Address::XS; use Email::Sender::Simple qw(sendmail); use IO::All; use GT::MIMETypes; use Encode; 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 ); # 构建纯文本+HTML的替代部件 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; # 把替代部件包装成multipart/alternative push @all_parts, Email::MIME->create( parts => \@message_parts, # 直接传数组引用,不要额外嵌套 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]; # 这里可以用GT::MIMETypes自动识别,示例里暂时硬编码 my $mime = "text/plain"; push @all_parts, Email::MIME->create( attributes => { filename => $filename, content_type => $mime, encoding => "base64", name => $filename }, body => io( $attach )->binary->all, ) } # 构建最外层的multipart/mixed邮件 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);
正确的MIME结构
现在的debug结构完全符合标准,所有邮件客户端都能正确解析:
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"
内容的提问来源于stack exchange,提问作者Andrew Newby




