Custom Module for Sending Emails in Development Servers

I am working on a custom module that replaces email recipients, subject, and body. We have a few different versions of our Production website being Staging, Dev, and individual developer sandboxes.

Whenever any email is sent from the Dolibarr system, we want to update the values for the recipients, subject, and body.

We use SMTPS. The emails go through the following classes to send out emails:

  • CMailFile - htdocs/core/class/CMailFile.class.php
  • SMTPs - htdocs/core/class/smtps.class.php

I have set up a hook in my custom module and am able to successfully update the email’s subject and body.

What I am having trouble with are the recipients. I do not want our development servers to email the thirdparties or internal users at all. I would rather have the Dolibarr system email the email address(es) I tell it to. I am able to add an email address to the smtps->setTO() but the system does not remove the thirdparty. So, an email gets sent out to both the new email address I set it to and the thridparty’s email address. I just want the system to email the email address I set it to.

Please see example code of my hook where I intercept the value and replace them before sending the email out.


public function sendMail($parameters, &$object, &$action, $hookmanager)
{
	$production_recipients = $object->addr_to;
	$recipients = TEST_MODE ? $this->testemailrecipients : $production_recipients;
	
	$email_subject = sprintf( '%s %s', $this->emaildebugsubject, $object->subject );
	$email_message = sprintf( '%s %s', $object->msg, $this->getEmaildebugsnippet($production_recipients) );

	if ( $object->sendmode == 'mail' ) {
		$object->message = $email_message;
	} elseif ( $object->sendmode == 'smtps' ) {
		$object->smtps->setSubject($email_subject);
		
		if ( TEST_MODE ) {
			$object->smtps->setTO('phil@noemail.invalid'); // <<<< this is where I set the email address(es)
		}

		if ( $object->msgishtml ) {
			$object->smtps->setBodyContent($email_message, 'html');
		} else {
			$object->smtps->setBodyContent($email_message, 'plain');
		}
	} elseif ($this->sendmode == 'swiftmailer') {
		$object->message->setBody($email_message, 'text/html');
	}

	return 0;
}

I append a debug snippet as an include in the email body. I like to show who the production recipients would have been.

However, if I make use of the global MAIN_MAIL_FORCE_SENDTO, then I do not see who the production recipients(s) would have been because the addr_to is the value set in the global MAIN_MAIL_FORCE_SENDTO. This is an example of what the debug snippet included in the email body would like if I set the global MAIN_MAIL_FORCE_SENDTO value to phil@noemail.invalid

_______________________________________
TEST MODE DEBUG INFORMATION 
________________________________________
PRODUCTION recipients would have been: phil@noemail.invalid
ACTUAL recipients: phil@noemail.invalid
________________________________________

When I do not use the global MAIN_MAIL_FORCE_SENDTO and set a test recipient as seen in the function in my initial message above ( see: $object->smtps->setTO(‘phil@noemail.invalid’); ), I do see who the production recipients would have been, which is desired. But the system still sends the email to the production recipient in my test environments as well the test recipient.

_______________________________________
TEST MODE DEBUG INFORMATION 
________________________________________
PRODUCTION recipients would have been: production@noemail.invalid 
ACTUAL recipients: phil@noemail.invalid
________________________________________

I feel as though I fought the good fight and tried everything I can to tackle the issue I am having, but will have to throw in the towel and go with the global MAIN_MAIL_FORCE_SENDTO for setting the recipients when sending all emails to (instead of real recipients, for test purposes).

I’ll keep following this post if someone knows how I can overcome this issue. Thanks!

Came up with a solution.

I ended up copying the contents of the following block of code in the constructor from the CMailFile class at htdocs/core/class/CMailFile.class.php in a public function called newSmtps() in my custom module’s hook class. The newSmtps() function replaces $object->smtps with a NEW instance of SMTPS.

elseif ($this->sendmode == 'smtps')

So, now my hook function looks like this:

	public function sendMail($parameters, &$object, &$action, $hookmanager)
	{
		if ( ! TEST_MODE ) return 0;
		
		$production_recipients = $object->addr_to;
		$recipients = TEST_MODE ? $this->testemailrecipients : $production_recipients;

		// START: Update the addr_to field value on the CMailFile object
		$object->addr_to = $recipients;
		// END: Update the addr_to field value on the CMailFile object
		
		$email_subject = sprintf( '%s %s', $this->emaildebugsubject, $object->subject );
		$email_message = sprintf( '%s %s', $object->msg, $this->getEmaildebugsnippet($production_recipients) );

		if ( $object->sendmode == 'mail' ) {
			$object->message = $email_message;
		} elseif ( $object->sendmode == 'smtps' ) {
			$object->smtps = $this->newSmtps($object); // 
			$object->smtps->setSubject($email_subject);
			if ( $object->msgishtml ) {
					$object->smtps->setBodyContent($email_message, 'html');
			} else {
					$object->smtps->setBodyContent($email_message, 'plain');
			}
		} elseif ($this->sendmode == 'swiftmailer') {
			// START: Update the to address(s) on the Swift_Message object.
			try {
				$result = $object->message->setTo($object->getArrayAddress($object->addr_to));
			} catch (Exception $e) {
				$object->errors[] = $e->getMessage();
			}
			// END: Update the to address(s) on the Swift_Message object.

			$object->message->setBody($email_message, 'text/html');
		}

		return 0;
	}

It is important to replace $this with $object so that the smtps class has all the values it needs to send out the emails.

	public function newSmtps( &$object ) {
		global $conf;
		$filename_list = $object->filename_list;
		$mimetype_list = $object->mimetype_list;
		$mimefilename_list = $object->mimefilename_list;
		$moreinheader = $object->smtps->getMoreInHeader();
		$css = ( ! empty( $object->css ) ? $object->css : '' );

		// Use SMTPS library
		// ------------------------------------------

		require_once DOL_DOCUMENT_ROOT.'/core/class/smtps.class.php';
		$smtps = new SMTPs();
		$smtps->setCharSet($conf->file->character_set_client);

		

		// Encode subject if required.
		$subjecttouse = $object->subject;
		if (!ascii_check($subjecttouse)) {
			$subjecttouse = $object->encodetorfc2822($subjecttouse);
		}

		$smtps->setSubject($subjecttouse);
		$smtps->setTO($object->getValidAddress($object->addr_to, 0, 1));
		$smtps->setFrom($object->getValidAddress($object->addr_from, 0, 1));
		$smtps->setTrackId($object->trackid);
		$smtps->setReplyTo($object->getValidAddress($object->reply_to, 0, 1));

		if (!empty($moreinheader)) $smtps->setMoreInHeader($moreinheader);

		if (!empty($object->html))
		{
			if (!empty($css))
			{
				$object->css = $css;
				$object->buildCSS();
			}
			$msg = $object->html;
			$msg = $object->checkIfHTML($msg);
		}

		// Replace . alone on a new line with .. to avoid to have SMTP interpret this as end of message
		$msg = preg_replace('/(\r|\n)\.(\r|\n)/ims', '\1..\2', $msg);

		if ($object->msgishtml) $smtps->setBodyContent($msg, 'html');
		else $smtps->setBodyContent($msg, 'plain');

		if ($object->atleastoneimage)
		{
			foreach ($object->images_encoded as $img)
			{
				$smtps->setImageInline($img['image_encoded'], $img['name'], $img['content_type'], $img['cid']);
			}
		}

		if (!empty($object->atleastonefile))
		{
			foreach ($filename_list as $i => $val)
			{
				$content = file_get_contents($filename_list[$i]);
				$smtps->setAttachment($content, $mimefilename_list[$i], $mimetype_list[$i]);
			}
		}

		$smtps->setCC($object->addr_cc);
		$smtps->setBCC($object->addr_bcc);
		$smtps->setErrorsTo($object->errors_to);
		$smtps->setDeliveryReceipt($object->deliveryreceipt);
		if (!empty($conf->global->$keyforsslseflsigned)) $smtps->setOptions(array('ssl' => array('verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true)));

		$host = dol_getprefix('email');
		$object->msgid = time().'.SMTPs-dolibarr-'.$object->trackid.'@'.$host;

		return $smtps;
	}

It is important that the newSmtps() function accept $object as ‘&$object’ so you get a reference instead of a clone.

Furthermore, you need to place the Dolibarr global $conf at the top of the new function along with the following constructors. The constructors are important so that files attached to emails are attached properly.

		$filename_list = $object->filename_list;
		$mimetype_list = $object->mimetype_list;
		$mimefilename_list = $object->mimefilename_list;
		$moreinheader = $object->smtps->getMoreInHeader();
		$css = ( ! empty( $object->css ) ? $object->css : '' );
1 Like