Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: ZfcMail #2

Open
EvanDotPro opened this issue Jul 10, 2012 · 13 comments
Open

RFC: ZfcMail #2

EvanDotPro opened this issue Jul 10, 2012 · 13 comments

Comments

@EvanDotPro
Copy link
Member

Comments and discussion for: RFC: ZfcMail

@EvanDotPro
Copy link
Member Author

It's possible this could be split up into two modules -- one for additional mail adapters and one for an email template system, as both modules would be useful on their own without the other.

@yanickrochon
Copy link

I don't see why it should be split into two modules. An email module would always require a template portion. Or else, what would be the other use for a ZfcTemplate module?

ZfcMail should provide mail adapter and template configurations, all overridable and extendable.

@Danielss89
Copy link
Member

I think voting should start on this.
I think that the most needed functions is covered:
Different mail adapters and mail templates, i think especially that templates are needed, ASAP :D
This module would be used for ZfcUser to send email.

@yanickrochon
Copy link

The core functionalities of such module should include, but not limited to :

  • templates based on partials (setup via config as : "template name" => "path/to/partial.phtml"). The partial would optionally receive an object that can hold data (ex: some order data) and by which the mail subject (or other headers) can be set
  • base controller
    • sendTemplateAction : send template (tid) from specified sender (from [, fromName]), optionally to a specified destination (to [, toName]) where this feature could be disabled to channel all mails to a specified fixed destination. This could also display a form to select the template to send if arguments are not valid.
    • sendAction : display a mail form with the fields from, to, subject, body (the to field can be disabled to channel all mails to a specified fixed destination
    • all actions could provide a redirect on submit (ex: redirect=/user)
    • configurable captcha option for the mail forms
  • configurable transport and default sender / destination
  • service to perform all the functionalities of the module externally
  • events : zfcMail.beforeSend (before partial is rendered), zfcMail.sending (after partial is rendered), zfcMail.afterSend (complete, error, etc.)

@juriansluiman
Copy link
Member

Two modules

I would split the two modules. Transportation is something different than 3rd party email service providers. I have now basic integration with various services in SlmMail and it does not focus on a single transportation system, but integration with the full API. You can check for bounces, get statistics, use tags etcetera.

Modules are small and very lightweight: there is no need to put everything into one module. I assume for most users a templating system is good enough since often you only use SMTP or the standard mail() feature. If you use 3rd party email service providers, it is more likely you test some different services to test them for delivery speed for example.

That said, it is a community vote and if everybody else agree transportation and email template rendering should be put into one module, you can integrate the services from SlmMail easily.

Implementation

Then about @yanickrochon suggested features. I would never send emails in a controller. Forcing developers to extend from a base controller just because you need some email service, is also not really a good example of using abstract classes.

Instead, I would suggest that email rendering should happen in a service layer. You get an email service, where you can inject a transport and basic message object. Then you specify some variables (subject, to address), a view template and view variables. The service is registered in the service manager and you can make other services aware, so with an initializer the mail service is injected.

This method is used in Soflomo\Mail and Soflomo\Log and Soflomo\Cache. I do not provide templating (yet) but it's fairly simple:

namespace MyModule\Service;

use Soflomo\Mail\MessageAwareInterface;
use Soflomo\Mail\TransportAwareInterface;

use Zend\Mail\Message;
use Zend\Mail\Transport\TransportInterface as Transport;

class MyService implements MessageAwareInterface, TransportAwareInterface
{
    protected $message;
    protected $transport;

    public function setMessage(Message $message)
    {
        $this->message = $message;
    }

    public function setTransport(Transport $transport)
    {
        $this->transport = $transport;
    }

    public function doSomething()
    {
        $message = $this->message;
        $message->setTo('[email protected]', 'John Doe');
        $message->setSubject('Hello you');
        $message->setBody('Hi there!');

        $this->transport->send($message);
    }
}

Your service is aware of Mail, Cache or Log and you get it injected. Then you consume it.

You initiate this kind of action from the controller or another service. I can imagine there is a controller plugin invoking the email service to make it easy to send an email from a controller, but this should never happen inside the controller itself:

$this->zfcMail()
     ->mail('[email protected]', 'John Doe')
     ->template('my/email/say-hi', array('name' => 'John'))
     ->send();

Features

Some suggestion for features:

  1. Use the normal view resolvers, so the service can simply render zfc-user/email/activation and that just resolves as normally: no specific template configuration since every zf2 user knows how template names might resolve. Additional logic blurs the picture.
  2. Split the work in three distinctive steps: a. Configure message object b. Render view script c. Send message. The three are consecutively called with a wrapper method. This way you can do various stuff with each result: add an attachment to the mail message object, for example.
  3. Good point for the events. I would suggest a .pre and .post for every above action. That will make a message.pre, message.post, render.pre, render.post, send.pre, send.post for example.

@yanickrochon
Copy link

@juriansluiman, I believe you misunderstood (or perhaps I wasn't clear enough about it) what I meant by having controllers and actions within the module.

As I did mention services and events, the controllers would only serve as built-in and ready-to-use-out-of-the-box module functionality with pre-configured routes that would make use of the declared services and functionalities. Of course, these routes and module core functionality could very well be overridden and disabled in favor of an application specific usage.

Less the mail and template module separation, I actually agree with your feature suggestions. The example you gave does not contradict how I see this module.

Though I am proposing another core feature, to use pre-configured named templates (see my last comment), that could be used as :

// using your last example :
$this->zfcMail()
     ->mail('[email protected]', 'John Doe')
     ->body('my/email/say-hi', array('name' => 'John'))   // or bodyHtml(...) and bodyText(...)
     ->send();

// using a pre-configured named template (overridable via configurations)
$this->zfcMail()
     /// ->mail('[email protected]', 'John Doe')    // optional override
     ->template('template_name', array('name' => 'John'))
     ->send();

And that template could have access to the actual message to setup subject, cc fields, etc.

<?php
// specify extra parameters
$this->message
    ->subject('Message subject')   // could be ignored if already set externally to the template, for example
    ->addCc('[email protected]');

?>
<p>This is a template message for <?= $this->name ?> !</p>
<p>Some other text.</p>

@Thinkscape
Copy link
Member

@juriansluiman I don't like how you're injecting a message into a service. MessageAwareInterface is very odd.

A transport represents a configurable service - once selected and injected into MyService it stays the same.

A message on the other hand, is volatile, passable object that should be created on the fly (new Message()) or with a factory (MessageFactory::factory). It must not be injected from the outside. What if my service wants to send 10 messages?

@juriansluiman
Copy link
Member

@yanickrochon I still don't get why you would want a controller with a route. This is typical business logic a developer should implement him/herself. An publicly available controller action through which every user could send a message sounds like a ContactForm module or something. And contact forms do not necessarily use email as transport and emails are not sent solely for contact forms.

@Thinkscape we've thought about this for a while. We sometimes clone the message after injection. In that case, my example would look like this:

namespace MyModule\Service;

use Soflomo\Mail\MessageAwareInterface;

class MyService implements MessageAwareInterface
{
    // implementation here

    public function doSomething()
    {
        $message = clone $this->getMessage();

        // Rest here
    }
}

I agree messages are an edge case. However, this was our reasoning:

In 1, 5, or perhaps 10 classes we consume email message. In our case, 100% of the messages across all cases comes from the same sender. There are three variables which differ: subject, to address and body message. In the services consuming mail messages, you can easily set those properties. To set the from address in the message, you have either three options:

  1. Inject the from email address + from name in the service. This will be done either by a controller or a factory;
  2. Inject a ModuleOptions class in the service, where you specify a method to get the default from address;
  3. Inject a pre-configured message class.

In case no1 and no2 you can create the message yourself. But no1 is tedious to work with, if you choose no2 you have to depend on a specific module with an options class and pull it there.

With no3 you just can ignore all configuration and assume the message (including encoding, for example) is configured correctly. There is no need to clone if you only send one message (again, in our case also many times), but if you need a loop and send messages, clone the object. Mind that email messages are not shared in the service manager, so each time you inject a message, it is a new class.

@yanickrochon
Copy link

@juriansluiman, perhaps. This I wrote before I had an idea about a general purpose API web service module for other modules to register to (I talked about it last time I logged on IRC with EvanDotPro) that would totally replace controllers here and would only offer services as you mentioned. The rationale was that ZfcUser had controllers that could be extended or overridden, and I thought ZfcMail could offer the same basic functionality out of the box.

BTW: this API module (ex: ZfcAPI or ZfcExternalAPI, etc.) is an idea from WHMCS External API feature. Which would make it very useful for third party (existing?) applications to communicate with applications built on top of ZFC modules.

@Thinkscape
Copy link
Member

@juriansluiman Thx for the explanation, now I get where you came from with this.

In ZF1 and similar fw, basic message info was pre-populated usually in the transport itself or somewhere "nearby".

Bottom line is - the MessageAwareInterface and the injection is basically used for instance templating which reduces boilerplate code and centralizes the configuration of sender address (might also be used for things like reply-to, priority, default subject, etc.)

@ghost
Copy link

ghost commented Dec 19, 2012

Hi there what about zfcmail ?
Everybody seems to agreee with 2 distinct modules.
Everybody seems to agreee that SlmMail is great to manage different transports.
But most of people only need smtp transport so the most important fonctionality is to send mail with html rendering.
@juriansluiman you described a good way to do this with service layer. Do you provide it now into Soflomo/Mail ?

@juriansluiman
Copy link
Member

@booradleys we use Soflomo\Mail now for SMTP integration and message templating. The html rendering is done outside that module, because it is fairly simple to inject the php renderer and render some view script in a service layer.

In the case we used a 3rd party email service provider, we hooked SlmMail into it so we had a pre-configured mail transport for that as well.

@ghost
Copy link

ghost commented Dec 19, 2012

@juriansluiman ok thank you so i'm going to use Soflomo\Mail because i can't wait that zfcMail be ready.
Thank you very much!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants