Problem/Motivation

PhpMailerSmtp is used as a singleton across multiple \Drupal::service('plugin.manager.mail')->mail() calls within the same PHP process (e.g., during a cron run). After each successful SMTP send, PhpMailerSmtp::reset() is called to prepare the object for the next message. However, reset() does not clear $this->Body or $this->AltBody.

When phpmailer_smtp is used as the formatter for a message, format() calls $this->msgHTML(), which sets both $this->Body and $this->AltBody. After that message is sent and reset() runs, $this->AltBody retains the plain-text content from the previous message.

When the next message is then sent — particularly one using a different formatter such as MimeMail, which sets $message['body'] to a complete MIME string and does not touch $this->AltBodyPhpMailerSmtp::mail() sets $this->Body = $message['body'] but leaves $this->AltBody populated with stale content from the previous send. PHPMailer detects a non-empty $this->AltBody and wraps the message in its own multipart/alternative structure, producing a malformed email: the stale plain-text appears as the text/plain part, and the complete MIME body (e.g., a MimeMail-generated MIME document with its own boundaries) appears raw as the text/html part.

Steps to reproduce

  1. Configure two mail keys: one using phpmailer_smtp as both formatter and sender (e.g., an internal notification), and one using mime_mail as formatter and phpmailer_smtp as sender (e.g., a branded notification).
  2. Send both emails in the same PHP process in that order — for example, from two consecutive hook_cron() implementations.
  3. Inspect the second email (the MimeMail-formatted one). It will contain a PHPMailer-generated multipart/alternative wrapper whose text/plain part contains the body of the first email, and whose text/html part contains a raw MIME document string rather than rendered HTML.

Proposed resolution

Add $this->Body = ''; and $this->AltBody = ''; to PhpMailerSmtp::reset(), ensuring both properties are cleared between sends alongside the existing recipient, header, and attachment resets.

public function reset() {
  $this->clearAllRecipients();
  $this->clearReplyTos();
  $this->clearAttachments();
  $this->clearCustomHeaders();

  $this->Priority    = 3;
  $this->CharSet     = 'utf-8';
  $this->ContentType = 'text/plain';
  $this->Encoding    = '8bit';
  $this->Body        = '';
  $this->AltBody     = '';

  $from_name = $this->config->get('smtp_fromname');
  if ($from_name == '') {
    $from_name = $this->configFactory->get('system.site')->get('name');
  }
  $this->FromName  = $from_name;
  $this->Sender    = '';
  $this->MessageID = '';
}

Remaining tasks

  • Review patch
  • Commit to 2.x
Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

teknocat created an issue. See original summary.

imclean’s picture

Thanks for the detailed report. This makes sense and shouldn't cause any problems.

imclean’s picture

Status: Active » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.