Sending Email With PHPMailer
[This is a slightly updated copy of the tutorial on now defunct wiki.cakephp.org]
This example will show you how to send HTML mail from you Cake application with PHPMailer.
You will create:
1. cakePHP component
2. a vendor package
3. view for plain text email body
4. view for HTML email body
5. a function in your controller to send mail.
Steps
Get PHPMailer
- Get PHPMailer http://phpmailer.sourceforge.net/
- Unpack it into app/vendors/phpmailer/ , so you'll have /vendors/phpmailer/class.phpmailer.php etc.etc.
Create views and layouts
- Create two views, default_html.thtml and default_text.thtml and place them in app/views/your_controller/email/
- Create a layout for the HTML part of the email, call it app/views/layouts/email.thtml
Create component
- [li]Create new component email. Paste the following code into app/controllers/components/email.php
Component Class:
<?php
/**
* This is a component to send email from CakePHP using PHPMailer
* @link http://bakery.cakephp.org/articles/view/94
* @see http://bakery.cakephp.org/articles/view/94
*/
class EmailComponent
{
/**
* Send email using SMTP Auth by default.
*/
var $from = 'phpmailer@cakephp';
var $fromName = "Cake PHP-Mailer";
var $smtpUserName = ''; // SMTP username
var $smtpPassword = ''; // SMTP password
var $smtpHostNames= ""; // specify main and backup server
var $text_body = null;
var $html_body = null;
var $to = null;
var $toName = null;
var $subject = null;
var $cc = null;
var $bcc = null;
var $template = 'email/default';
var $attachments = null;
var $controller;
function startup( &$controller ) {
$this->controller = &$controller;
}
function bodyText() {
/** This is the body in plain text for non-HTML mail clients
*/
ob_start();
$temp_layout = $this->controller->layout;
$this->controller->layout = ''; // Turn off the layout wrapping
$this->controller->render($this->template . '_text');
$mail = ob_get_clean();
$this->controller->layout = $temp_layout; // Turn on layout wrapping again
return $mail;
}
function bodyHTML() {
/** This is HTML body text for HTML-enabled mail clients
*/
ob_start();
$temp_layout = $this->controller->layout;
$this->controller->layout = 'email'; // HTML wrapper for my html email in /app/views/layouts
$this->controller->render($this->template . '_html');
$mail = ob_get_clean();
$this->controller->layout = $temp_layout; // Turn on layout wrapping again
return $mail;
}
function attach($filename, $asfile = '') {
if (empty($this->attachments)) {
$this->attachments = array();
$this->attachments[0]['filename'] = $filename;
$this->attachments[0]['asfile'] = $asfile;
} else {
$count = count($this->attachments);
$this->attachments[$count+1]['filename'] = $filename;
$this->attachments[$count+1]['asfile'] = $asfile;
}
}
function send()
{
vendor('phpmailer'.DS.'class.phpmailer');
$mail = new PHPMailer();
$mail->IsSMTP(); // set mailer to use SMTP
$mail->SMTPAuth = true; // turn on SMTP authentication
$mail->Host = $this->smtpHostNames;
$mail->Username = $this->smtpUserName;
$mail->Password = $this->smtpPassword;
$mail->From = $this->from;
$mail->FromName = $this->fromName;
$mail->AddAddress($this->to, $this->toName );
$mail->AddReplyTo($this->from, $this->fromName );
$mail->CharSet = 'UTF-8';
$mail->WordWrap = 50; // set word wrap to 50 characters
if (!empty($this->attachments)) {
foreach ($this->attachments as $attachment) {
if (empty($attachment['asfile'])) {
$mail->AddAttachment($attachment['filename']);
} else {
$mail->AddAttachment($attachment['filename'], $attachment['asfile']);
}
}
}
$mail->IsHTML(true); // set email format to HTML
$mail->Subject = $this->subject;
$mail->Body = $this->bodyHTML();
$mail->AltBody = $this->bodyText();
$result = $mail->Send();
if($result == false ) $result = $mail->ErrorInfo;
return $result;
}
}
?>
Useing it in your controller
Controller Class:
<?php
class MyController extends AppController{
var $components = array('Email'); // use component email
...
function send() {
$this->Email->template = 'email/confirm';
// You can use customised thmls or the default ones you setup at the start
$this->set('data', $data);
$this->Email->to = 'someone@somewhere.com';
$this->Email->subject = 'your new account';
$this->Email->attach($fully_qualified_filename, optionally $new_name_when_attached);
// You can attach as many files as you like.
$result = $this->Email->send();
//the rest of the controller method...
}
}
?>
Credits
This is lifted almost word for word from the original piece at http://wiki.cakephp.org/tutorials:sending_email_with_phpmailer but i figured as i was about to use it, i would rewrite it for the bakery.
Enjoy.
Drayen.

Here's given folder structure is also different then latest releases. Therefore I think a new updated article is needed for this task.
The problem is the loading of body content using a buffer (ob_start (), etc.). To avoid this problem, you must slightly change the code and directly set the result of rendering to the $mail varible:
function bodyText() {
/** This is the body in plain text for non-HTML mail clients
*/
//ob_start(); remove this line
$temp_layout = $this->controller->layout;
$this->controller->layout = ''; // Turn off the layout wrapping
$mail = $this->controller->render($this->template . '_text'); //result of rendering to $mail
//$mail = ob_get_clean(); remove this line
$this->controller->layout = $temp_layout; // Turn on layout wrapping again
return $mail;
}
The problem is the loading of body content using a buffer (ob_start (), etc.). To avoid this problem, you must slightly change the code and directly set the result of rendering to the $mail varible:
function bodyText() {
/** This is the body in plain text for non-HTML mail clients
*/
//ob_start(); remove this line
$temp_layout = $this->controller->layout;
$this->controller->layout = ''; // Turn off the layout wrapping
$mail = $this->controller->render($this->template . '_text'); //result of rendering to $mail
//$mail = ob_get_clean(); remove this line
$this->controller->layout = $temp_layout; // Turn on layout wrapping again
return $mail;
}
[end quote]
t worked perfectly: D
Just what it says to do this in the role bodyText (), but it is also necessary in bodyHTML ()
Thanks (and sorry for the bad english)
Anderson Ferreira
$this->attachments[$count+1]['asfile'] = $asfile;
probably should be
$this->attachments[$count]['filename'] = $filename;
$this->attachments[$count]['asfile'] = $asfile;
since arrays in PHP start from 0, not 1.
You can find it at http://phazeforward.blogspot.com/2009/03/emailing-from-cakephp-12x.html
Will see about incorporating the embed() function that Kristofer poste previously.
Add this to the component:
var $embedded = null;
function embed($filename,$cid,$name = '') {
if (empty($this->embedded)) {
$this->embedded = array();
$this->embedded[0]['filename'] = $filename;
$this->embedded[0]['cid'] = $cid;
$this->embedded[0]['name'] = $name;
} else {
$count = count($this->embedded);
$this->embedded[$count+1]['filename'] = $filename;
$this->embedded[$count+1]['cid'] = $cid;
$this->embedded[$count+1]['name'] = $name;
}
}
and make this addtion to the send function:
if (!empty($this->embedded)) {
foreach ($this->embedded as $image) {
if (empty($image['name'])) {
$mail->AddEmbeddedImage($image['filename'],$image['cid'],$image['cid']);
} else {
$mail->AddEmbeddedImage($image['filename'],$image['cid'],$image['name']);
}
}
}
Use it in this way in your controller:
$this->Email->embed('/home/domain/public_html/app/webroot/img/logo.jpg','logo');
And in this way in the view or layout:
1)Create email_html.thtml and email_text.thtml with
<?php echo $content_for_layout ?>in app\views\layouts\2)Open your component controller email.php and add var $layout = 'email'; (where are all vars defined)
3) change functions bodyText() and bodyHTML() to this:
function bodyText() {
/** This is HTML body text for HTML-enabled mail clients
*/
// Temporarily store vital variables used by the controller.
$tmpLayout = $this->controller->layout;
$tmpAction = $this->controller->action;
$tmpOutput = $this->controller->output;
$tmpRender = $this->controller->autoRender;
ob_start();
$this->controller->output = null;
$mail = $this->controller->render($this->template . '_text', $this->layout . '_text');
ob_get_clean();
// Restore the layout, view, output, and autoRender values to the controller.
$this->controller->layout = $tmpLayout;
$this->controller->action = $tmpAction;
$this->controller->output = $tmpOutput;
$this->controller->autoRender = $tmpRender;
return $mail;
}
function bodyHTML() {
/** This is HTML body text for HTML-enabled mail clients
*/
// Temporarily store vital variables used by the controller.
$tmpLayout = $this->controller->layout;
$tmpAction = $this->controller->action;
$tmpOutput = $this->controller->output;
$tmpRender = $this->controller->autoRender;
ob_start();
$this->controller->output = null;
$mail = $this->controller->render($this->template . '_html', $this->layout . '_html');
ob_get_clean();
// Restore the layout, view, output, and autoRender values to the controller.
$this->controller->layout = $tmpLayout;
$this->controller->action = $tmpAction;
$this->controller->output = $tmpOutput;
$this->controller->autoRender = $tmpRender;
return $mail;
}
Now it must be working fine.
(Sorry for bad english)
Unfortunately the email being received is blank.
My component is as follows:
function send() {
$this->Contact->set($this->data);
$this->Email->template = 'email/contact'; // this will use contact_html.ctp
$this->Email->from = $this->data['Contact']['Email'];
$this->Email->fromName = 'My Company';
$this->Email->to = 'xxx@xxx.com';
$this->Email->subject = 'Contact Request';
$result = $this->Email->send();
$this->redirect('thankyou');
exit();
}// end send()
And my email template is app/views/contact/email/contact_html.ctp:
echo $content_for_layout;
Im sure im missing something but I just cant figure it out. Any help is appreciated.
Thanks!
justclint
Thanks!
justclint
Replace:
vendor('phpmailer'.DS.'class.phpmailer');
With:
App::import('Vendor', 'phpmailer', array('file' => 'phpmailer'.DS.'class.phpmailer.php'));
I applied what you said and I get the following error:
Fatal error: Class 'App' not found in ....controllers/components/email.php on line 74
Why it is happening?
I am new to cake.
Please help.
Regards,
Ranjan
echo $this->controller->render($this->template.'_html');
$mail = ob_get_clean();
But I think the problem is not the ob_start() or ob_get_clean() functions. I don't know, but for some reason the controller render function doesn't output the result
I encountered an issue when I tried to use your code with cakephp 1.2
It seems that the solution is to catch the rendering by an other way
$mail = $this->controller->render($this->template . '_text');$this->controller->output = '';
I don't know really why, but ob_start doesn't work for me.
If someone can confirm the problem ? or find an other solution !
i try to chag the $CharSet variable in class.phpmailer.php file to 'windows-1256' and no thing happened, then i try to to put a meta tag in my view : charset('windows-1256');?> and also no thing happened, finally i try to use header() function in the beforeRender function in my controller : header('Content-Type:text/html; charset=windows-1256'); and also no thing happened the characters is still like ���Ö ��....... !!!!! -- :-( .. any can help me please...
Thanks a lot.
$this->Email->attach($fully_qualified_filename, optionally $new_name_when_attached);
What will I use instead of $fully_qualified_filename. Example please.
Thanks in advance.
Warning: Cannot modify header information - headers already sent by (output started at /home/visioniz/public_html/books/app/controllers/components/email.php:114) in /home/visioniz/public_html/books/cake/libs/controller/controller.php on line 447
The funny thing is, I receive the sent emails, no problem, but cake can't render the page.
Please help.
http://www.digital-web.com/articles/bulletproof_contact_form_with_php/
The easiest thing to do is just add these values in your app/config/core.php file e.g.
define('CAKE_EMAIL_USER', 'website@mywebsite.com');
Then in the mailer component you just reference these values:
class EmailComponent
{
/**
* Send email using SMTP Auth by default.
*/
var $from = 'phpmailer@cakephp';
var $fromName = "Cake PHP-Mailer";
var $smtpUserName = CAKE_EMAIL_USER; // SMTP username
This way you've set the default values, but you can still over ride them when you use the component.
how can i declare smtp data (host,user,pass,port) in a config file, so the component reads it from config/email.php for example?!
cheers,
stefan
I do have one problem though, the HTML layout - app/views/layouts/email.thtml simply isn't rendering and I can't work out why.
The emails are sent perfectly just without the layout wrapped around them. I was hoping somebody else had experienced the same problem and found a solution.
Thanks
View Template:
<?php
echo $content_for_layout;
?>
fsockopen(): unable to connect to :25 in /home/content/i/m/r/imranbaran/html/app/vendors/phpmailer/class.smtp.php on line 105
please help
Fatal error: Call to undefined method stdClass::send() in F:\xampplite
\htdocs\cake\app\controllers\users_controller.php
turned out that I had declared
var $components = array('email');
NOT!!
var $components = array('Email');
Just thought I'd post in case anyone else was as daft as me :-).
is the result of that the PHPmailer class cannot locate its language files when placing the complete class in the vendors directory. Moving the complete languages folder of the PHPmailer directory into the webroot of CakePHP fixes the problem.
Although I'm still strugling with a SMTP error, at least I do now receive the correct error :*(
"Language string failed to load: recipients_failed"
It happens when you have smtp on other port then 25 (in my case it was port 26). So I put one more variable to email component class:
var $smtpHostPort= "26";
And a line to function send()
$mail->Password = $this->smtpPassword;
You can call it whatever you want.
For example, for a purchase confirmation email, you might list the products purchased in the emails, so you might use:
or something like that.$products = $this->Session->read('shopping_basket');
$this->set('products',$products);
Thanks.
tut coming soon
1. Put the folowing line inside your controller action:
$this->Email->attach_to($to_address, $to_name);
/*Add this line as many times as you want.*/
2. Add the following to the email component.
//Initialize variable
var $to_arr = null;
//Add function
function attach_to($to_address, $to_name = '') {
if (empty($this->to_arr)) {
$this->to_arr = array();
$this->to_arr[0]['to_address'] = $to_address;
$this->to_arr[0]['to_name'] = $to_name;
} else {
$count = count($this->to_arr);
$this->to_arr[$count+1]['to_address'] = $to_address;
$this->to_arr[$count+1]['to_name'] = $to_name;
}
}
//Add this loop within the send function
if (!empty($this->to_arr)) {
foreach ($this->to_arr as $to) {
$mail->AddAddress($to['to_address'], $to['to_name']);
}
}
Thats it hope it helps someone out.
~Gene Kelly