Skip to content

Commit

Permalink
Email fallback. In Progress (#189)
Browse files Browse the repository at this point in the history
* Email fallback. In Progress

* Prepare version 2.1.1
  • Loading branch information
oleksandr-mykhailenko authored Aug 17, 2024
1 parent 6b3fb1b commit e0a5863
Show file tree
Hide file tree
Showing 6 changed files with 271 additions and 28 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
Changelog
=========

2.1.1 (2024-08-17)
- Added fallback to regular mail in case or error during sending email vua API

2.1.0 (2024-07-27)
- Added ability to suppress Track Clicks when we send Reset Password email (it was an issue with domain url in the email)
- Added field to setup Reply-to(header) email for the emails.
Expand Down
281 changes: 257 additions & 24 deletions includes/wp-mail-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -449,39 +449,272 @@ function wp_mail($to, $subject, $message, $headers = '', $attachments = [])
];

$endpoint = mg_api_get_region($region);
$endpoint = ($endpoint) ? $endpoint : 'https://api.mailgun.net/v3/';
$endpoint = ($endpoint) ?: 'https://api.mailgun.net/v3/';
$url = $endpoint . "{$domain}/messages";

// TODO: Mailgun only supports 1000 recipients per request, since we are
// overriding this function, let's add looping here to handle that
$response = wp_remote_post($url, $data);
if (is_wp_error($response)) {
// Store WP error in last error.
mg_api_last_error($response->get_error_message());
$isFallbackNeeded = false;
try {
$response = wp_remote_post($url, $data);
if (is_wp_error($response)) {
// Store WP error in last error.
mg_api_last_error($response->get_error_message());

return false;
}
$isFallbackNeeded = true;
}

$response_code = wp_remote_retrieve_response_code($response);
$response_body = json_decode(wp_remote_retrieve_body($response));
$response_code = wp_remote_retrieve_response_code($response);
$response_body = json_decode(wp_remote_retrieve_body($response));

// Mailgun API should *always* return a `message` field, even when
// $response_code != 200, so a lack of `message` indicates something
// is broken.
if ((int)$response_code != 200 || !isset($response_body->message)) {
// Store response code and HTTP response message in last error.
$response_message = wp_remote_retrieve_response_message($response);
$errmsg = "$response_code - $response_message";
mg_api_last_error($errmsg);
if ((int)$response_code !== 200 || !isset($response_body->message)) {
// Store response code and HTTP response message in last error.
$response_message = wp_remote_retrieve_response_message($response);
$errmsg = "$response_code - $response_message";
mg_api_last_error($errmsg);

return false;
$isFallbackNeeded = true;
}
if ($response_body->message !== 'Queued. Thank you.') {
mg_api_last_error($response_body->message);

$isFallbackNeeded = true;
}
} catch (Throwable $throwable) {
$isFallbackNeeded = true;
}

// Not sure there is any additional checking that needs to be done here, but why not?
if ($response_body->message !== 'Queued. Thank you.') {
mg_api_last_error($response_body->message);
//Email Fallback

return false;
if ($isFallbackNeeded) {
global $phpmailer;

// (Re)create it, if it's gone missing.
if (!($phpmailer instanceof PHPMailer\PHPMailer\PHPMailer)) {
require_once ABSPATH . WPINC . '/PHPMailer/PHPMailer.php';
require_once ABSPATH . WPINC . '/PHPMailer/SMTP.php';
require_once ABSPATH . WPINC . '/PHPMailer/Exception.php';
$phpmailer = new PHPMailer\PHPMailer\PHPMailer(true);

$phpmailer::$validator = static function ($email) {
return (bool)is_email($email);
};
}

// Empty out the values that may be set.
$phpmailer->clearAllRecipients();
$phpmailer->clearAttachments();
$phpmailer->clearCustomHeaders();
$phpmailer->clearReplyTos();
$phpmailer->Body = '';
$phpmailer->AltBody = '';

// Set "From" name and email.

// 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 = wp_parse_url(network_home_url(), PHP_URL_HOST);
$from_email = 'wordpress@';

if (null !== $sitename) {
if (str_starts_with($sitename, 'www.')) {
$sitename = substr($sitename, 4);
}

$from_email .= $sitename;
}
}

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

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

try {
$phpmailer->setFrom($from_email, $from_name, false);
} catch (PHPMailer\PHPMailer\Exception $e) {
$mail_error_data = compact('to', 'subject', 'message', 'headers', 'attachments');
$mail_error_data['phpmailer_exception_code'] = $e->getCode();

/** This filter is documented in wp-includes/pluggable.php */
do_action('wp_mail_failed', new WP_Error('wp_mail_failed', $e->getMessage(), $mail_error_data));

return false;
}

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

// Set destination addresses, using appropriate methods for handling addresses.
$address_headers = compact('to', 'cc', 'bcc', 'replyTo');

foreach ($address_headers as $address_header => $addresses) {
if (empty($addresses)) {
continue;
}

foreach ((array)$addresses as $address) {
try {
// Break $recipient into name and address parts if in the format "Foo <[email protected]>".
$recipient_name = '';

if (preg_match('/(.*)<(.+)>/', $address, $matches)) {
if (count($matches) === 3) {
$recipient_name = $matches[1];
$address = $matches[2];
}
}

switch ($address_header) {
case 'to':
$phpmailer->addAddress($address, $recipient_name);
break;
case 'cc':
$phpmailer->addCc($address, $recipient_name);
break;
case 'bcc':
$phpmailer->addBcc($address, $recipient_name);
break;
case 'reply_to':
$phpmailer->addReplyTo($address, $recipient_name);
break;
}
} catch (PHPMailer\PHPMailer\Exception $e) {
continue;
}
}
}

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

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

/**
* Filters the wp_mail() content type.
* @param string $content_type Default wp_mail() content type.
* @since 2.3.0
*/
$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');
}

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

// Set custom headers.
if (!empty($headers)) {
foreach ((array)$headers as $name => $content) {
// Only add custom headers not added automatically by PHPMailer.
if (!in_array($name, ['MIME-Version', 'X-Mailer'], true)) {
try {
$phpmailer->addCustomHeader(sprintf('%1$s: %2$s', $name, $content));
} catch (PHPMailer\PHPMailer\Exception $e) {
continue;
}
}
}

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

if (!empty($attachments)) {
foreach ($attachments as $filename => $attachment) {
$filename = is_string($filename) ? $filename : '';

try {
$phpmailer->addAttachment($attachment, $filename);
} catch (PHPMailer\PHPMailer\Exception $e) {
continue;
}
}
}

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

$mail_data = compact('to', 'subject', 'message', 'headers', 'attachments');

// Send!
try {
$send = $phpmailer->send();

/**
* Fires after PHPMailer has successfully sent an email.
* The firing of this action does not necessarily mean that the recipient(s) received the
* email successfully. It only means that the `send` method above was able to
* process the request without any errors.
* @param array $mail_data {
* An array containing the email recipient(s), subject, message, headers, and attachments.
* @type string[] $to Email addresses to send message.
* @type string $subject Email subject.
* @type string $message Message contents.
* @type string[] $headers Additional headers.
* @type string[] $attachments Paths to files to attach.
* }
* @since 5.9.0
*/
do_action('wp_mail_succeeded', $mail_data);

return $send;
} catch (PHPMailer\PHPMailer\Exception $e) {
$mail_data['phpmailer_exception_code'] = $e->getCode();

/**
* Fires after a PHPMailer\PHPMailer\Exception is caught.
* @param WP_Error $error A WP_Error object with the PHPMailer\PHPMailer\Exception message, and an array
* containing the mail recipient, subject, message, headers, and attachments.
* @since 4.4.0
*/
do_action('wp_mail_failed', new WP_Error('wp_mail_failed', $e->getMessage(), $mail_data));

return false;
}
}

return true;
Expand Down
4 changes: 3 additions & 1 deletion includes/wp-mail-smtp.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ function wp_mail_failed($error)
if (is_wp_error($error)) {
mg_smtp_last_error($error->get_error_message());
} else {
mg_smtp_last_error($error->__toString());
if (method_exists($error, '__toString')) {
mg_smtp_last_error($error->__toString());
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion mailgun.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Plugin Name: Mailgun
* Plugin URI: http://wordpress.org/extend/plugins/mailgun/
* Description: Mailgun integration for WordPress
* Version: 2.1.0
* Version: 2.1.1
* Requires PHP: 7.4
* Requires at least: 4.4
* Author: Mailgun
Expand Down
5 changes: 4 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Mailgun for WordPress
Contributors: mailgun, sivel, lookahead.io, m35dev, alanfuller
Tags: mailgun, smtp, http, api, mail, email
Tested up to: 6.6.1
Stable tag: 2.1.0
Stable tag: 2.1.1
License: GPLv2 or later

Easily send email from your WordPress site through Mailgun using the HTTP API or SMTP.
Expand Down Expand Up @@ -132,6 +132,9 @@ MAILGUN_REPLY_TO_ADDRESS Type: string

== Changelog ==

= 2.1.1 (2024-08-17): =
- Added fallback to regular mail in case or error during sending email vua API

= 2.1.0 (2024-07-27): =
- Added ability to suppress Track Clicks when we send Reset Password email (it was an issue with domain url in the email)
- Added field to setup Reply-to(header) email for the emails.
Expand Down
4 changes: 3 additions & 1 deletion readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,11 @@ MAILGUN_TRACK_OPENS Type: string Choices: 'yes' or 'no'
5. Using a Subscription Code
6. Subscription Form Seen By Site Visitors


== Changelog ==

= 2.1.1 (2024-08-17): =
- Added fallback to regular mail in case or error during sending email vua API

= 2.1.0 (2024-07-27): =
- Added ability to suppress Track Clicks when we send Reset Password email (it was an issue with domain url in the email)
- Added field to setup Reply-to(header) email for the emails.
Expand Down

0 comments on commit e0a5863

Please sign in to comment.