WoltLab Suite Core 3.0 ships with a brand new email API. The old API pretty much was the same since Community Framework 1.x and was neither flexible nor fail-safe. Without doing manual extra work you were restricted to either text/plain or text/html only emails (no combination of both), support for own headers was rudimentary and if an email delivery failed everything exploded. While one could catch the exception, one would have to decide whether to drop the email or queue it manually.
The new API solves all of these problems: You can freely mix plain text and HTML “Mime Parts”, you have proper support for message headers (especially the Message-IDs) and we even put in an event that allows you to modify emails just before they are queued for delivery. And don’t worry if an email fails to send: sending will automatically be retried several times without requiring assistance from you.
Sending an email
Let’s go ahead and send our first email. We start off by creating a new \wcf\system\email\SimpleEmail object and filling in the subject:
The recipient is important as well. Instead of extracting the username and email address from a user, you simply pass the \wcf\data\user\User object as the recipient:
Next is the message text (body) of the email. When using the SimpleEmail class you get the overall layout of the email for free:
$message->setMessage('Boring non-bold text');
$message->setHtmlMessage('<b>Fancy bold text</b>');
Looks like our email is ready for delivery. As you might have noticed: You don’t have to care about the inner workings of an email. Simply specify what you need and WoltLab Suite Core will make a pretty email out of it. So, let’s put it into the email queue:
send() will prepare the email for delivery and checks whether you provided all the needed data. It will automatically queue the email for delivery. Once the email is safe in the background queue, your work is done. You don’t need to care about it any longer. It will eventually get delivered to the recipient.
If you need more fine grained control about the email and SimpleEmail does not cut it, there is \wcf\system\email\Email which is used internally by SimpleEmail. You can get the underlying Email object by calling SimpleEmail::getEmail(). It allows you to specify multiple recipients, custom templates, attachments and much more. For further details we recommend taking a look inside the classes in \wcf\system\email\*. They should be pretty self explanatory.
One final note when working directly with Email: Check whether it makes sense to generate the Message-Id deterministically on your own and provide a proper references header. This will create a nice email thread in the recipient’s email program. Consider you are running a shop and a user orders some products. You would then create a Message-Id like com.example.shop-{$orderID}-new for the order confirmation. Once you received the money, you would send an email with the ID com.example.shop-{$orderID}-paid with References and In-Reply-To set to com.example.shop-{$orderID}-new. The emails will automatically be grouped in the user’s mail program (if the user enabled threading)! Example:
$message->setMessageID('com.example.shop.'.$orderID.'-new');
// getMessageID() returns the full Message-ID including the host
$messageID = $message->getMessageID();
// equivalent to:
$messageID = '<com.example.shop.'.$orderID.'-new@'.Email::getHost().'>';
// now save the messageID somewhere
Later:
$message->setMessageID('com.example.shop.'.$orderID.'-paid');
// this should be a *direct reply* to at least one other message
$message->addInReplyTo($savedMessageID);
// these should be all the message IDs of the current email thread
$message->addReferences($savedMessageID);
The background queue
We’ve mentioned the background queue above and said that emails will automatically be retried if delivery failed. What exactly is this background queue? The name pretty much explains it purpose: It allows you to queue jobs for asynchronous execution. There are simply things which are not necessary to generate the reply to the current request, but which will take potentially a long time. Generally everything that touches the network will inevitable fail at times you need it least. Especially emails can be pretty flaky. For these things we introduced the background queue. You put jobs in and forget about them. WoltLab Suite Core will automatically execute them when they are due. What if a job fails? It will automatically be put into the queue again for later execution – up to a certain amount of times.
En-queuing a job can be done with enqueueIn($jobs, $time = 0). The job will then be considered for execution in $time seconds, it may be executed at any later point in time. Similarly to the cronjobs the background queue is only cleared when a user accesses the page. There also is enqueueAt($jobs, $time) to set the earliest possible execution to the given UNIX timestamp. Before you can queue a job, you'll need a job. To create one you have to extend wcf\system\background\job\AbstractBackgroundJob and implement the perform() method. If wanted you can also customize the retryAfter() method that determines how long a job will have to wait before it is rescheduled.
As example of such a such a job is the EmailDeliveryBackgroundJob which is returned by Message::getJobs():
<?php
namespace wcf\system\background\job;
use wcf\system\email\transport\exception\PermanentFailure;
use wcf\system\email\Email;
use wcf\system\email\Mailbox;
/**
* Delivers the given email to the given mailbox.
*
* @author Tim Duesterhus
* @copyright 2001-2016 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package WoltLabSuite\Core\System\Background\Job
* @since 3.0
*/
class EmailDeliveryBackgroundJob extends AbstractBackgroundJob {
/**
* email to send
* @var \wcf\system\email\Email
*/
protected $email;
/**
* sender mailbox
* @var \wcf\system\email\Mailbox
*/
protected $envelopeFrom;
/**
* recipient mailbox
* @var \wcf\system\email\Mailbox
*/
protected $envelopeTo;
/**
* instance of the default transport
* @var \wcf\system\email\transport\EmailTransport
*/
protected static $transport = null;
/**
* Creates the job using the given the email and the destination mailbox.
*
* @param \wcf\system\email\Email $email
* @param \wcf\system\email\Mailbox $envelopeFrom
* @param \wcf\system\email\Mailbox $envelopeTo
* @see \wcf\system\email\transport\EmailTransport
*/
public function __construct(Email $email, Mailbox $envelopeFrom, Mailbox $envelopeTo) {
$this->email = $email;
$this->envelopeFrom = $envelopeFrom;
$this->envelopeTo = $envelopeTo;
}
/**
* Emails will be sent with an increasing timeout between the tries.
*
* @return int 5 minutes, 30 minutes, 2 hours.
*/
public function retryAfter() {
switch ($this->getFailures()) {
case 1:
return 5 * 60;
case 2:
return 30 * 60;
case 3:
return 2 * 60 * 60;
}
}
/**
* @inheritDoc
*/
public function perform() {
if (self::$transport === null) {
$name = '\wcf\system\email\transport\\'.ucfirst(MAIL_SEND_METHOD).'EmailTransport';
self::$transport = new $name();
}
try {
self::$transport->deliver($this->email, $this->envelopeFrom, $this->envelopeTo);
}
catch (PermanentFailure $e) {
// no need for retrying. Eat Exception and log the error.
\wcf\functions\exception\logThrowable($e);
}
}
}
Display More
Note: The wcf1_background_job table is explicitly not part of the public API, do not touch and do not rely on it.