How to send an email
====================

Overview
--------

Sending mails from a web application is a very common tasks. Symfony uses its architecture to automate it in a familiar way (MVC separation) and provides a specific class to deal with the particularities of the emails (multiple mime types, enclosed media, attachments).

Introduction
------------

Symfony offers two ways to send emails from your web application: 

- Via the `sfMail` class, which is a proxy class that offers an interface with [PHPMailer](http://phpmailer.sourceforge.net/). This solution is simple and fast, but doesn't provide MVC separation and is hardly compatible with i18n. Complex emails are also harder to compose with the `sfMail` class alone.

- Via a specific action and template. This solution is very versatile and deals with emails just as regular pages, with the addition of the specificities of this media. It is a little longer to put in place, but much more powerful than the first one.

The implementation of both solution will be illustrated through the same example: the sending of a forgotten password requested by a user.

Direct use of `sfMail`
----------------------

The `sfMail` class will look familiar to those who know the `PHPMailer` class. It is simply a proxy class to `PHPMailer`, taking advantage of the symfony syntax. The `PHPMailer` class is included in the symfony package, so no additional installation (nor require) is required.

To send an email containing a password to a customer, an action has to do like the following:

    [php]
    public function executePasswordRequest()
    {
      // determine customer from the request 'id' parameter
      $customer = CustomerPeer::retrieveByPk($this->getRequestParameter('id'));
      
      // class initialization
      $mail = new sfMail();
      $mail->initialize();
      $mail->setMailer('sendmail');
      $mail->setCharset('utf-8');
      
      // definition of the required parameters
      $mail->setSender('webmaster@my-company.com', 'My Company webmaster');
      $mail->setFrom('webmaster@my-company.com', 'My Company webmaster');
      $mail->addReplyTo('webmaster_copy@my-company.com');
  
      $mail->addAddress($customer->getEmail());
  
      $mail->setSubject('Your password request');
      $mail->setBody('
      Dear customer,
      
      You are so absentminded. Next time, try to remember your password:
      '.$customer->getPassword().'
      
      Regards,
      The My Company webmaster');
      
      // send the email
      $mail->send();
    }
  
Use of an alternate action
--------------------------

In many cases, as the email sending process is just a detour in the logic of an action that does something else, it is often delegated to another action. Here is how it goes:

    [php]
    public function executePasswordRequest()
    {
      // send the email
      $raw_email = $this->sendEmail('mail', 'sendPassword');  
      
      // log the email
      $this->logMessage($raw_email, 'debug');
    }

The email sending is delegated to a `sendPassword` action of a `mail` module. The `sendEmail()` method of the `sfAction` class is a special kind of `forward()` that executes another action but comes back afterward (it doesn't stop the execution of the current action). In addition, it returns a raw email that can be written into a log file (you will find more information about the way to log information in [Chapter 16](http://www.symfony-project.com/book/trunk/16-Application-Management-Tools#Debugging)).

The `mail/sendPassword` deals with the `sfMail` object, but it doesn't need to define the mailer (it is taken from a configuration file) nor to initialize it:

    [php]
    public function executeSendPassword()
    {
      // determine customer from the request 'id' parameter
      $customer = CustomerPeer::retrieveByPk($this->getRequestParameter('id'));
      
      // class initialization
      $mail = new sfMail();
      $mail->setCharset('utf-8');      
      
      // definition of the required parameters
      $mail->setSender('webmaster@my-company.com', 'My Company webmaster');
      $mail->setFrom('webmaster@my-company.com', 'My Company webmaster');
      $mail->addReplyTo('webmaster_copy@my-company.com');
  
      $mail->addAddress($customer->getEmail());
  
      $mail->setSubject('Your password request');
      
      $this->password = $customer->getPassword();
      $this->mail = $mail;    
    }
    
Notice that the action doesn't need to call the `send()` method for the `sfMail` object; being called by a `sendEmail()`, it knows that it needs to finish by sending its `$this->mail` `sfMail` object. And where is the mail? will you ask. That's the beauty of the MVC separation: the body of the mail itself is to be written in the template `sendPasswordSuccess.php`:

    [php]
    Dear customer,
    
    You are so absentminded. Next time, try to remember your password:
    <?php echo $password ?>
    
    Regards,
    The My Company webmaster

>**Note**: If the `sendPassword` action ever determines that the email doesn't have to be sent, it can still abort the emailing process by returning `sfView::NONE`, just like a regular action.

Mailer configuration
--------------------

If you use the alternate action method, you can (you don't have to) set the mailer and activate it environment by environment through a configuration file.

Create a `mailer.yml` configuration file in the `modules/mail/config/` directory  with:

    dev:
      deliver:    off
    
    all:
      mailer:     sendmail

This stipulates the mailer program to be used to send mails, and deactivates the sending of mails in the development environment.

Send HTML email
---------------

Most of the time, emails are to be sent in HTML format, or even in a multipart format (enclosing both HTML and text format). To handle it directly with the `sfMail` object, use the `setContentType()` method and specify an alternate body:

    [php]
    $mail->setContentType('text/html');
    $mail->setAltBody('
    <p>Dear customer</p>,
    <p>
      You are so <i>absentminded</i>. Next time, try to remember your password:<br>
      <b>'.$customer->getPassword().'</b>
    </p>
    <p>
      Regards,<br>
      The My Company webmaster
    </p>');    
    $mail->setAltBody('
    Dear customer,
    
    You are so absentminded. Next time, try to remember your password:
    '.$customer->getPassword().'
    
    Regards,
    The My Company webmaster');    

If you use an alternate action, you will just need to use an alternate template ending with `.altbody.php`. Symfony will automatically add it as alternate body:

    [php]
    // in sendPasswordSuccess.php
    <p>Dear customer</p>,
    <p>
      You are so <i>absentminded</i>. Next time, try to remember your password:<br>
      <b><?php echo $password ?></b>
    </p>
    <p>
      Regards,<br>
      The My Company webmaster
    </p>
        
    // in sendPasswordSuccess.altbody.php
    Dear customer,
    
    You are so absentminded. Next time, try to remember your password:
    <?php echo $password ?>
    
    Regards,
    The My Company webmaster

>**Note**: If you just use an HTML version without altbody template, you will need to set the content type to `text/html` in the `sendPassword` action.

Embed images
------------

HTML emails can contain images directly embedded in the body. To add an embedded image, use the `addEmbeddedImage()` method of the `sfMail` object:

    [php]
    $mail->addEmbeddedImage(sfConfig::get('sf_web_dir').'/images/my_company_logo.gif', 'CID1', 'My Company Logo', 'base64', 'image/gif');

The first argument is the path to the image, the second is a reference of the image that you can use in the template to embed it:

    [php]
    // in sendPasswordSuccess.php
    <p>Dear customer</p>,
    <p>
      You are so <i>absentminded</i>. Next time, try to remember your password:<br>
      <b><?php echo $password ?></b>
    </p>
    <p>
      Regards,<br>
      The My Company webmaster
      <img src="cid:CID1" />
    </p>
    
>**Tip**: You can also embed imaged directly from the template, without any code in the action. Refer to the `embedded_image()` helper description in the [code snippets section](http://www.symfony-project.com/snippets/snippet/27).

Attachments
-----------

Attaching a document to a mail is as simple as you would expect it to be:

    [php]
    // document attachment
    $mail->addAttachment(sfConfig::get('sf_data_dir').'/MyDocument.doc');
    // string attachment
    $mail->addStringAttachment('this is some cool text to embed', 'file.txt');

Email addresses advanced syntax
-------------------------------

In addition to recipients, emails often need to be sent as carbon copy ('cc:') or blind carbon copy ('bcc:'). Here is how to do this with `sfMail`:

    [php]
    $mail->addAddress($customer->getEmail());
    $mail->addCc('carbon_copy@my-companyt.com ');    
    $mail->addBcc('blind_carbon_copy@my-company.com');

The `sfMail` methods used to set emails (`setSender()`, `setFrom()`, `addReplyTo()`, `addAddress()`, `addCc()`, `addBcc()`) can use two syntaxes:

    [php]
    $mail->setFrom('me@symfony-project.com', 'Symfony');
    // is equivalent to
    $mail->setFrom('me@symfony-project.com <Symfony>');

In addition, to minimize the code in case of multiple recipients, `sfMail` has an `addAddresses()` method:

    [php]
    $mail->addAddress('client1@client.com <Jules>');
    $mail->addAddress('client2@client.com <Jim>');
    // is equivalent to
    $mail->addAddresses(array('client1@client.com <Jules>', 'client2@client.com <Jim>'));
    
`sfMail` methods
----------------

Once you've built your `sfMail` object, you might want to check its content. Fortunately, all the setter methods described above have an equivalent getter method, and you can clear the properties previously set:

    [php]
    setCharset($charset)
    getCharset()
    setContentType($content_type)
    getContentType()
    setPriority($priority)
    getPriority()
    setEncoding($encoding)
    getEncoding()
    setSubject($subject)
    getSubject()
    setBody($body)
    getBody()
    setAltBody($text)
    getAltBody()
    setMailer($type = 'mail')
    getMailer()
    setSender($address, $name = null)
    getSender()
    setFrom($address, $name = null)
    getFrom()
    addAddresses($addresses)
    addAddress($address, $name = null)
    addCc($address, $name = null)
    addBcc($address, $name = null)
    addReplyTo($address, $name = null)
    clearAddresses()
    clearCcs()
    clearBccs()
    clearReplyTos()
    clearAllRecipients()
    addAttachment($path, $name = '', $encoding = 'base64', $type = 'application/octet-stream')
    addStringAttachment($string, $filename, $encoding = 'base64', $type = 'application/octet-stream')
    addEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = 'application/octet-stream')
    setAttachments($attachments)
    clearAttachments()
    addCustomHeader($name, $value)
    clearCustomHeaders()
    setWordWrap($wordWrap)
    getWordWrap()
    
Sending emails from outside an action
-------------------------------------

If you want to send emails outside of an action (for instance in a batch process), you don't have any actions object to call the `sendEmail()` method on. Fortunately, this method is also available on the `sfController` object. So here is how you would call an emailing action in a batch script:

    [php]
    <?php

    define('SF_ROOT_DIR',    realpath(dirname(__FILE__).'/..'));
    define('SF_APP',         'myapp');
    define('SF_ENVIRONMENT', 'test');
    define('SF_DEBUG',       false);

    require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'config.php');

    $raw_email = sfContext::getInstance()->getController()->sendEmail('mail', 'sendPassword');