Skip to end of metadata
Go to start of metadata

<p>Zend_Mail was initially a very popular component in ZF1, as it provided a fluent interface for creation of mail messages, abstraction around the transport used to send messages, and reasonable attachment support.</p>

<p>Several problems present themselves, however. </p>

<ul>
<li>The relationships between classes are inverted. Current, Zend_Mail is capable of sending itself, and either requires that you provide a default transport, or pass a transport to the send() method. This makes cloning of messages expensive, sending of many messages at once expensive, and muddies configuration of mailers. Ideally, the mail transport should be the base object, and mail messages should be passed to the transport to send.</li>
<li>Message assembly has largely occurred in the transports; this should be the responsibility of the Message itself.</li>
<li>Numerous issues exist regarding MIME encoding as well as unicode support of mail messages.</li>
<li>It's relatively difficult to provide non-standard headers to a mail message.</li>
<li>Attachments are not intuitve.</li>
</ul>

<h2>Proposed Architecture</h2>

<p>I propose the following core architecture for the Mail component:</p>

<ul>
<li><strong>Mailer:</strong> an interface with a single method, <code>send($message)</code>. <code>$message</code> could be either a single message of type <code>Message</code>, or a collection of messages.
<ul>
<li>We would supply mailers for Sendmail, SMTP, and the filesystem at the minimum</li>
</ul>
</li>
<li><strong>Message:</strong> a class that aggregates the following:
<ul>
<li>To, From, B/CC, and Reply-To Addresses</li>
<li>Subject</li>
<li>Arbitrary Headers</li>
<li>Attachments</li>
<li>De/Serialization methods (specifically, from/to string)</li>
</ul>
</li>
<li><strong>Address:</strong> a value object containing a <strong>name</strong> and <strong>email</strong></li>
<li><strong>Header:</strong> a value object containing a <strong>fieldName</strong> and <strong>fieldValue</strong></li>
<li><strong>Headers:</strong> an aggregate of Header objects</li>
<li><strong>Attachment:</strong> a value object containing:
<ul>
<li>Mime-Type</li>
<li>Content (which could in turn be made up of Attachments)</li>
<li>Headers</li>
</ul>
</li>
<li><strong>Attachments:</strong> an aggregate of Attachment objects. Additionally:
<ul>
<li>A Mime boundary</li>
<li>Convenience methods such as "isMultiPart()" (do we have > 1 attachments)</li>
</ul>
</li>
<li><strong>MessageCollection:</strong> a collection of Message objects</li>
</ul>

<p>The To, From, B/CC, Reply-To, and Subject values would in turn populate individual Header objects.</p>

<h2>Usage</h2>

<p>The primary unit of usage is a Mailer.</p>

<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
use Zend\Mail\Mailer\Smtp,
Zend\Mail\Mailer\SmtpOptions;
$options = new SmtpOptions(array(
'host' => 'localhost',
'port' => 25,
));
$mailer = new Smtp($options);
]]></ac:plain-text-body></ac:macro>

<p>You create Message objects and pass them to your mailer.</p>

<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
use Zend\Mail\Message;

$message = new Message();

// Convenience:
$message->from('matthew@zend.com', "Matthew Weier O'Phinney");
// Explicit:
$from = new Address('matthew@zend.com', "Matthew Weier O'Phinney");
$message->from($from);

// Fluent API:
$body = new Attachment();
$body->setMimeType('text/plain');
$body->setContent('Here is the invite for our meeting tomorrow.');
$invite = file_get_contents('meeting_invite.ics');

$message->to('ralph@zend.com', 'Ralph Schindler')
->subject('Meeting today')
->cc('enrico@zend.com') // omit full name
->addAttachment($body)
->addAttachment($invite, 'text/calendar');
$message->headers()->addHeader('X-Mailer', 'ZF2-Zend\Mail');

// Send the message
$mailer->send($message);
]]></ac:plain-text-body></ac:macro>

<h3>Notes</h3>

<p>The fluent API of a Message object can take arguments that can be used to create the appropriate objects, or actual concrete instances of those objects. This allows sculpting the message exactly how one wants, including nesting attachments, setting specific attachment headers, etc. </p>

<h2>Benefits</h2>

<ul>
<li>Easier to test base functionality of Messages, including serialization to string.</li>
<li>Easier to test discrete operations of each mailer type.</li>
<li>More flexibility in message creation.</li>
</ul>

<h2>What we're not addressing</h2>

<p>A number of feature requests/concerns were raised in the <ac:link><ri:page ri:content-title="Zend Mail 2.0" /><ac:link-body>Zend_Mail 2.0 proposal</ac:link-body></ac:link>. I'm specifically not addressing several of these, as I think they fall outside the responsibilities of a mail component. As examples:</p>

<ul>
<li>Templating. Since the bodies can be seeded separately, and message objects will be basically value objects, I see no value to adding templating capabilities. This sort of thing can be easily achieved in userland, and would prevent coupling of the Mail component with the View layer.</li>
<li>Inline images. I'd like to address this, but I'm not sure how we should approach it, as the proposed use cases require parsing attachments for links as well as transformation of the parsed attachments.</li>
<li>CSS inlining. Same argument as for Inline images</li>
<li>Advanced IMAP/Storage operations. This proposal is really directed primarily at the generation and sending of emails, not reading of them. Such requirements should be addressed in a separate proposal.</li>
</ul>

<p>The first three items feel like a fit for a filtering or event system, and we could potentially compose an EventManager into the Message and/or Mailer classes to allow such operations. However, I'd like feedback as to where in the workflow we should trigger events.</p>

Labels:
None
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.
  1. Oct 31, 2011

    <p>+1 for relation inverted between Message and Transport <ac:emoticon ac:name="laugh" /></p>

    <p>You exclude the mail() function from the list of transports. Why?</p>

    <p>A useful feature that I miss in the development environment: to redirect all emails to a single address. The bonus is to have the original recipient in customized header (like X-MAIL-TO). This will be possible with event on send method.</p>

    <p>It disturbs me still that the inline images are not supported. It is common in HTML mails since the images are filtered by security (= a warning in the mail client). My use case is inline images and an attachment (like PDF): fails in ZF1.</p>

    <p>No worries about Storage <ac:emoticon ac:name="smile" /><br />
    I like your architecture.</p>

    1. Oct 31, 2011

      <p>The "Sendmail" transport <em>is</em> mail(). <ac:emoticon ac:name="smile" /></p>

      <p>re: redirect all emails to a single address – I'd argue either an event in send(), or a custom transport.</p>

      <p>re: inline images: as I noted in the RFC, I think there are number of ways to address this, but my take on it is that we'd need to have a filter on the message body. The question is whether we allow adding a filter on messages, or pass the message as an argument to an event during send(). Either way can easily be slip-streamed into the architecture as proposed.</p>

      <p>Thanks for your feedback!</p>

      1. Apr 04, 2012

        <blockquote><p> The question is whether we allow adding a filter on messages, or pass the message as an argument to an event during send().</p></blockquote>

        <p>I think filters should be added on the message...this should be facilitated by the event system, but it is only an implementation detail that the user shouldn't have to worry about.</p>

  2. Nov 04, 2011

    <p>Attachments should be renamed AttachmentAggregate for consistency with others namespaces (EventManager, and Paginator).</p>

  3. Nov 23, 2011

    <p>Are the names "to", "subject" and "cc" correct for those functions? It feels like there is some inconsistency between those names and the "add...", "set...", "get.." naming convention.</p>

  4. Apr 04, 2012

    <p>One thing that wasn't mentioned was how SMTP authentication would be handled. Since there are multiple authentication strategies (i.e. basic, Xoauth), it would be nice to be able to specify the class name. The implementation in ZF 1.x was inflexible because if you wanted to use a custom auth strategy, you'd have to put the custom class under Zend_Mail_Protocol_Smtp_Auth_*, which was inconvenient if you're using a non-zend namespace.</p>