Alan W
Alan W

Reputation: 301

Cakephp not redirecting after sending email through CakeEmail

In CakePHP 2.X, for one of my controller actions, I am trying to send an email to all user's every time a new record is created.

Updated with drmonkeyninja's suggestions:

// NewslettersContoller.php

public function add() {
  if($this->request->is('post')) {
    $this->Newsletter->create();

  if($this->Newsletter->save($this->request->data)) {
      $this->Session->setFlash(__('Your newsletter has been submited.'));
      $this->redirect(array('action' => 'view', $this->Newsletter->id));
  }
  else {
      $this->Session->setFlash(__('Unable to add your post.'));
      return $this->redirect(array('action' => 'add'));
  }
 }
}

// Newsletter.php
public function afterSave($created, $options = []) {
  parent::afterSave($created, $options);
  if ($created === true) {
    $newsletter = $this->findById($this->id);

    // Get all users with an email address.
    $emails = ClassRegistry::init('User')->find(
        'list',
        array(
            'fields' => array(
                'User.id',
                'User.email'
            ),
            'conditions' => array(
                'User.email <>' => null
            )
        )
    );

    $Email = new CakeEmail('gmail');
    $Email->from(array('[email protected]' => 'Caprock Portal'));
    $Email->to($emails);
    $Email->subject($newsletter['Newsletter']['title']);
    $Email->message($newsletter['Newsletter']['content']);
    try {
       $Email->send();
    } catch (Exception $exception) {
        $this->log($exception);
    }
  }
}

As you can see from the code snippet, I use CakeEmail to send every user an email containing the newsletter created in the action. Unfortunately for some reason, every time CakeEmail finishes sending the email, CakePHP ignores my redirect request and proceeds to render a blank view (route is still add). I have verified that it is the CakeEmail function by commenting it and verifying that redirection starts to work again. Nothing is caught in the try-catch block either.

One of my assumptions for the cause of this problem is that the email headers are interfering with the redirect headers for my action. I inspected the network requests through my browser, but nothing seems to be sent out, except from the original post request to the add function.

Upvotes: 2

Views: 609

Answers (4)

darensipes
darensipes

Reputation: 674

Another alternative is to send the emails out as a Shell/Console using a Cronjob.

This is a link to the book where you can find a bunch of information about creating shells.

CakePHP 3.x http://book.cakephp.org/3.0/en/console-and-shells.html

CakePHP 2.x http://book.cakephp.org/2.0/en/console-and-shells.html

This eliminates blocking your processing. If you send emails in controller or model, your app is still waiting for the emails to be sent before the page is rendered or redirected. So by moving to a cronjob the user has as faster better experience because the are not waiting for emails to be sent out.

So how do you know what to send out via the shell??? Well that depends on your use case but I flag records that need sent with a database column for this purpose, find all the ones that haven't been sent, mark those found records with another flag as being processed, the second flag in the database prevents two cron tasks from reading and sending the same records while the first cronjob is still processing. I like to set my cronjobs for mailing as low as possible for transaction type emails, however that can depend on the hosting provider and their settings.

Upvotes: 0

drmonkeyninja
drmonkeyninja

Reputation: 8540

It would be better to send the email from the Newsletter model in the afterSave() callback:-

// app/Model/Newsletter.php

App::uses('CakeEmail', 'Network/Email');

public function afterSave($created, $options = []) {
    parent::afterSave($created, $options);
    if ($created === true) {
        $newsletter = $this->findById($this->id);

        // Get all users with an email address.
        $emails = ClassRegistry::init('User')->find(
            'list',
            array(
                'fields' => array(
                    'User.id',
                    'User.email'
                ),
                'conditions' => array(
                    'User.email <>' => null
                )
            )
        );

        $Email = new CakeEmail('gmail');
        $Email->from(array('[email protected]' => 'Caprock Portal'));
        $Email->to($emails);
        $Email->subject($newsletter['Newsletter']['title']);
        $Email->message($newsletter['Newsletter']['content']);
        try {
           $Email->send();
        } catch (Exception $exception) {
            $this->log($exception);
        }
    }
}

Your controller action would then just be:-

// app/Controller/NewslettersController.php
public function add() {
    if ($this->request->is('post')) {
        $this->Newsletter->create();
        if ($this->Newsletter->save($this->request->data)) {
            return $this->redirect(array('action' => 'view', $this->Newsletter->id));
        } else {
            $this->Session->setFlash(__('Unable to add your post.'));
            return $this->redirect(array('action' => 'add'));
        }
    }
}

If the view is still not rendering then temporarily disable the afterSave() callback to check that the Controller part of the code is working as expected when saving your newsletter.

Note that you can filter out the users without an email address when you retrieve the users from the database. By using find('list') you don't need to mess with a foreach loop.

You also don't need to use $this->Newsletter->getLastInsertId(); as $this->Newsletter->id should have already been set to this at the time of save.

Cake's debug() method is better to use when debugging variables than var_dump() which incidentally won't work in your example as your are redirecting after it has been output!

Upvotes: 2

Populus
Populus

Reputation: 7680

Remove the var_dump. It breaks HTTP protocol if you try to send header information (for the redirect) after content has already been sent.

Upvotes: 0

Supravat Mondal
Supravat Mondal

Reputation: 2584

You can also try specifying the controller in the url array:

if($Email->send()) {
    return $this->redirect(
            array('controller' => 'controller_name', 'action' => 'view', $id)
        );
} else  {
    return $this->redirect(
            array('controller' => 'controller_name', 'action' => 'add')
        );
}        

Upvotes: 0

Related Questions