An idea for sending emails with CakePHP

On the weekend, Felix Geisendörfer (aka the_undefined) presented an email solution for CakePHP based on the ActionMailer class from Ruby on Rails. It inspired me to think about this topic.

The result of this thinking is a proof-of-concept implementation of a mailer component which uses models (and some of Felix’ code). You can download it from CakeForge. Feedback is welcome :)

Here an example of how the approach works. As mentioned above, it uses models for storing the mail data. As a convention, such models must have the instance variables $from, $to, $subject, and $message. I overwrite the save function in this example to set the mail data, but you can also set the data directly, or in one or more custom functions.

// app/models/confirmation_mail.php
class ConfirmationMail extends AppModel
{
    var $useTable = false;
		
    var $from = 'me@mydomain.com';
    var $to = '';
    var $subject = 'Confirmation';
    var $message = '';
		
    function save($data)
    {
        $this->to = $data[$this->name]['to'];
        $this->message = $data[$this->name]['username'] . ', thanks for subscribing.';
			
        return true;
    }
}

The controller is simple: we “save” the data in our mail model, and send the email.

// app/controllers/example_controller.php
class ExampleController extends AppController
{
    var $uses = array('ConfirmationMail');
    var $components = array('Mailer');
		
    function test()
    {
        $this->data['ConfirmationMail']['to'] = 'testuser@domain.com'; 
        $this->data['ConfirmationMail']['username'] = 'testuser'; 
        $this->ConfirmationMail->save($this->data);
        $this->Mailer->send($this->ConfirmationMail);
    }
}

So, what do you think about this approach?

11 Comments

  1. Posted August 7, 2006 at 2:58 pm | Permalink

    Good idea, I think I like that one better then my initial one. I’ll probably change it to work this way.

    But I think overwriting save is something I wouldn’t do. Because by using a real model, this takes away your ability to store copies of the emails you send out to the database. Which would be nice, wouldn’t it?

    Another thing: Do you really want to create a Model for every type of eMail? We don’t create Model’s for every type of Users or such either. I think the Model should be named Mail. Every mail has a var $type. You set it in your controller, and afterwards you Model::set() your data. When the Mailer then sends out the email, he executes your Mail logic. This logic would rest in functions called prepare_$type_mail. i.e. prepare_confirmation_mail.

    That’s just my initial idea, what do you think?

  2. Posted August 7, 2006 at 4:05 pm | Permalink

    I do understand it can be interesting for an Email to be considered has a Model, because this way you can save the email’s data in a database or something ; but there is something that bothers me with this approach : is an Email just a Model ? Isn’t it a View, too ? The message needs to be rendered in it’s own logic : in a View !

    My idea would be to have an EmailComponent capable to render a message using AppView and then send it using an EmailModel — even if I think the Component could send it itself.

    That way any Controller would be able to send email and to render complex emails, just by adding a Component.

  3. Posted August 7, 2006 at 10:29 pm | Permalink

    Hey JMG, have you looked at the ActionMailer class I did? It already does the view rendering like you suggested. This script is only a PoC (according to daniel), so I think it’d do that in future as well.

  4. Posted August 8, 2006 at 6:20 am | Permalink

    What is the benefit of inheriting the Model over creating a compenet? Doesn’t the model come with a low of overhead that is just waisted in this application?

  5. Posted August 8, 2006 at 6:43 am | Permalink

    Hypercubed: Most of the overhead you are talking about is the stuff that get’s executed when a Model *without* var $useTable = false; get’s initialized. Just inherting a class with a good amound of functions shouldn’nt suck up that much performance if it happens several times during a request (which is usally the case). However, a component would take less resources, I agree.

    But this is not about resources, this is about code design. In the MVC pattern all your Domain data belongs in Models, which would also includes e-mails. Now Models are by nature passive and the controller it’s up to the controller how to make use of them. Components in CakePHP are parts of the Controller, and therefor are allowed to operate these kinds of operations as well. So dhofstets idea about inherting AppModel is actually quite beautiful, as it seems to fit very well in the exisitng structures.

  6. Posted August 8, 2006 at 9:53 am | Permalink

    I just noticed something about my prior statement: Maybe having one Model per Mail type makes sense. Because validation get’s messy otherwise. I also forgot about the fact that different mail types mean different fields, which qualifies for different models ; ).

  7. Posted August 8, 2006 at 10:26 am | Permalink

    @all: Thanks for your comments.

    @Felix: Yes, overwriting the save function is only practical if you don’t want to save the mails in the database.

    The reason that each mail has its own model is that for me a confirmation mail is not the same as an invoice mail or a reminder mail. And in fact, the models are just data containers with data to be used in a mail (or whatever system you use for transporting messages).

    @JMG: As Felix already said: it is a proof-of-concept, and so it implements just the easiest case: sending plain text mails.

    Yes, there will be support for views in the future, but at the moment I don’t know how that will look like.

  8. riverfr0zen
    Posted August 8, 2006 at 9:50 pm | Permalink

    Neat. If you’re going with saving the emails to DB, then with little more work, you could come up with a handy QueueController invoked, by say, cron, that fires off email (your ExampleController above just saves the mail model, without sending it).

    The ExampleController could just set a ‘queue’ flag in the mail instance, and save it, so that its picked up by the Queue Controller.

  9. ryan
    Posted August 11, 2006 at 1:11 am | Permalink

    i think couple of people commenting are missing “var $useTable = false;” – tho I could be misreading their comments

    this method is not saving emails to a database, just using a model to hold the email data

    i created a very simple model and controller for a contact form that emails the submitted data, and using a Model for my data meant I could use cake’s built in validation: http://community.livejournal.com/cakephp/1370.html#cutid1

  10. Posted September 5, 2006 at 4:23 am | Permalink

    Why not do both send email and save?

    Just remove the ‘$useTable = false’ reference and then add a call to parent::save().

  11. Posted September 6, 2006 at 9:50 am | Permalink

    @Lamby: Yes, that’s possible, depending on your use case.


%d bloggers like this: