Sending Email with Zend Framework 2 Using Template

Sending a templated HTML email is a one-liner in some frameworks, but Zend Framework 2 ships no such helper, so you have to wire up the view renderer, the MIME message, and the transport yourself. This post walks through that setup.

Note: Zend Framework 2 has reached end of life. In 2019 the project moved to the Linux Foundation and was renamed Laminas; the Zend\* classes shown below now live under Laminas\* (e.g. Laminas\Mail, Laminas\View). The technique is unchanged, so this post still applies once you swap the namespaces.

For comparison, here is the same task in Laravel, where a single Mail::send() call renders a view and mails it:

Mail::send("mails.reset", $data, function ($message) use ($email) {
    $message
        ->from("noreply@infinitescript.com", "CourseOcean")
        ->subject("Reset Your Password");
    $message->to($email);
});

Zend Framework 2 has no equivalent shortcut, so we build it in two steps: a view template for the email body (Step 1), and a function that renders the template and sends the message (Step 2).

Step 1: Create an Email Template

The example here is a password-reset email for CourseOcean, an online course platform I built. Create the email body as a standard .phtml view script in the views folder, for example /view/mails/reset.phtml. It is an ordinary view template: the <?=$this->username?>, <?=$this->email?>, and <?=$this->keycode?> placeholders are view variables that we populate when rendering it in Step 2.

The markup itself is just an ordinary HTML email (inline styles omitted below for brevity); what matters is where the placeholders go:

<table cellspacing="0" cellpadding="0" border="0">
  <tbody>
    <tr>
      <td>
        <img src="https://lab.haozhexie.com/CourseOcean/img/logo.webp" alt="CourseOcean" />
      </td>
    </tr>
    <tr>
      <td>
        We received a request to reset the password for your account,
        <?=$this->username?>.<br /><br />
        <!-- ... -->
        <a
          href="https://lab.haozhexie.com/CourseOcean/accounts/resetPassword?email=<?=$this->email?>&amp;keycode=<?=$this->keycode?>"
          >Reset your password</a
        >
        <!-- ... -->
      </td>
    </tr>
  </tbody>
</table>

Step 2: Complete Sending Email Function

With the template ready, the sending function does four things: render the template to an HTML string, wrap that string in a MIME part marked as HTML, attach it to a mail message, and hand the message to a transport.

function sendResetPasswordEmail($username, $email) {
    $keycode = $this->generateRandomString(32);
    $view = new \Zend\View\Renderer\PhpRenderer();
    $resolver = new \Zend\View\Resolver\TemplateMapResolver();
    $resolver->setMap([
        "mailTemplate" => __DIR__ . "/../../../view/mails/reset.phtml",
    ]);
    $view->setResolver($resolver);

    $viewModel = new ViewModel();
    $viewModel->setTemplate("mailTemplate")->setVariables([
        "username" => $username,
        "email" => $email,
        "keycode" => $keycode,
    ]);

    $bodyPart = new \Zend\Mime\Message();
    $bodyMessage = new \Zend\Mime\Part($view->render($viewModel));
    $bodyMessage->type = "text/html";
    $bodyPart->setParts([$bodyMessage]);

    $message = new \Zend\Mail\Message();
    $message
        ->addFrom("noreply@infinitescript.com", "CourseOcean")
        ->addTo($email)
        ->setSubject("Reset Your Password")
        ->setBody($bodyPart)
        ->setEncoding("UTF-8");
    $transport = new \Zend\Mail\Transport\Sendmail();
    $transport->send($message);
}

A few pieces are worth calling out:

  • PhpRenderer + TemplateMapResolver turn the .phtml file into a string. The resolver maps the logical name "mailTemplate" to the file on disk, so $view->render() knows what to load.
  • ViewModel carries the variables (username, email, keycode) into the template, where they surface as $this->username and so on.
  • Zend\Mime\Part with type = "text/html" is what makes this an HTML email rather than plain text. Without the explicit MIME type, the body is sent as text/plain and the markup shows up as raw tags.
  • Zend\Mail\Transport\Sendmail sends through the local sendmail binary. Swap in Zend\Mail\Transport\Smtp if you need to send through an SMTP server instead.

Conclusion

There is more boilerplate here than Laravel’s one-liner, but the pieces are reusable: keep the PhpRenderer setup in a helper and every templated email becomes a matter of picking a template and passing variables.

References