How do I send emails in my applications

Today I decided to share some of my ideas according to email sending. I will use yii app as an example but it’s absolutely not required. It’s just an aproach that you can use with any other framework. Right now yii doesn’t have default mail component. But I won’t tell you what mail extension or lib should you use. I want to tell you about how should look like email send in your app.

Well I really believe that you already don’t send emails like this :)

$message = " Pellentesque convallis tempor tortor. Nullam nec purus.";
mail('somebody@example.com', 'Nonsensical Latin', $message);

But you do it like this

     $message = \Swift_Message::newInstance()
        ->setSubject('Hello Email')
        ->setFrom('send@example.com')
        ->setTo('recipient@example.com')
        ->setBody(
            $this->renderView(
                'HelloBundle:Hello:email.txt',
                array('name' => $name)
            )
        )
    ;
    $this->get('mailer')->send($message);

It could be Swift or Zend Mailer or other good lib.

What are advantages in this approach

  • First of all you don’t create a wheel but use good and checked tools
  • It doesn’t render email body inside your code. It renders it from some view. Very good, take five

But what are disadvantages in this approach

  • it’s not DRY. In real app you will send dozens of emails. And following this approach you’ll have dozens places with the same piece code. 
  • it’s doesn’t scalable at all if tomorrow you want to log sent emails or want to render it in html, or add bcc etc, you have to to add additional logic in dozen of places. 
  • it’s quite difficult to update email template. If we have some managers, moderators, admin that sometimes want to improve some text in email they can’t do that without asking developer.

So what is my proposal?

Email send have to look like this

$emailReset = new \MyApp\email\ResetPassword(array(
    'user' => $userModel,
    'hash' => $hash,
    /* other dynamic data for email */
));
$emailReset->send();

And that’s it! You don’t need another code. You create an obejct of required email, in my example it’s reset password email. I pass user model, so then we can get email, username, etc and I pass specific data for that email. And… I send it.

How we can achieve that?
We have various emails in our app – sign in, reset password, sale emails, promo emails, and many many others. For each email we know subject and body.  The idea is to keep email templates in DB

email

Nothing fancy at that point. Then I add parent class for all my emails

<?php

namespace MyApp\email;

abstract class Base
{

    /**
     *
     * @var TransportInterface
     */
    private static $_transport;

    /**
     *
     * @var \MyApp\model\EmailTemplate
     */
    private $_emailTemplate;
    protected $_emailCode;
    private $_addToEmail;
    private $_addToUsername;
    private $_data;

    /**
     * Recipient
     * @var \MyApp\model\User
     */
    private $_user;

    public function __construct($data)
    {
        $data = $this->_initData($data);

        if (!empty($data['user']) && ($data['user'] instanceof \MyApp\model\User)) {
            $this->_user = $data['user'];
            $data['email'] = $this->_user->email;
            $data['username'] = $this->_user->getUsername();
        } else {
            if (empty($data['email'])) {
                throw new \Exception("Email is not defined");
            }
        }
        $this->_addToEmail = $data['email'];
        if (!empty($data['username'])) {
            $this->_addToUsername = $data['username'];
        }
        $this->_data = $data;
    }

    protected function _initData($data)
    {
        return $data;
    }

    /**
     * Compile and send email
     */
    public function send()
    {
        $email = $this->_emailTemplate();

        $email->compileEmail($this->_data);

        $defaultEmailParams = \Yii::app()->params['email'];
        $mail = new \Zend\Mail\Message();
        $mail->setBody($email->body);
        $mail->setSubject($email->subject);
        $mail->addTo($this->_addToEmail, $this->_addToUsername);
        $mail->setFrom($defaultEmailParams['defaultFromEmail'], $defaultEmailParams['defaultFromName']
        );

        //$mail->addHeader('List-Unsubscribe', '<' . 'link' . '>');
        $this->getTransport()->send($mail);

        return true;
    }

    /**
     * Return email transport
     * @return \Zend\Mail\Transport\TransportInterface
     */
    public static function getTransport()
    {
        if (!self::$_transport) {
            if (\Yii::app()->params['email']['provider'] == 'sendmail') {
                self::$_transport = new \Zend\Mail\Transport\Sendmail();
            } elseif (\Yii::app()->params['email']['provider'] == 'file') {
                $fileOptions = new \Zend\Mail\Transport\FileOptions();
                $fileOptions->setPath(\Yii::app()->basePath . '/runtime/cache/email/' . APPLICATION_ENV);
                self::$_transport = new \Zend\Mail\Transport\File($fileOptions);
            }
        }
        return self::$_transport;
    }

    /**
     * Return email object
     * @return \MyApp\model\EmailTemplate
     * @throws Exception
     */
    protected function _emailTemplate()
    {
        if (!$this->_emailCode) {
            throw new Exception("Template name is not set");
        }
        if (!$this->_emailTemplate) {
            $this->_emailTemplate = \MyApp\model\EmailTemplate::getByCode($this->_emailCode);
        }

        return $this->_emailTemplate;
    }

}

It’s just an example. So your class be different could more functionality that I described above. I show just an approach. I used ZendMail component it’s so easy now if you user composer (  “zendframework/zend-mail”: “*” ) but you wanna use what ever you want. I believe everything is quite straight forward here.

Only the one note about $email->compileEmail($this->_data) I liked to use Twig to render emails. So compileEmail in fact just render twig template that we have in db and  copy in file. But it’s another topic. If anyone would be interested I can describe that in more detail.

Finally the example of specific email is following

<?php

namespace MyApp\email;

class ResetPassword extends \MyApp\email\Base
{

    protected $_emailCode = 'reset_password';

    protected function _initData($data)
    {
        $data['link'] = \Yii::app()->createAbsoluteUrl('urltoreset', array(
            'email' => $data['user']['email'],
            'hash' => $data['hash']
        ));

        return $data;
    }

}

That’s it. Hope it would be helpful for someone :)

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>