电子邮件:MIME,用附件多部分发送?

Posted

技术标签:

【中文标题】电子邮件:MIME,用附件多部分发送?【英文标题】:Email:MIME, sending in multipart with attachment? 【发布时间】:2020-06-30 18:59:45 【问题描述】:

这让我发疯了。我一定错过了一些愚蠢的东西。我有以下子:

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


它需要做的是包含电子邮件的纯文本和 HTML 正文。然后,还附加了一个文件(一个 .cgi 仅用于测试:))。

虽然电子邮件在 Gmail 上可以正常发送,但在 Outlook/Thunderbird 上却很糟糕。我有一种感觉,我正在分解“部分”。据我了解,您需要一个“主要”正文部分,可以将其拆分为纯文本和 HTML 版本-然后将附件作为主要“部分”的另一部分。我不太确定如何实现这一目标?

“debug_structure”就是这样出来的:

Structure: + multipart/multipart; boundary="15846317930.c94ff7.26547"
     + text/plain; charset="UTF-8"
     + text/html; charset="UTF-8"
     + text/plain; attachment="attachment"; name="tables.cgi"

更新:按照建议,我现在正在尝试嵌套部分:

# 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";

但我得到一个错误:

无法在 unblessed 引用上调用方法“as_string” /usr/local/share/perl/5.22.1/Email/MIME.pm 第 771 行。

第 771 行在 Email::MIME 中的 parts_set 中 - 所以我一定是做错了什么设置?

更新 2:感谢 Steffen 的帮助!所以这是最终的工作代码,具有正确的结构:

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);

结构现在正确显示为:

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"

【问题讨论】:

【参考方案1】:

没有您使用的 multipart/multipart 这样的东西。您的邮件应具有以下结构:

 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

对于附件,选择更匹配附件类型的content-type 可能会很有用。如果附件实际上是纯文本,那么text/plain 可能没问题,但如果它是图像、办公文档、存档......应该使用不同的content-type

除此之外,encodingcharsetdisposition 在多部分定义中都没有任何意义。这些仅与最终部件(text/plain 等)相关,与容器部件无关(multipart/whatever

    attributes => 
        encoding => 'base64',
        charset  => "UTF-8",
        content_type => "multipart/multipart",
        #disposition  => "inline",
    

【讨论】:

谢谢 - 不知道 multipart/multipart 是怎么进来的 :) 一定是盯着代码太久了!我仍在试图弄清楚如何使用Email::MIME 获得所需的结构。它允许您传入parts =&gt; [\@array_of_parts],但我看不到您如何处理子部分(即文本/html 版本)。我会继续玩 @AndrewNewby:您目前已经使用Email::MIME-&gt;create 创建单个部件并基于这些部件创建多部件。这也可以嵌套完成,即新的多部分中的多部分。 aaah 是的 - 我没有意识到我也可以在其中使用“零件”位。我会试一试:) @AndrewNewby:它不应该是parts =&gt; [\@message_parts],,而是parts =&gt; \@message_parts,,即只是一个数组引用,而不是一个包含数组引用的数组。 啊哈,伙计——我不敢相信有那么愚蠢!有趣的是,文档有(metacpan.org/pod/Email::MIME)Email::MIME-&gt;create( parts =&gt; [ @parts ] ) - 那一定是我从那里得到 [] 位的 - 呃!它现在可以完美导出 - 非常感谢 :)

以上是关于电子邮件:MIME,用附件多部分发送?的主要内容,如果未能解决你的问题,请参考以下文章

带有附件问题的 PHP Pear Mime 邮件

如何编写多部分 MIME 混合消息以在 Outlook 中正确显示

我可以将 S/MIME 作为多部分/混合消息的一部分吗?

附件可以在 MIME 中的嵌套多部分中吗?

在 iOS 上为亚马逊 SES 创建多部分/混合 MIME

将 MIME 中的附加数据嵌入到电子邮件的 HTML 部分。未链接到附件