通过wp_mail()发送多部分(文本/ html)电子邮件可能会导致您的域被禁止


37

摘要

由于WP Core中存在一个错误,具有讽刺意味的是,您的域被Hotmail(和其他Microsoft电子邮件)阻止了,因此使用wp_mail()发送多部分电子邮件(html /文本)(以减少电子邮件最终进入垃圾邮件文件夹的可能性)。

这是一个复杂的问题,我将详细分解以帮助某人找到可行的解决方案,并最终在核心中实现。

这将是一个有益的阅读。让我们开始...

错误

避免使时事通讯电子邮件最终进入垃圾邮件文件夹的最常见建议是发送多部分邮件。

多部分(MIME)是指在一封电子邮件中同时发送电子邮件的HTML和TEXT部分。客户端收到多部分消息时,如果可以呈现HTML,则接受HTML版本,否则将呈现纯文本版本。

事实证明这是可行的。当发送到gmail时,我们所有的电子邮件都将放入垃圾邮件文件夹,直到当它们到达主收件箱时我们将邮件更改为多部分。好东西。

现在,当通过wp_mail()发送多部分消息时,它将两次输出内容类型(multipart / *),一次是带有边界(如果是自定义设置的),一次是没有边界的。此行为导致电子邮件被显示为原始消息,而不是包括某些所有 Microsoft(Hotmail,Outlook等)在内的某些电子邮件的一部分。

Microsoft将将此邮件标记为垃圾邮件,而接收到的少量邮件将由收件人手动标记。不幸的是,Microsoft电子邮件地址被广泛使用。我们有40%的订户使用它。

微软通过我们最近进行的电子邮件交换确认了这一点。

标记消息将导致域被完全阻止。这意味着该邮件将不会发送到垃圾邮件文件夹,甚至根本不会发送给收件人。

到目前为止,我们已经屏蔽了3次主域名。

因为这是WP核心中的错误,所以发送多部分消息的每个域都被阻止。问题是大多数网站管理员都不知道为什么。我在进行研究时发现这一点,并看到其他用户在论坛等上进行讨论。这需要深入研究原始代码,并对这些电子邮件的工作方式有充分的了解,我们将继续进行下一步...

让我们将其分解为代码

创建一个hotmail / outlook帐户。然后,运行以下代码:

// Set $to to an hotmail.com or outlook.com email
$to = "YourEmail@hotmail.com";

$subject = 'wp_mail testing multipart';

$message = '------=_Part_18243133_1346573420.1408991447668
Content-Type: text/plain; charset=UTF-8

Hello world! This is plain text...


------=_Part_18243133_1346573420.1408991447668
Content-Type: text/html; charset=UTF-8

<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>

<p>Hello World! This is HTML...</p> 

</body>
</html>


------=_Part_18243133_1346573420.1408991447668--';

$headers = "MIME-Version: 1.0\r\n";
$headers .= "From: Foo <foo@bar.com>\r\n";
$headers .= 'Content-Type: multipart/alternative;boundary="----=_Part_18243133_1346573420.1408991447668"';


// send email
wp_mail( $to, $subject, $message, $headers );

如果要更改默认的内容类型,请使用:

add_filter( 'wp_mail_content_type', 'set_content_type' );
function set_content_type( $content_type ) {
    return 'multipart/alternative';
}

这将发送多部分消息。

因此,如果检查消息的完整原始资源,您会注意到内容类型被添加了两次,一次没有边界:

MIME-Version: 1.0
Content-Type: multipart/alternative;
         boundary="====f230673f9d7c359a81ffebccb88e5d61=="
MIME-Version: 1.0
Content-Type: multipart/alternative; charset=

这就是问题。

问题的根源在于pluggable.php-如果我们在这里查看:

// Set Content-Type and charset
    // If we don't have a content-type from the input headers
    if ( !isset( $content_type ) )
        $content_type = 'text/plain';

    /**
     * Filter the wp_mail() content type.
     *
     * @since 2.3.0
     *
     * @param string $content_type Default wp_mail() content type.
     */
    $content_type = apply_filters( 'wp_mail_content_type', $content_type );

    $phpmailer->ContentType = $content_type;

    // Set whether it's plaintext, depending on $content_type
    if ( 'text/html' == $content_type )
        $phpmailer->IsHTML( true );

    // If we don't have a charset from the input headers
    if ( !isset( $charset ) )
        $charset = get_bloginfo( 'charset' );

    // Set the content-type and charset

    /**
     * Filter the default wp_mail() charset.
     *
     * @since 2.3.0
     *
     * @param string $charset Default email charset.
     */
    $phpmailer->CharSet = apply_filters( 'wp_mail_charset', $charset );

    // Set custom headers
    if ( !empty( $headers ) ) {
        foreach( (array) $headers as $name => $content ) {
            $phpmailer->AddCustomHeader( sprintf( '%1$s: %2$s', $name, $content ) );
        }

        if ( false !== stripos( $content_type, 'multipart' ) && ! empty($boundary) )
            $phpmailer->AddCustomHeader( sprintf( "Content-Type: %s;\n\t boundary=\"%s\"", $content_type, $boundary ) );
    }

    if ( !empty( $attachments ) ) {
        foreach ( $attachments as $attachment ) {
            try {
                $phpmailer->AddAttachment($attachment);
            } catch ( phpmailerException $e ) {
                continue;
            }
        }
    }

潜在解决方案

所以您想知道,为什么不在Trac上报告此情况?已经有。令我惊讶的是,五年前创建了另一张票证,概述了相同的问题。

面对现实,已经过去了五年。在互联网时代,大约是30岁。这个问题显然已经被放弃了,并且基本上永远不会得到解决(...除非我们在这里解决,否则)。

我在这里找到了一个提供解决方案的好线程,但是尽管他的解决方案有效,但它破坏了没有自定义$headers设置的电子邮件。

那就是我们每次崩溃的地方。多部分版本可以正常工作,而普通未设置的$headers消息则行不通,或者不推荐使用。

我们想到的解决方案是:

if ( false !== stripos( $content_type, 'multipart' ) && ! empty($boundary) ) {
    $phpmailer->ContentType = $content_type . "; boundary=" . $boundary;
}
else {

        $content_type = apply_filters( 'wp_mail_content_type', $content_type );

    $phpmailer->ContentType = $content_type;

    // Set whether it's plaintext, depending on $content_type
    if ( 'text/html' == $content_type )
        $phpmailer->IsHTML( true );

    // If we don't have a charset from the input headers
    if ( !isset( $charset ) )
        $charset = get_bloginfo( 'charset' );
}

// Set the content-type and charset

/**
 * Filter the default wp_mail() charset.
 *
 * @since 2.3.0
 *
 * @param string $charset Default email charset.
 */
$phpmailer->CharSet = apply_filters( 'wp_mail_charset', $charset );

// Set custom headers
if ( !empty( $headers ) ) {
    foreach( (array) $headers as $name => $content ) {
        $phpmailer->AddCustomHeader( sprintf( '%1$s: %2$s', $name, $content ) );
    }

}

是的,我知道,编辑核心文件是忌讳的,请坐下来……这是一个绝望的修复,并且是为内核提供修复的较差尝试。

我们的修复程序存在的问题是,默认电子邮件(如新注册,评论,密码重置等)将作为空白邮件发送。因此,我们有一个有效的wp_mail()脚本,该脚本将发送多部分消息,但没有其他内容。

该怎么办

这里的目的是找到一种使用核心wp_mail()函数(而不是自定义sendmail函数)发送常规(纯文本)消息和多部分消息的方法

尝试解决此问题时,您将遇到的主要问题是花在发送虚假消息,检查是否收到虚假消息以及基本上打开一盒阿司匹林并在Microsoft受到诅咒的时间,因为您已经习惯了IE问题而不幸的是,这里的gremlin是WordPress。

更新资料

@bonger发布的解决方案允许$message是一个包含内容类型键控替代项的数组。我已经确认它可以在所有情况下使用。

我们将允许悬而未决的问题悬而未决,直到赏金用完为止,以提高人们对该问题的认识,也许将其提高到可以解决核心问题的程度。随时发布一个替代解决方案,其中$message可以是一个字符串。


1
由于该wp_mail()功能是可插拔的,不是将替代品定义为必须使用的插件(在wp-content / mu-plugins中)对您来说不是一个好的解决方案(以及其他所有人,都无法通过核心修复)?在哪种情况下,设置$phpmailer->ContentType = $content_type;(而不是选择)后不能将多部分/边界检查移至?
邦吉2015年

@bonger您能写出详细说明您的解决方案的答案吗?
克里斯汀·库珀

1
不需要编辑core,因为wp_mail可以插入。将原始功能复制到插件中,根据需要进行编辑并激活插件。WordPress将使用您编辑的功能而不是原始功能,而无需编辑核心。
gmazzap

@ChristineCooper我毫不犹豫地这样做,因为像你说的测试是这样一个皇家疼痛,但看着补丁core.trac.wordpress.org/ticket/15448在TRAC建议由@ rmccue / @ MattyRob,看起来非常好的办法走吧,所以我将根据这个结果发布未经测试的答案...
bonger 2015年

2
@ChristineCooper如果您简单地挂接到phpmailer并在$ phpmailer-> AltBody中设置文本正文,是否会发生相同的错误?
chifliiiii

Answers:


15

以下版本的wp_mail()带有票证https://core.trac.wordpress.org/ticket/15448中@ rmccue / @ MattyRob的修补程序,已更新为4.2.2,该$message数组允许为包含内容类型的数组键控替代项:

/**
 * Send mail, similar to PHP's mail
 *
 * A true return value does not automatically mean that the user received the
 * email successfully. It just only means that the method used was able to
 * process the request without any errors.
 *
 * Using the two 'wp_mail_from' and 'wp_mail_from_name' hooks allow from
 * creating a from address like 'Name <email@address.com>' when both are set. If
 * just 'wp_mail_from' is set, then just the email address will be used with no
 * name.
 *
 * The default content type is 'text/plain' which does not allow using HTML.
 * However, you can set the content type of the email by using the
 * 'wp_mail_content_type' filter.
 *
 * If $message is an array, the key of each is used to add as an attachment
 * with the value used as the body. The 'text/plain' element is used as the
 * text version of the body, with the 'text/html' element used as the HTML
 * version of the body. All other types are added as attachments.
 *
 * The default charset is based on the charset used on the blog. The charset can
 * be set using the 'wp_mail_charset' filter.
 *
 * @since 1.2.1
 *
 * @uses PHPMailer
 *
 * @param string|array $to Array or comma-separated list of email addresses to send message.
 * @param string $subject Email subject
 * @param string|array $message Message contents
 * @param string|array $headers Optional. Additional headers.
 * @param string|array $attachments Optional. Files to attach.
 * @return bool Whether the email contents were sent successfully.
 */
function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() ) {
    // Compact the input, apply the filters, and extract them back out

    /**
     * Filter the wp_mail() arguments.
     *
     * @since 2.2.0
     *
     * @param array $args A compacted array of wp_mail() arguments, including the "to" email,
     *                    subject, message, headers, and attachments values.
     */
    $atts = apply_filters( 'wp_mail', compact( 'to', 'subject', 'message', 'headers', 'attachments' ) );

    if ( isset( $atts['to'] ) ) {
        $to = $atts['to'];
    }

    if ( isset( $atts['subject'] ) ) {
        $subject = $atts['subject'];
    }

    if ( isset( $atts['message'] ) ) {
        $message = $atts['message'];
    }

    if ( isset( $atts['headers'] ) ) {
        $headers = $atts['headers'];
    }

    if ( isset( $atts['attachments'] ) ) {
        $attachments = $atts['attachments'];
    }

    if ( ! is_array( $attachments ) ) {
        $attachments = explode( "\n", str_replace( "\r\n", "\n", $attachments ) );
    }
    global $phpmailer;

    // (Re)create it, if it's gone missing
    if ( ! ( $phpmailer instanceof PHPMailer ) ) {
        require_once ABSPATH . WPINC . '/class-phpmailer.php';
        require_once ABSPATH . WPINC . '/class-smtp.php';
        $phpmailer = new PHPMailer( true );
    }

    // Headers
    if ( empty( $headers ) ) {
        $headers = array();
    } else {
        if ( !is_array( $headers ) ) {
            // Explode the headers out, so this function can take both
            // string headers and an array of headers.
            $tempheaders = explode( "\n", str_replace( "\r\n", "\n", $headers ) );
        } else {
            $tempheaders = $headers;
        }
        $headers = array();
        $cc = array();
        $bcc = array();

        // If it's actually got contents
        if ( !empty( $tempheaders ) ) {
            // Iterate through the raw headers
            foreach ( (array) $tempheaders as $header ) {
                if ( strpos($header, ':') === false ) {
                    if ( false !== stripos( $header, 'boundary=' ) ) {
                        $parts = preg_split('/boundary=/i', trim( $header ) );
                        $boundary = trim( str_replace( array( "'", '"' ), '', $parts[1] ) );
                    }
                    continue;
                }
                // Explode them out
                list( $name, $content ) = explode( ':', trim( $header ), 2 );

                // Cleanup crew
                $name    = trim( $name    );
                $content = trim( $content );

                switch ( strtolower( $name ) ) {
                    // Mainly for legacy -- process a From: header if it's there
                    case 'from':
                        $bracket_pos = strpos( $content, '<' );
                        if ( $bracket_pos !== false ) {
                            // Text before the bracketed email is the "From" name.
                            if ( $bracket_pos > 0 ) {
                                $from_name = substr( $content, 0, $bracket_pos - 1 );
                                $from_name = str_replace( '"', '', $from_name );
                                $from_name = trim( $from_name );
                            }

                            $from_email = substr( $content, $bracket_pos + 1 );
                            $from_email = str_replace( '>', '', $from_email );
                            $from_email = trim( $from_email );

                        // Avoid setting an empty $from_email.
                        } elseif ( '' !== trim( $content ) ) {
                            $from_email = trim( $content );
                        }
                        break;
                    case 'content-type':
                        if ( is_array($message) ) {
                            // Multipart email, ignore the content-type header
                            break;
                        }
                        if ( strpos( $content, ';' ) !== false ) {
                            list( $type, $charset_content ) = explode( ';', $content );
                            $content_type = trim( $type );
                            if ( false !== stripos( $charset_content, 'charset=' ) ) {
                                $charset = trim( str_replace( array( 'charset=', '"' ), '', $charset_content ) );
                            } elseif ( false !== stripos( $charset_content, 'boundary=' ) ) {
                                $boundary = trim( str_replace( array( 'BOUNDARY=', 'boundary=', '"' ), '', $charset_content ) );
                                $charset = '';
                            }

                        // Avoid setting an empty $content_type.
                        } elseif ( '' !== trim( $content ) ) {
                            $content_type = trim( $content );
                        }
                        break;
                    case 'cc':
                        $cc = array_merge( (array) $cc, explode( ',', $content ) );
                        break;
                    case 'bcc':
                        $bcc = array_merge( (array) $bcc, explode( ',', $content ) );
                        break;
                    default:
                        // Add it to our grand headers array
                        $headers[trim( $name )] = trim( $content );
                        break;
                }
            }
        }
    }

    // Empty out the values that may be set
    $phpmailer->ClearAllRecipients();
    $phpmailer->ClearAttachments();
    $phpmailer->ClearCustomHeaders();
    $phpmailer->ClearReplyTos();

    $phpmailer->Body= '';
    $phpmailer->AltBody= '';

    // From email and name
    // If we don't have a name from the input headers
    if ( !isset( $from_name ) )
        $from_name = 'WordPress';

    /* If we don't have an email from the input headers default to wordpress@$sitename
     * Some hosts will block outgoing mail from this address if it doesn't exist but
     * there's no easy alternative. Defaulting to admin_email might appear to be another
     * option but some hosts may refuse to relay mail from an unknown domain. See
     * https://core.trac.wordpress.org/ticket/5007.
     */

    if ( !isset( $from_email ) ) {
        // Get the site domain and get rid of www.
        $sitename = strtolower( $_SERVER['SERVER_NAME'] );
        if ( substr( $sitename, 0, 4 ) == 'www.' ) {
            $sitename = substr( $sitename, 4 );
        }

        $from_email = 'wordpress@' . $sitename;
    }

    /**
     * Filter the email address to send from.
     *
     * @since 2.2.0
     *
     * @param string $from_email Email address to send from.
     */
    $phpmailer->From = apply_filters( 'wp_mail_from', $from_email );

    /**
     * Filter the name to associate with the "from" email address.
     *
     * @since 2.3.0
     *
     * @param string $from_name Name associated with the "from" email address.
     */
    $phpmailer->FromName = apply_filters( 'wp_mail_from_name', $from_name );

    // Set destination addresses
    if ( !is_array( $to ) )
        $to = explode( ',', $to );

    foreach ( (array) $to as $recipient ) {
        try {
            // Break $recipient into name and address parts if in the format "Foo <bar@baz.com>"
            $recipient_name = '';
            if( preg_match( '/(.*)<(.+)>/', $recipient, $matches ) ) {
                if ( count( $matches ) == 3 ) {
                    $recipient_name = $matches[1];
                    $recipient = $matches[2];
                }
            }
            $phpmailer->AddAddress( $recipient, $recipient_name);
        } catch ( phpmailerException $e ) {
            continue;
        }
    }

    // If we don't have a charset from the input headers
    if ( !isset( $charset ) )
        $charset = get_bloginfo( 'charset' );

    // Set the content-type and charset

    /**
     * Filter the default wp_mail() charset.
     *
     * @since 2.3.0
     *
     * @param string $charset Default email charset.
     */
    $phpmailer->CharSet = apply_filters( 'wp_mail_charset', $charset );

    // Set mail's subject and body
    $phpmailer->Subject = $subject;

    if ( is_string($message) ) {
        $phpmailer->Body = $message;

        // Set Content-Type and charset
        // If we don't have a content-type from the input headers
        if ( !isset( $content_type ) )
            $content_type = 'text/plain';

        /**
         * Filter the wp_mail() content type.
         *
         * @since 2.3.0
         *
         * @param string $content_type Default wp_mail() content type.
         */
        $content_type = apply_filters( 'wp_mail_content_type', $content_type );

        $phpmailer->ContentType = $content_type;

        // Set whether it's plaintext, depending on $content_type
        if ( 'text/html' == $content_type )
            $phpmailer->IsHTML( true );

        // For backwards compatibility, new multipart emails should use
        // the array style $message. This never really worked well anyway
        if ( false !== stripos( $content_type, 'multipart' ) && ! empty($boundary) )
            $phpmailer->AddCustomHeader( sprintf( "Content-Type: %s;\n\t boundary=\"%s\"", $content_type, $boundary ) );
    }
    elseif ( is_array($message) ) {
        foreach ($message as $type => $bodies) {
            foreach ((array) $bodies as $body) {
                if ($type === 'text/html') {
                    $phpmailer->Body = $body;
                }
                elseif ($type === 'text/plain') {
                    $phpmailer->AltBody = $body;
                }
                else {
                    $phpmailer->AddAttachment($body, '', 'base64', $type);
                }
            }
        }
    }

    // Add any CC and BCC recipients
    if ( !empty( $cc ) ) {
        foreach ( (array) $cc as $recipient ) {
            try {
                // Break $recipient into name and address parts if in the format "Foo <bar@baz.com>"
                $recipient_name = '';
                if( preg_match( '/(.*)<(.+)>/', $recipient, $matches ) ) {
                    if ( count( $matches ) == 3 ) {
                        $recipient_name = $matches[1];
                        $recipient = $matches[2];
                    }
                }
                $phpmailer->AddCc( $recipient, $recipient_name );
            } catch ( phpmailerException $e ) {
                continue;
            }
        }
    }

    if ( !empty( $bcc ) ) {
        foreach ( (array) $bcc as $recipient) {
            try {
                // Break $recipient into name and address parts if in the format "Foo <bar@baz.com>"
                $recipient_name = '';
                if( preg_match( '/(.*)<(.+)>/', $recipient, $matches ) ) {
                    if ( count( $matches ) == 3 ) {
                        $recipient_name = $matches[1];
                        $recipient = $matches[2];
                    }
                }
                $phpmailer->AddBcc( $recipient, $recipient_name );
            } catch ( phpmailerException $e ) {
                continue;
            }
        }
    }

    // Set to use PHP's mail()
    $phpmailer->IsMail();

    // Set custom headers
    if ( !empty( $headers ) ) {
        foreach ( (array) $headers as $name => $content ) {
            $phpmailer->AddCustomHeader( sprintf( '%1$s: %2$s', $name, $content ) );
        }
    }

    if ( !empty( $attachments ) ) {
        foreach ( $attachments as $attachment ) {
            try {
                $phpmailer->AddAttachment($attachment);
            } catch ( phpmailerException $e ) {
                continue;
            }
        }
    }

    /**
     * Fires after PHPMailer is initialized.
     *
     * @since 2.2.0
     *
     * @param PHPMailer &$phpmailer The PHPMailer instance, passed by reference.
     */
    do_action_ref_array( 'phpmailer_init', array( &$phpmailer ) );

    // Send!
    try {
        return $phpmailer->Send();
    } catch ( phpmailerException $e ) {
        return false;
    }
}

因此,如果将其放在例如“ wp-content / mu-plugins / functions.php”文件中,则它将覆盖WP版本。它具有很好的用法,并且不会弄乱标题,例如:

// Set $to to an hotmail.com or outlook.com email
$to = "YourEmail@hotmail.com";

$subject = 'wp_mail testing multipart';

$message['text/plain'] = 'Hello world! This is plain text...';
$message['text/html'] = '<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>

<p>Hello World! This is HTML...</p> 

</body>
</html>';

add_filter( 'wp_mail_from', $from_func = function ( $from_email ) { return 'foo@bar.com'; } );
add_filter( 'wp_mail_from_name', $from_name_func = function ( $from_name ) { return 'Foo'; } );

// send email
wp_mail( $to, $subject, $message );

remove_filter( 'wp_mail_from', $from_func );
remove_filter( 'wp_mail_from_name', $from_name_func );

请注意,我尚未使用实际的电子邮件进行测试...


我添加了它,使其必须具有插件并运行了测试代码;有效。我已经测试了默认的核心通知(新用户通知等),并且它也起作用。我将在本周末继续进行测试,并查看插件将如何工作以及基本上一切正常。我将特别浏览消息的原始数据。这将是一项非常耗时的任务,但是请放心,我会在完成后向您报告。如果存在wp_mail()无法正常工作的情况(本来应该这样),请告诉我。感谢您的回答。
克里斯汀·库珀

好东西,我看了看输出,它看起来不错-实际上,在传递数组的情况下,该补丁仅使wp_mail使用PHPMailer的标准坚如磐石处理,否则默认为狡猾的WP东西(为了向后兼容)所以应该很好(显然,这里的补丁作者感到很荣幸)...我将从现在开始使用它(并最终进行改装)-并感谢您再次使用html / plain减少被视为垃圾邮件的机率...
bonger 2015年

1
我们已经在所有可能的情况下对其进行了测试,并且效果很好。明天我们将发布新闻通讯,看看是否收到用户的投诉。我们需要做的唯一较小的更改是在将数组插入db中时对它进行清理/清理(在que中有消息,cron在其中将cron批量发送出去)。我将允许悬而未决的问题悬而未决,直到赏金用完为止,以便我们提高对此问题的认识。希望此补丁或替代方法将添加到核心中。更重要的是,为什么不呢?他们在想什么!
克里斯汀·库珀

我随机注意到您对链接的跟踪单进行了更新。这是此代码的更新吗?如果是这样,您是否也可以通过在此处编辑答案来发布此更新,以使该答案保持最新?非常感谢你。
克里斯汀·库珀

嗨,不,这只是针对当前主干的补丁更新,以便其合并而不会发生冲突(希望引起注意),代码完全相同...
bonger

4

这根本不是一个WordPress错误,它是一个phpmailer不允许自定义标头的问题……如果您查看class-phpmailer.php

public function getMailMIME()
{
    $result = '';
    $ismultipart = true;
    switch ($this->message_type) {
        case 'inline':
            $result .= $this->headerLine('Content-Type', 'multipart/related;');
            $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
            break;
        case 'attach':
        case 'inline_attach':
        case 'alt_attach':
        case 'alt_inline_attach':
            $result .= $this->headerLine('Content-Type', 'multipart/mixed;');
            $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
            break;
        case 'alt':
        case 'alt_inline':
            $result .= $this->headerLine('Content-Type', 'multipart/alternative;');
            $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
            break;
        default:
            // Catches case 'plain': and case '':
            $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet);
            $ismultipart = false;
            break;
    }

您可以看到令人讨厌的默认情况是输出带有字符集且无边界的额外标题行的情况。仅通过过滤器设置内容类型本身并不能解决此问题,这是因为alt此处的情况是message_type通过检查AltBody不是空的而是内容类型来设置的。

protected function setMessageType()
{
    $type = array();
    if ($this->alternativeExists()) {
        $type[] = 'alt';
    }
    if ($this->inlineImageExists()) {
        $type[] = 'inline';
    }
    if ($this->attachmentExists()) {
        $type[] = 'attach';
    }
    $this->message_type = implode('_', $type);
    if ($this->message_type == '') {
        $this->message_type = 'plain';
    }
}

public function alternativeExists()
{
    return !empty($this->AltBody);
}

最后,这意味着一旦附加了文件或嵌入式图像或设置了AltBody,就应该绕过令人讨厌的错误。这也意味着不需要显式设置内容类型,因为一旦将AltBody其设置为multipart/alternativeby phpmailer

因此,简单的答案是:

add_action('phpmailer_init','wp_mail_set_text_body');
function wp_mail_set_text_body($phpmailer) {
     if (empty($phpmailer->AltBody)) {$phpmailer->AltBody = strip_tags($phpmailer->Body);}
}

然后,您无需显式设置标头,只需执行以下操作:

 $message ='<html>
 <head>
     <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
 </head>
 <body>
     <p>Hello World! This is HTML...</p> 
 </body>
 </html>';

 wp_mail($to,$subject,$message);

不幸的是,该类中的许多函数和属性phpmailer都受到保护,否则,一个有效的选择就是在发送之前MIMEHeaders通过phpmailer_init钩子简单地检查和覆盖属性。


2

我刚刚发布了一个插件,使用户可以在WordPress和Im上使用html模板,现在可以在dev版本上播放,以添加简单的文本后备广告。我做了以下工作,在测试中,我只看到添加了一个边界,并且电子邮件对Hotmail的接收效果很好。

add_action( 'phpmailer_init', array($this->mailer, 'send_email' ) );

/**
* Modify php mailer body with final email
*
* @since 1.0.0
* @param object $phpmailer
*/
function send_email( $phpmailer ) {

    $message            =  $this->add_template( apply_filters( 'mailtpl/email_content', $phpmailer->Body ) );
    $phpmailer->AltBody =  $this->replace_placeholders( strip_tags($phpmailer->Body) );
    $phpmailer->Body    =  $this->replace_placeholders( $message );
}

因此,基本上,我在这里所做的就是修改phpmailer对象,在html模板中加载消息并将其设置为Body属性。我也接受了原始消息并设置了AltBody属性。



2

对于使用“ phpmailer_init”钩子添加自己的“ AltBody”的任何人:

替代文本正文可重复用于发送的不同连续邮件,除非您手动清除它!WordPress不会在wp_mail()中清除它,因为它不希望使用此属性。

这导致收件人可能收到不适合他们的邮件。幸运的是,大多数使用启用HTML的邮件客户端的人都看不到文本版本,但是从根本上来说,这仍然是一个安全问题。

幸运的是,有一个简单的解决方法。这包括高身替换位;请注意,您确实需要Html2Text PHP库:

add_filter( 'wp_mail', 'wpse191923_force_phpmailer_reinit_for_multiple_mails', -1 );
function wpse191923_force_phpmailer_reinit_for_multiple_mails( $wp_mail_atts ) {
  global $phpmailer;

  if ( $phpmailer instanceof PHPMailer && $phpmailer->alternativeExists() ) {
    // AltBody property is set, so WordPress must already have used this
    // $phpmailer object just now to send mail, so let's
    // clear the AltBody property
    $phpmailer->AltBody = '';
  }

  // Return untouched atts
  return $wp_mail_atts;
}

add_action( 'phpmailer_init', 'wpse191923_phpmailer_init_altbody', 1000, 1 );
function wpse191923_phpmailer_init_altbody( $phpmailer ) {
  if ( ( $phpmailer->ContentType == 'text/html' ) && empty( $phpmailer->AltBody ) ) {
    if ( ! class_exists( 'Html2Text\Html2Text' ) ) {
      require_once( 'Html2Text.php' );
    }
    if ( ! class_exists( 'Html2Text\Html2TextException' ) ) {
      require_once( 'Html2TextException.php' );
    }
    $phpmailer->AltBody = Html2Text\Html2Text::convert( $phpmailer->Body );
  }
}

这也是我修改以解决此问题的WP插件的要点:https : //gist.github.com/youri--/c4618740b7c50c549314eaebc9f78661

不幸的是,由于我没有足够的代表要发表评论,因此我无法对使用上述挂钩的其他解决方案发表评论,以警告他们。


1

这可能不是这里最初文章的确切答案,但是它是此处提供的有关设置替代主体的一些解决方案的替代方案

本质上,我需要(希望)在html部分之外另外设置一个不同的替代体(即纯文本),而不是依赖于某些转换/标签和诸如此类的东西。所以我想出了这似乎很好

/* setting the message parts for wp_mail()*/
$markup = array();
$markup['html'] = '<html>some html</html>';
$markup['plaintext'] = 'some plaintext';
/* message we are sending */    
$message = maybe_serialize($markup);


/* setting alt body distinctly */
add_action('phpmailer_init', array($this, 'set_alt_mail_body'));

function set_alt_mail_body($phpmailer){
    if( $phpmailer->ContentType == 'text/html' ) {
        $body_parts = maybe_unserialize($phpmailer->Body);

        if(!empty($body_parts['html'])){
            $phpmailer->MsgHTML($body_parts['html']);
        }

        if(!empty($body_parts['plaintext'])){
            $phpmailer->AltBody = $body_parts['plaintext'];
        }
    }   
}

0

如果您不想在Wordpress核心中创建任何代码冲突,我认为替代或最简单的解决方案是phpmailer_init在实际发送邮件之前对其添加操作wp_mail。为了简化我的解释,请参见以下代码示例:

<?php 

$to = '';
$subject = '';
$from = '';
$body = 'The text html content, <html>...';

$headers = "FROM: {$from}";

add_action( 'phpmailer_init', function ( $phpmailer ) {
    $phpmailer->AltBody = 'The text plain content of your original text html content.';
} );

wp_mail($to, $subject, $body, $headers);

如果您在PHPMailer类AltBody属性中添加内容,则默认内容类型将自动设置为multipart/alternative

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.