Add events so that the success or failure can be tracked

Created on 6 June 2024, 6 months ago
Updated 12 June 2024, 5 months ago

Problem/Motivation

We use Mailgun to deliver emails triggered by submissions to Webform. However we have no way of knowing whether an email has been accepted by the API or not. The only way is to try to compare the logs in Mailgun (which only last a short time) and the error logs to try to see if an email failed to be sent to the API.

Steps to reproduce

Create a Webform that has am email handler and send that email through Mailgun - once the email gets to the Maligun module there is no way of linking the Mailgun API call success or failure with the module that sent out the email.

Proposed resolution

There are two elements required for a module sending out emails to track what has happened to them in the API.

1. Third party modules need to be able to alter the mailgun message array to add custom v:my-var parameters to the array which can then be passed through in:

2. Success and Error events triggered in \Drupal\mailgun\MailgunHandler::sendMail that either pass the success response containing the mailgun api id of the email or the exception if the API rejected it along with the mailgun message array containing the 'v:my-var' parameters added earlier.

Part 1 requires simply adding an alter hook to the end of the \Drupal\mailgun\Plugin\Mail\MailgunMail::buildMessage so that third party modules can check the original message array and if it is a message from them add v;my-var parameters to track the mail later:

diff --git a/src/Plugin/Mail/MailgunMail.php b/src/Plugin/Mail/MailgunMail.php
index 1620479..719f673 100644
--- a/src/Plugin/Mail/MailgunMail.php
+++ b/src/Plugin/Mail/MailgunMail.php
@@ -293,6 +293,8 @@ class MailgunMail implements MailInterface, ContainerFactoryPluginInterface {
       $mailgun_message['o:tracking'] = 'no';
     }
 
+    \Drupal::moduleHandler()->alter('mailgun_message', $mailgun_message, $message);
+
     return $mailgun_message;
   }

In a custom mailgun_webform_submission module you can then have:

function mailgun_webform_submission_mailgun_message_alter(&$mailgun_message, $message) {
  if ($message['module'] == 'webform') {
    if (isset($message['params']['webform_submission'])
      && $message['params']['webform_submission'] instanceof WebformSubmission) {
      $mailgun_message['v:webform_submission'] = $message['params']['webform_submission']->id();
    }

    if (isset($message['params']['handler'])
      && $message['params']['handler'] instanceof EmailWebformHandler) {
      $mailgun_message['v:email_webform_handler'] = $message['params']['handler']->getHandlerId();
    }
  }
}

For part 2 we need to add Success and Error events and then dispatch them appropriately from \Drupal\mailgun\MailgunHandler::sendMail

diff --git a/mailgun.services.yml b/mailgun.services.yml
index 6e39a2b..4cd1710 100644
--- a/mailgun.services.yml
+++ b/mailgun.services.yml
@@ -7,7 +7,7 @@ services:
     factory: mailgun.mailgun_client_factory:create
   mailgun.mail_handler:
     class: Drupal\mailgun\MailgunHandler
-    arguments: ['@mailgun.mailgun_client', '@config.factory', '@logger.channel.mailgun', '@messenger', '@email.validator']
+    arguments: ['@mailgun.mailgun_client', '@config.factory', '@logger.channel.mailgun', '@messenger', '@email.validator', '@event_dispatcher']
   logger.channel.mailgun:
     class: Drupal\Core\Logger\LoggerChannel
     factory: logger.factory:get
diff --git a/src/Event/MailgunErrorEvent.php b/src/Event/MailgunErrorEvent.php
new file mode 100644
index 0000000..cec4a02
--- /dev/null
+++ b/src/Event/MailgunErrorEvent.php
@@ -0,0 +1,57 @@
+<?php
+
+namespace Drupal\mailgun\Event;
+
+use Drupal\Component\EventDispatcher\Event;
+
+class MailgunErrorEvent extends Event {
+
+  const ERROR = 'mailgun.send.error';
+
+  /**
+   * Array containing the Mailgun Message array.
+   *
+   * @var array
+   */
+  private $mailgun_message;
+
+  /**
+   * Exception recorded when trying to send email and it failed.
+   *
+   * @var \Throwable|null
+   */
+  private $exception;
+
+
+  /**
+   * Constructs the object.
+   *
+   * @param array $mailgun_message
+   *   Array containing the Mailgun Message array (and original mail array).
+   */
+  public function __construct(array $mailgun_message, \Throwable $exception) {
+    $this->mailgun_message = $mailgun_message;
+    $this->exception = $exception;
+  }
+
+  /**
+   * Returns the Mailgun Message array.
+   *
+   * @return array
+   *   Array containing the Mailgun Message array (and original mail array).
+   */
+  public function getMailgunMessage(): array {
+    return $this->mailgun_message;
+  }
+
+  /**
+   * Return the exception recorded when trying to send email and it failed.
+   *
+   * @return \Throwable|null
+   *   Exception recorded when trying to send email and it failed.
+   */
+  public function getException(): ?\Throwable {
+    return $this->exception;
+  }
+
+}
diff --git a/src/Event/MailgunSuccessEvent.php b/src/Event/MailgunSuccessEvent.php
new file mode 100644
index 0000000..f1f785f
--- /dev/null
+++ b/src/Event/MailgunSuccessEvent.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace Drupal\mailgun\Event;
+
+use Drupal\Component\EventDispatcher\Event;
+use Mailgun\Model\Message\SendResponse;
+use Psr\Http\Message\ResponseInterface;
+
+class MailgunSuccessEvent extends Event {
+
+  public const SUCCESS = 'mailgun.send.success';
+
+  /**
+   * Array containing the Mailgun Message array.
+   *
+   * @var array
+   */
+  private $mailgun_message;
+
+  /**
+   * Success response from the Mailgun API when email is sent.
+   *
+   * @var \Mailgun\Model\Message\SendResponse|null
+   */
+  private $response;
+
+  /**
+   * Constructs the object.
+   *
+   * @param array $mailgun_message
+   *   Array containing the Mailgun Message array (and original mail array).
+   */
+  public function __construct(array $mailgun_message, SendResponse|ResponseInterface $response) {
+    $this->mailgun_message = $mailgun_message;
+    $this->response = $response;
+  }
+
+  /**
+   * Returns the Mailgun Message array.
+   *
+   * @return array
+   *   Array containing the Mailgun Message array.
+   */
+  public function getMailgunMessage(): array {
+    return $this->mailgun_message;
+  }
+
+  /**
+   * Get the success response from the Mailgun API when email is sent.
+   *
+   * @return \Mailgun\Model\Message\SendResponse|null
+   *   Returns the success response from the Mailgun API when email is sent.
+   */
+  public function getResponse(): ?SendResponse {
+    return $this->response;
+  }
+
+}
diff --git a/src/MailgunHandler.php b/src/MailgunHandler.php
index 54d6fbf..dc90e74 100644
--- a/src/MailgunHandler.php
+++ b/src/MailgunHandler.php
@@ -5,12 +5,15 @@ namespace Drupal\mailgun;
 use Drupal\Component\Utility\EmailValidatorInterface;
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Messenger\MessengerInterface;
+use Drupal\mailgun\Event\MailgunErrorEvent;
+use Drupal\mailgun\Event\MailgunSuccessEvent;
 use Egulias\EmailValidator\EmailLexer;
 use Egulias\EmailValidator\EmailParser;
 use Psr\Log\LoggerInterface;
 use Mailgun\Mailgun;
 use Mailgun\Exception;
 use Egulias\EmailValidator\Result\Result;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 
 /**
  * Mail handler to send out an email message array to the Mailgun API.
@@ -53,6 +56,13 @@ class MailgunHandler implements MailgunHandlerInterface {
   protected $emailValidator;
 
   /**
+   * Event dispatcher to send events out to listening classes.
+   *
+   * @var \Symfony\Contracts\EventDispatcher\EventDispatcherInterface
+   */
+  private $eventDispatcher;
+
+  /**
    * Constructs a new \Drupal\mailgun\MailHandler object.
    *
    * @param \Mailgun\Mailgun $mailgun_client
@@ -66,12 +76,13 @@ class MailgunHandler implements MailgunHandlerInterface {
    * @param \Drupal\Component\Utility\EmailValidatorInterface $email_validator
    *   The email validator.
    */
-  public function __construct(Mailgun $mailgun_client, ConfigFactoryInterface $config_factory, LoggerInterface $logger, MessengerInterface $messenger, EmailValidatorInterface $email_validator) {
+  public function __construct(Mailgun $mailgun_client, ConfigFactoryInterface $config_factory, LoggerInterface $logger, MessengerInterface $messenger, EmailValidatorInterface $email_validator, EventDispatcherInterface $event_dispatcher) {
     $this->mailgunConfig = $config_factory->get(MailgunHandlerInterface::CONFIG_NAME);
     $this->logger = $logger;
     $this->mailgun = $mailgun_client;
     $this->messenger = $messenger;
     $this->emailValidator = $email_validator;
+    $this->eventDispatcher = $event_dispatcher;
   }
 
   /**
@@ -113,6 +124,8 @@ class MailgunHandler implements MailgunHandlerInterface {
           ]
         );
       }
+      $mailgunSuccessEvent = new MailgunSuccessEvent($mailgunMessage, $response);
+      $this->eventDispatcher->dispatch($mailgunSuccessEvent, MailgunSuccessEvent::SUCCESS);
       return $response;
     }
     catch (Exception $e) {
@@ -124,6 +137,8 @@ class MailgunHandler implements MailgunHandlerInterface {
           '@message' => $e->getMessage(),
         ]
       );
+      $mailgunErrorEvent = new MailgunErrorEvent($mailgunMessage, $e);
+      $this->eventDispatcher->dispatch($mailgunErrorEvent, MailgunErrorEvent::ERROR);
       return FALSE;
     }
   }

And in the mailgun_webform_submission module the events are subscribed to and the success or error added as a note to the webform submission.

Remaining tasks

Once the code is dispatching success events then a Mailgun Entity can be added and linked to the webform submission so that when webhooks are supported the result of the API trying to send the email itself can also be logged in the webform submission itself.

Feature request
Status

Active

Version

2.0

Component

Code

Created by

🇬🇧United Kingdom altcom_neil

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Production build 0.71.5 2024