Yii 2 - Sending emails using Swiftmailer

2016-08-20
Yii

In Yii 2, the default component for sending emails is already built in, which is use Swiftmailer. Consider at the example of the fineness of work with this component, such as passing parameters to the layout and others.

Goals

It is necessary to configure the sending of letters, so that:

  • they were not sent, but saved in a certain folder, because it is convenient for local debugging;
  • using views and layouts of emails;
  • passing parameters to the email layout it the same way as to the view;
  • the sender was inserted by default;
  • the name and email of the recipient were inserted;
  • UTF-8 emails charset;
  • two form of emails sending: HTML and plain-text.

In this article will be used "clean" basic application template of Yii 2, which can be installed through Composer following this instruction.

Configuring

Begin implementation with the write settings to the config file \config\web.php:

...
'components' => [
    ...
    'mailer' => [
        'class' => 'yii\swiftmailer\Mailer',
        'viewPath' => '@app/mail',
        'htmlLayout' => 'layouts/main-html',
        'textLayout' => 'layouts/main-text',
        'messageConfig' => [
            'charset' => 'UTF-8',
            'from' => ['noreply@site.com' => 'Site Name'],
        ],
        'useFileTransport' => true,
    ],
    ...
]
...

In this configuration:

  • viewPath - path of email views;
  • htmlLayout and textLayout - email layouts, HTML and plain-text versions respectively;
  • messageConfig -> charset - UTF-8 emails charset;
  • messageConfig -> from - email and name of the sender by default;
  • useFileTransport - set this flag for saving emails to directory \runtime\mail, instead of real sending;

Why are set the two versions of layouts, HTML and plain-text? In general, this is not necessary, but it is desirable. Because when sending an email, both versions will be attached to it. If user application, with the help of which he reads emails, supports HTML markup, will be displayed HTML version, otherwise plain-text. Of course, in today's time most applications supports the HTML emails, but probably it is better and for online email services, perhaps this will reduce the probability of getting into spam, if will arises such situation.

Adding layout

According to the configuration of the above, layouts will be located in a directory \mail\layouts.

In the basic application template of Yii 2, which using in this article, already has one layout html.php, it is does not fit, delete it.

Further create a two new layouts, HTML and plain-text. HTML version must located in the file \mail\layouts\main-html.php:

<?php
use yii\helpers\Html;

/* @var $this \yii\web\View view component instance */
/* @var $message \yii\mail\MessageInterface the message being composed */
/* @var $content string main view render result */
?>
<?php $this->beginPage() ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=<?= Yii::$app->charset ?>" />
    <title><?= Html::encode($this->title) ?></title>
    <?php $this->head() ?>
</head>
<body>
    <?php $this->beginBody() ?>

    <h4>Hello <?= $this->params['userName'] ?></h4>

    <?= $content ?>

    <?php $this->endBody() ?>
</body>
</html>
<?php $this->endPage() ?>

The plain-text version must located in the file \mail\layouts\main-text.php:

<?php

/* @var $this \yii\web\View view component instance */
/* @var $message \yii\mail\MessageInterface the message being composed */
/* @var $content string main view render result */

?>
Hello <?= $this->params['userName'] ?>

<?= $content ?>

In this layouts, <?= $this->params['userName'] ?> - it is parameters passing, user name in this case.

Adding view

Now will add test email view. As well as with layouts, for views need creates two versions: HTML and plain-text. Create file \mail\views\example-html.php, which will be contents HTML version of email:

<?php

/** @var $this \yii\web\View */
/** @var $link string */
/** @var $paramExample string */

?>
<p>Email text...</p>

<p>Some passed parameter: <?= $paramExample ?></p>

Also create file \mail\views\example-text.php, which contents the plain-text version of email:

<?php

/** @var $this \yii\web\View */
/** @var $link string */
/** @var $paramExample string */

?>
Email text...

Some passed parameter: <?= $paramExample ?>

In this views is present a parameter <?= $paramExample ?>, which will be pass to the email as example, to show how exactly the parameters are passed to the email.

User model preparing

In the basic application template of Yii 2, already has the model \models\User.php, which intended for work with users. But it is a simplified, and even do not interacts with DB. For the example described in this article, this simplified model is enough. The only thing missing in it is the email address field, which would show the email address of the user, add it:

<?php

namespace app\models;

class User extends \yii\base\Object implements \yii\web\IdentityInterface
{
    ...
    public $email;
    ...

    private static $users = [
        '100' => [
            ...
            'email' => 'admin@site.com',
            ...
        ],
        '101' => [
            ...
            'email' => 'demo@site.com',
            ...
        ],
    ];

    ...
}

Now all users, there are now two: admin and demo, have the email fields with contents their email addresses.

Create method for email sending

Now we come to the most important, writing a method for sending emails to users. For convenience, this method will be located in the user model, i.e. in the class app\models\User. Open the file \models\User.php containing the user model class, and add a new method to its end:

<?php

namespace app\models;

class User extends \yii\base\Object implements \yii\web\IdentityInterface
{
    ...

    /**
     * @param string $view
     * @param string $subject
     * @param array $params
     * @return bool
     */
    public function sendMail($view, $subject, $params = []) {
        // Set layout params
        \Yii::$app->mailer->getView()->params['userName'] = $this->username;

        $result = \Yii::$app->mailer->compose([
            'html' => 'views/' . $view . '-html',
            'text' => 'views/' . $view . '-text',
        ], $params)->setTo([$this->email => $this->username])
            ->setSubject($subject)
            ->send();

        // Reset layout params
        \Yii::$app->mailer->getView()->params['userName'] = null;

        return $result;
    }
}

Let's consider in detail what happens in this method.

In the next section of the code, the parameters are passed to the layout, in this case the user name:

// Set layout params
\Yii::$app->mailer->getView()->params['userName'] = $this->username;

Besides an user name, of course can pass any other parameters.

After the send() method was called and the email was sent, you must clear the parameters, which were passed to the layout, this is done in this section of the code:

// Reset layout params
\Yii::$app->mailer->getView()->params['userName'] = null;

This cleaning is necessary in order that these parameters can not be passed to the next email, which can be sent from the other code place.

As described above, it is necessary that would be sent two versions of the email for the user: HTML and plain-text. For this in the compose() method is necessary to specify a two kinds of a view, first for HTML version of the email, second for plain-text:

$result = \Yii::$app->mailer->compose([
    'html' => 'views/' . $view . '-html',
    'text' => 'views/' . $view . '-text',
], $params)

In the setTo() method, the name and email address of the recipient are passed as an array:

setTo([$this->email => $this->username])

Sending test email

It's time to check how the email is sent and in what form it comes. To do this, add the new method in the controller app\controllers\SiteController:

...
class SiteController extends Controller
{
    ...
    public function actionTestMailer() {
        \app\models\User::findByUsername('admin')->sendMail('example', 'Email example', ['paramExample' => '123']);
    }
    ...
}

Due to the fact that we added a method of sending an email to the user model, we can, after searching the user findByUsername('admin') immediately send him an email using the sendMail() method. The convenience of this approach is that we do not need to pass the recipient's name and email address manually, since this data will be available in the user model, in which we have the method of sending the email sendMail(). And for that in this method to substitute the name and email address of the recipient, in this case, the user of which we just found using the \app\models\User::findByUsername('admin') method, you can simply use the corresponding class fields: $this->username and $this->email.

Now consider the parameters that need to be passed to the sendMail() method. The first parameter was passed to 'example' - this is the alias of the email view. Second parameter - Email example - this is email subject. Third parameter - ['paramExample' => '123'] - this is vars, which will be passed to the email, in this case we have only one variable in the email view, which we use for the example, that would show how the parameters are transferred from the code to the email.

This concludes the implementation of the action actionTestMailer() is done, it's time to open it in the browser and check what the sent email will look like. Open the browser and go to the address like: /index.php?r=site/test-mailer, the most likely to appear is a blank page, it should be so.

Now let's check how the email is displayed. But in our case it was not sent anywhere, but should have been saved in the folder \runtime\mail. As you can see, as a result, all the parameters that we passed where substituted in the email, also a layout was applied.

Configuring SMTP

In order to achieve the goal, it remains to configure the email sending through SMTP.

In order for Swiftmailer to start sending emails via SMTP, you need to make some changes in the config \config\web.php:

...
'components' => [
    ...
    'mailer' => [
        ...
        'messageConfig' => [
            ...
            'from' => ['your@email.com' => 'test-user'],
            ...
        ],
        ...
        'useFileTransport' => false,
        ...
        'transport' => [
            'class' => 'Swift_SmtpTransport',
            'host' => 'smtp.some-host.com',
            'username' => 'test-user',
            'password' => 'test-user-password',
            'port' => '465',
            'encryption' => 'ssl',
        ],
        ...
    ],
    ...
]
...

What changes were made:

  • messageConfig -> from - here you must specify the email address that is used for sending emails;
  • 'useFileTransport' => false - disable saving emails to the folder \runtime\mail;
  • in the transport section, set the SMTP server settings from which the mail will be sent.

This completes the SMTP setup, in order to check how to send the email, just open the browser's address /index.php?r=site/test-mailer.