You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

使用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->createparts参数只需要直接传数组引用(\@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

火山引擎 最新活动