El envío de correos electrónicos multiparte (texto / html) a través de wp_mail () probablemente hará que su dominio sea bloqueado

37

Resumen

Debido a un error en WP Core, el envío de correos electrónicos de varias partes (html / text) con wp_mail () (para reducir la posibilidad de que los correos electrónicos terminen en carpetas de spam) resultará irónicamente con el bloqueo de su dominio por Hotmail (y otros correos electrónicos de Microsoft).

Este es un problema complejo que trataré de analizar con gran detalle en un intento de ayudar a alguien a encontrar una solución viable que eventualmente se pueda implementar en el núcleo.

Será una lectura gratificante. Vamos a empezar...

El bicho

El consejo más común para evitar que los correos electrónicos de sus boletines terminen en carpetas de spam es enviar mensajes de varias partes.

Multi-parte (mime) se refiere al envío de una parte HTML y TEXT de un mensaje de correo electrónico en un solo correo electrónico. Cuando un cliente recibe un mensaje de varias partes, acepta la versión HTML si puede representar HTML; de lo contrario, presenta la versión de texto sin formato.

Esto está demostrado que funciona. Al enviar a gmail, todos nuestros correos electrónicos llegaron a carpetas de spam hasta que cambiamos los mensajes a multiparte cuando llegaron a la bandeja de entrada principal. Buena cosa.

Ahora, al enviar mensajes de varias partes a través de wp_mail (), genera el Tipo de contenido (multipart / *) dos veces, una con límite (si se configura de manera personalizada) y otra sin él. Este comportamiento da como resultado que el correo electrónico se muestre como un mensaje sin procesar y no con varias partes en algunos correos electrónicos, incluidos todos los de Microsoft (Hotmail, Outlook, etc.)

Microsoft marcará este mensaje como basura, y los pocos mensajes que lleguen serán marcados manualmente por el destinatario. Desafortunadamente , las direcciones de correo electrónico de Microsoft son ampliamente utilizadas. El 40% de nuestros suscriptores lo usan.

Esto es confirmado por Microsoft a través de un intercambio de correo electrónico que tuvimos recientemente.

El marcado de los mensajes dará como resultado que el dominio esté completamente bloqueado . Esto significa que el mensaje no se enviará a la carpeta de spam, ni siquiera se entregará al destinatario.

Hemos tenido nuestro dominio principal bloqueado 3 veces hasta ahora.

Debido a que este es un error en el núcleo de WP, todos los dominios que envían mensajes de varias partes están siendo bloqueados. El problema es que la mayoría de los webmasters no saben por qué. Lo he confirmado cuando investigo y veo a otros usuarios debatir esto en foros, etc. Requiere profundizar en el código sin procesar y tener un buen conocimiento de cómo funcionan este tipo de mensajes de correo electrónico, que vamos a ver a continuación ...

Vamos a dividirlo en código

Crea una cuenta hotmail / outlook. Luego, ejecute el siguiente código:

// Set $to to an hotmail.com or outlook.com email
$to = "[email protected]";

$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 <[email protected]>\r\n";
$headers .= 'Content-Type: multipart/alternative;boundary="----=_Part_18243133_1346573420.1408991447668"';


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

Y si desea cambiar el tipo de contenido predeterminado , use:

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

Esto enviará un mensaje multiparte.

Entonces, si verifica la fuente sin formato completa del mensaje, notará que el tipo de contenido se agrega dos veces, una vez sin límite:

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

Ese es el problema.

La fuente del problema radica en pluggable.php, si miramos aquí:

// 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;
            }
        }
    }

Posibles soluciones

Entonces te preguntas, ¿por qué no has informado esto en trac ? Yo ya tengo . Para mi gran sorpresa, hace 5 años se creó un boleto diferente que describe el mismo problema.

Seamos realistas, ha pasado media década. En años de internet, eso es más como 30. El problema claramente ha sido abandonado y básicamente nunca se solucionará (... a menos que lo resolvamos aquí).

Encontré un excelente hilo aquí que ofrece una solución, pero aunque su solución funciona, rompe los correos electrónicos que no tienen un $headersconjunto personalizado .

Ahí es donde nos estrellamos cada vez. O bien la versión multiparte funciona bien, y los $headersmensajes normales no configurados no funcionan, o viceversa.

La solución que se nos ocurrió fue:

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

}

Sí, lo sé, la edición de archivos centrales es tabú, siéntate ... esta fue una solución desesperada y un intento pobre de proporcionar una solución para el núcleo.

El problema con nuestra solución es que los correos electrónicos predeterminados como nuevos registros, comentarios, restablecimiento de contraseña, etc. se enviarán como mensajes en blanco. Así que tenemos un script de trabajo wp_mail () que enviará mensajes multiparte pero nada más.

Qué hacer

El objetivo aquí es encontrar una manera de enviar mensajes normales (texto sin formato) y multiparte utilizando la función central wp_mail () (no una función de envío personalizada).

Cuando intente resolver esto, el principal problema que encontrará es la cantidad de tiempo que pasará enviando mensajes ficticios, verificando si se reciben y básicamente abriendo una caja de aspirina y maldiciendo a Microsoft porque está acostumbrado a su Problemas de IE mientras que el gremlin aquí es desafortunadamente WordPress.

Actualizar

La solución publicada por @bonger permite $messageser una matriz que contiene alternativas con clave de tipo de contenido. He confirmado que funciona en todos los escenarios.

Permitiremos que esta pregunta permanezca abierta hasta que se agote la recompensa para crear conciencia sobre el problema, tal vez a un nivel en el que se solucionará en el núcleo. Siéntase libre de publicar una solución alternativa donde $messagepueda ser una cadena.

Christine Cooper
fuente
1
Como la wp_mail()función es enchufable, ¿no está definiendo su reemplazo como un complemento de uso obligatorio (en wp-content / mu-plugins), no es una buena solución para usted (y para todos los demás, falla la corrección del núcleo)? ¿En qué caso no $phpmailer->ContentType = $content_type;movería la verificación de múltiples partes / límites a después de la configuración (en lugar de eliminar) no funcionaría?
Bonger
@bonger ¿Puede escribir una respuesta que detalle su solución?
Christine Cooper
1
No es necesario editar el núcleo, porque wp_mailes conectable . Copie la función original en un complemento, edítela como lo necesite y active el complemento. WordPress usará su función editada en lugar de la original, sin necesidad de editar core.
gmazzap
@ChristineCooper Dudo en hacer esto, ya que usted dice que la prueba es un dolor real, pero mirando el parche core.trac.wordpress.org/ticket/15448 sugerido en el seguimiento por @ rmccue / @ MattyRob que se ve una muy buena manera de ir así que publicaré una respuesta no probada basada en eso ...
bonger
2
@ChristineCooper si simplemente te conectas a phpmailer y configuras el cuerpo del texto en $ phpmailer-> AltBody, ¿ocurre el mismo error?
chifliiiii

Respuestas:

15

La siguiente versión de wp_mail()es con el parche aplicado de @ rmccue / @ MattyRob en el ticket https://core.trac.wordpress.org/ticket/15448 , actualizado para 4.2.2, que permite $messageser una matriz que contiene contenido de tipo alternativas con llave:

/**
 * 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 protected]>' 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 <[email protected]>"
            $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 <[email protected]>"
                $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 <[email protected]>"
                $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;
    }
}

Entonces, si lo coloca en su archivo "wp-content / mu-plugins / functions.php", por ejemplo, anulará la versión de WP. Tiene un buen uso sin perder el tiempo con los encabezados, por ejemplo:

// Set $to to an hotmail.com or outlook.com email
$to = "[email protected]";

$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 '[email protected]'; } );
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 );

Tenga en cuenta que no he probado esto con correos electrónicos reales ...

bonger
fuente
He agregado esto para tener complementos y ejecuté el código de prueba; funcionó. He probado las notificaciones principales predeterminadas (notificación de usuario nuevo, etc.) y también funcionó. Continuaré realizando pruebas este fin de semana y veré cómo funcionarán los complementos con esto y, básicamente, si todo funciona. Veré específicamente los datos sin procesar del mensaje. Esta será una tarea que llevará mucho tiempo, pero tenga la seguridad de que informaré cuando termine. Si hay un escenario en el que wp_mail () no funcionará (cuando de otro modo debería funcionar), avíseme. Gracias por esta respuesta
Christine Cooper
Lo bueno es que he echado un vistazo a la salida y se ve bien; de hecho, el parche solo hace que wp_mail use el procesamiento sólido de roca estándar de PHPMailer en el caso de pasar una matriz, y de lo contrario se predetermina al material WP dudoso (para compatibilidad con versiones anteriores) así que debería ser bueno (obviamente, felicitaciones para los autores del parche) ... voy a usarlo de ahora en adelante (y eventualmente volveré a ajustarlo), y gracias de nuevo por la información sobre el uso de html / plain para reducir las posibilidades de ser asaltado como spam ...
bonger
1
Lo hemos probado en todos los escenarios posibles y está funcionando muy bien. Lanzaremos un boletín mañana y veremos si recibimos alguna queja de los usuarios. Los únicos cambios menores que necesitábamos hacer era desinfectar / desinfectar la matriz cuando se inserta en la base de datos (tener mensajes en una cola en la base de datos donde un cron lo envía en pequeños lotes). Permitiré que esta pregunta permanezca abierta y pendiente hasta que se agote la recompensa para que podamos dar a conocer este problema. Con suerte, este parche o una alternativa se agregará al núcleo. O más importante, por qué no. ¡Qué están pensando!
Christine Cooper
Al azar, noté que realizó una actualización del ticket de seguimiento vinculado. ¿Es esto una actualización de este código? Si es así, ¿podría publicar esta actualización editando su respuesta aquí también para que esta respuesta se mantenga actualizada? Muchas gracias.
Christine Cooper
Hola, no, fue solo una actualización del parche contra el tronco actual para que se fusione sin conflictos (con la esperanza de que reciba algo de atención), el código es exactamente el mismo ...
bonger
4

Esto no es realmente un error de WordPress, es phpmaileruno que no permite encabezados personalizados ... si nos fijamos en 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;
    }

Puede ver que el caso por defecto ofensivo es lo que está generando la línea de encabezado adicional con juego de caracteres y sin límite. Establecer el tipo de contenido por filtro no resuelve esto por sí solo porque el altcaso aquí se activa message_typemarcando AltBodyno está vacío en lugar del tipo de contenido.

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

Al final, lo que esto significa es que tan pronto como adjuntes un archivo o una imagen en línea, o configures el AltBody, el error ofensivo debería omitirse. También significa que no hay necesidad de establecer explícitamente el tipo de contenido porque tan pronto como haya un valor AltBodyestablecido multipart/alternativepor phpmailer.

Entonces la respuesta simple es:

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

Entonces no necesita configurar los encabezados explícitamente, simplemente puede hacer:

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

Desafortunadamente, muchas de las funciones y propiedades de la phpmailerclase están protegidas, de lo contrario, una alternativa válida sería simplemente verificar y anular la MIMEHeaderspropiedad a través del phpmailer_initenlace antes de enviarla.

majick
fuente
2

Acabo de lanzar un complemento para permitir que los usuarios usen plantillas html en WordPress y estoy jugando ahora en la versión de desarrollo para agregar un respaldo de texto simple. Hice lo siguiente y en mis pruebas solo veo un límite agregado y los correos electrónicos llegan bien a 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 );
}

Entonces, básicamente, lo que hago aquí es modificar el objeto phpmailer, cargar el mensaje dentro de una plantilla html y establecerlo en la propiedad Body. También tomé el mensaje original y configuré la propiedad AltBody.

chifliiiii
fuente
2

Mi solución simple es usar html2text https://github.com/soundasleep/html2text de esta manera:

add_action( 'phpmailer_init', 'phpmailer_init' );

//http://wordpress.stackexchange.com/a/191974
//http://stackoverflow.com/a/2564472
function phpmailer_init( $phpmailer )
{
  if( $phpmailer->ContentType == 'text/html' ) {
    $phpmailer->AltBody = Html2Text\Html2Text::convert( $phpmailer->Body );
  }
}

Aquí https://gist.github.com/ewake/6c4d22cd856456480bd77b988b5c9e80 también una idea general.

despertar
fuente
2

Para cualquiera que esté usando el gancho 'phpmailer_init' para agregar su propio 'AltBody':

El cuerpo de texto alternativo se reutiliza para enviar diferentes correos consecutivos, ¡a menos que lo borre manualmente! WordPress no lo borra en wp_mail () porque no espera que se use esta propiedad.

Esto da como resultado que los destinatarios reciban correos potencialmente no destinados a ellos. Afortunadamente, la mayoría de las personas que usan clientes de correo habilitados para HTML no verán la versión de texto, pero sigue siendo básicamente un problema de seguridad.

Afortunadamente hay una solución fácil. Esto incluye el bit de reemplazo de altbody; tenga en cuenta que necesita la biblioteca PHP Html2Text:

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

Aquí también hay un resumen de un complemento WP que modifiqué para solucionar este problema: https://gist.github.com/youri--/c4618740b7c50c549314eaebc9f78661

Desafortunadamente, no puedo comentar sobre las otras soluciones que usan el gancho antes mencionado, para advertirles de esto, ya que aún no tengo suficiente representante para comentar.

Tanuki
fuente
1

Esto podría no ser una respuesta exacta a la publicación inicial aquí, pero es una alternativa a algunas de las soluciones aquí proporcionadas con respecto a la configuración de un cuerpo alternativo

esencialmente, necesitaba (quería) establecer un cuerpo alternativo distinto (es decir, texto sin formato) adicionalmente a la parte html en lugar de confiar en algunas conversiones / striptags y otras cosas. así que se me ocurrió esto que parece funcionar bien

/* 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'];
        }
    }   
}
Olly
fuente
0

Si no desea crear ningún conflicto de código en el núcleo de Wordpress, creo que la solución alternativa o más simple es agregar una acción phpmailer_initque se realice antes del envío real de correo en el wp_mail. Para simplificar mi explicación, vea el siguiente ejemplo de código:

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

Si agrega un contenido en la AltBodypropiedad de clase PHPMailer, el tipo de contenido predeterminado se establecerá automáticamente en multipart/alternative.

Joshua Reyes
fuente