SwiftMailer Component Tutorial
Based on Chris Corbyn's excellent Swift Mailer. This is a component that does encapsulate it but not hide it, while it adds some nifty features. Read on how to bake with it.
Prerequisites
I would still advise you read the Swift Mailer documentation here: http://www.swiftmailer.org/docs/ SwiftMailer Component isn't an attempt to hide the already excellent class written by Chris Corbyn. Rather, the component tries to integrate and extend it (read, Cake-ish!).
The Swift object is still accessible through the member variable $this->SwiftMailer->mailer.
But first things first, Swift Mailer can be found here: http://www.swiftmailer.org/download/ The version this Component is based on is Swift-2.1.17-php4.
Download the package and extract it. put Swift.php and the Swift folder into your vendors directory.
you should have something like:
Download code
vendors/
Swift/
Swift.php
You are ready to go.Installation
Grab the component located here: http://bakery.cakephp.org/articles/view/192 Put it in your components folder and save it as swift_mailer.php, not swiftmailer.php!If you don't understand why, you should probably read the conventions appendix in the manual here:
http://manual.cakephp.org/appendix/conventions
Add the component to your controller:
Download code
var $components = array('SwiftMailer'); // along with the other components you use.
Woot, you serious?! Yep. Well you know, it's Cake! ;)Making the connection
Swift Mailer has three connection types: native, sendmail and smtp. We're going to see how to establish a connection using each type.Native Mail
In this type, Swift uses the PHP's mail function.This is what you should do:
Download code
$this->SwiftMailer->connection = 'native';
if($this->SwiftMailer->connect())
{
// ..
}
That's all? Absolutely.Sendmail
Quoting the Swift Mailer documentation:Establishes a I/O hook to a sendmail process running in -bs mode. The binary does not strictly need to be sendmail, it will work with exim, qmail and postfix too providing the version in question supports running in -bs mode.Here is how to do it using the component:
Download code
$this->SwiftMailer->connection = 'sendmail';
$this->SwiftMailer->sendmail_cmd = '/usr/sbin/sendmail -bs';
if($this->SwiftMailer->connect())
{
// ..
}
$sendmail_cmd defaults to false, Swift will then try to find sendmail itself by using the linux command `which`. Setting it to 'default' is equivalent to '/usr/sbin/sendmail -bs'.SMTP
This is the default connection type. Here is how to use it:Download code
$this->SwiftMailer->connection = 'smtp'; // default and thus you don't have to specify it
$this->SwiftMailer->smtp_host = 'smtp.gmail.com';
$this->SwiftMailer->smtp_type = 'tls';
if($this->SwiftMailer->connect())
{
// ..
}
$smtp_host is the fully qualified domain name of the server you wish to connect to. Defaults to null in the Component, which means Swift will read the value from php.ini$smtp_port: The default SMTP port is 25, 465 for SSL. It is set to false in the Component, to let Swift choose the value depending on $smtp_type. Set it to null to tell Swift to read the value from php.ini
$smtp_type Can be one of the following values: 'open', 'ssl', 'tls'.
Depending on your SMTP server, you should set this value accordingly.
By the way Gmail uses TLS.
Auth
If your server requires authetication, you can set up your username and password by doing the following:Download code
$this->SwiftMailer->username = 'user@gmail.com';
$this->SwiftMailer->password = 'secret';
Depending on your server, your username might be 'user' or 'user@domain.tld'.
Sending Mail
To avoid redundancy, I'm not going to focus on how to send mail using the Swift Mailer, rather, I will show how to do it using the component. If you need to access the Swift object directly, use $this->SwiftMailer->mailer.This component isn't attempting to hide Swift, but rather extend it's functionality and make it Cake-ish. Swift's syntax is really clear/clean, and re-inventing it is really pointless. That being said, I'm going to show you what this component adds:
addTo($type, $address, $name = false)
Originally, this function was written by TommyO in his component. I liked the idea, so I added it. I had to modify it though.$type Can be one of these values: 'from', 'to', 'cc', 'bcc'.
You can have many 'to','cc', 'bcc' entries, but only one 'from'.
Here are examples of usage:
Download code
$this->SwiftMailer->addTo('from',"user@gmail.com","firstname lastname");
$this->SwiftMailer->addTo('to',"user@domain.tld");
$this->SwiftMailer->addTo('to',"foobar@domain.tld","Foo Bar");
$this->SwiftMailer->addTo('to',"crazylegs@gmail.com","CraZyLeGs");
$this->SwiftMailer->addTo('cc',"cc1@domain.tld","C C 1");
$this->SwiftMailer->addTo('cc',"cc2@domain.tld","C C 2");
$this->SwiftMailer->addTo('bcc',"bcc1@domain.tld","B C C 1");
$this->SwiftMailer->addTo('bcc',"bcc2@domain.tld","B C C 2");
Adding a body
To add body to your message you can use the $mailer object within the component. Refer to the Swift Mailer documentation for more info.Example:
Download code
$this->SwiftMailer->mailer->addPart("Plain Body");
$this->SwiftMailer->mailer->addPart("Html Body", 'text/html');
I want to send it, damn it!
Fine.Download code
$this->SwiftMailer->send("Subject");
Happy? You could just do it with internal Swift object though: $this->SwiftMailer->send(...);What's the difference, then? Well, the component's send method takes into consideration the parts you added using addPart, and if you have specified a username and password, it will try to authenticate.
Oh really?! Really. Nice!!!. I know.
Until now, it's been pretty basic, but bear with me and continue reading.
wrapBody($msg, $type = 'plain', $return = false)
Actually, the exciting part starts here. The Swift Mailer Component has the capability to wrap your body message with a layout. Yeah, a Cake layout.In the controller there is a variable called $layout, which defaults to 'swift_email', and it is the layout you want your message to get wrapped with. You need to create swift_email.thtml in your layouts folder.
An example of a layout:
View Template:
Download code
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<base href="<?=FULL_BASE_URL?>" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>CakePHP SwiftMailer Component</title>
</head>
<body style="background: #dedede; padding-top: 100px;" >
<?=$html->image('w3c_xhtml10.png')?> <br/>
<?=$html->image('w3c_css.png')?><br/>
<?=$html->image('cake.power.png',array('embed'=>'swift'))?><br/>
<?=$content_for_layout; ?>
</body>
</html>
Please notice " />, You need to have this in order for internal elements ( img, anchors, etc.) to link properly from within the mail client reading the Email you're going to send.
There is also $email_views_dir which is the folder within the views directory where emails to be sent will be stored. More on that in the next section, but for now you need to have a view called default.thtml in there for wrapBody to function.
Content of default.thtml :
View Template:
Download code
<?php echo $swiftMailer_data;?>
$msg is the message you want to get wrapped by the layout.$type Either 'plain' or 'html', if plain, the HTML tags are stripped.
$return defaults to false, if true the wrapped msg is returned instead of added in the mail. You can call $this->SwiftMailer->mailer->addPart to add it then.
Usage:
Download code
$this->SwiftMailer->wrapBody("My Plain Body");
$this->SwiftMailer->wrapBody("My HTML Body",'html');
$body = $this->SwiftMailer->wrapBody("I want my body",'html',true);
This function can be useful for example to send a newsLetter, the body content is coming from the database where you archive the newsletters sent, you wrap the newsletter with a layout and you're all set!function viewBody($name, $type = 'both', $return = false)
With SwiftMailer Component, you have the possibility to send views you prepared in advance. A common example would be a confirmation email, etc. The views should be located in the directory specified by $email_views_dir.$msg is the name of the view you want to send without .thtml
$type can be one of the following values 'plain', 'html' or 'both', 'both' is the default
$return defaults to false, if true the rendered view is returned instead of added in the mail. you can call $this->SwiftMailer->mailer->addPart to add it then.
if $type is plain, the html tags are striped. if 'both', both an html and plain versions are added.
The 'html' view must have the suffix '_html', so if you plan to send an html confirmation email, you should name your view confirm_html.thtml.
So if type is 'both' and you want to send the confirm view, you need to actually have two views, one named confirm.thtml for the plain version and one named confirm_html.thtml for the HTML version. Clear? Thought so.
Usage:
Download code
$this->SwiftMailer->viewBody('confirm'); // defaults to 'both'
$this->SwiftMailer->viewBody('confirm','plain');
$this->SwiftMailer->viewBody('confirm','html');
$html_plain = $this->SwiftMailer->viewBody('confirm','plain',true);
$html_body = $this->SwiftMailer->viewBody('confirm','html',true);
// $both_body will contain an array of both the 'plain' and 'html' versions, in this order.
$both_body = $this->SwiftMailer->viewBody('confirm','both',true);
Shortcut functions
sendWrap($subject, $body, $type = 'plain')
This function is equivalent to calling wrapBody and send.sendWrap($subject, $body, $type = 'plain')
This function is equivalent to calling viewBody and send.Complete example
Download code
$this->SwiftMailer->connection = 'smtp';
$this->SwiftMailer->smtp_host = 'smtp.gmail.com';
$this->SwiftMailer->smtp_type = 'tls';
$this->SwiftMailer->username = 'user@gmail.com';
$this->SwiftMailer->password = 'secret';
if($this->SwiftMailer->connect())
{
$this->SwiftMailer->addTo('to',"crazylegs@gmail.com","CraZyLeGs");
$this->SwiftMailer->addTo('from',"user@gmail.com","some user");
if(!$this->SwiftMailer->sendView("SwiftComponent::sendView Exemple","confirm",'both'))
{
echo "The mailer failed to Send. Errors:";
pr($this->SwiftMailer->errors());
}
echo "Log:";
pr($this->SwiftMailer->transactions());
}
else
{
echo "The mailer failed to connect. Errors:";
pr($this->SwiftMailer->errors());
}
Bonus
One thing that is nice with Swift Mailer, is a function called addImage, it embeds an image into the body of the email to display inline.Something like:
View Template:
Download code
<img src="'.$swift->addImage($path_to_image).'" alt="Holiday" />
The problem with that though, is that Cake is an MVC framework and thus the view doesn't have business logic so it can not access the Mailer, well actually it can($this->controller->SwiftMailer->mailer->addImage(WWW_ROOT.'img'.'ewww_ugly.jpg');), but it should not, read it, must not.And so there is no obvious way of calling addImage from the view, if we want to embed images. I hear you saying, use a helper, will this won't solve the issue, because, you would want to create an instance of Swift, that instance will not be the one the component is using.
Oh Ma'..so what's the solution? Heh, don't beat me if I say that it's already solved. actually the component's viewBody function solves it. Shut up!! yeah, it actually looks for images that have the param embed="swift" in them and converts them automagically into embeded images! You're kidding? Hell, no.
[view] =$html->image('cake.power.png',array('embed'=>'swift'))?> That was my bonus.
Conclusion
That's it guys, I hope you'll find the component useful, Thanks to Chris Corbyn for making such a great class. As always, comments enhancements, typo corrections, bug reports are welcome.
Comments
Comment
1 Merge view with variables
I would like to be able to setup a view for an email and have placeholder for data e.g.
Hello [Firstname]
Is this possible?
Bug
2 Swift Mail could not Connect
Call to undefined method stdClass::connect() in /data/projects/cake_test/controllers/admins_controller.php on line 80
Here is my controller code
function news()
{
$res = $this->Member->query("SELECT members.email FROM members");
foreach($res as $val)
{
$this->SwiftMailer->connection = 'native';
if($this->SwiftMailer->connect())
{
$this->SwiftMailer->addTo('to',"$val","CraZyLeGs");
$this->SwiftMailer->addTo('from',"santosh@apexdivision.com","Santosh");
if(!$this->SwiftMailer->sendView("SwiftComponent::sendView Exemple","confirm",'both'))
{
echo "The mailer failed to Send. Errors:";
pr($this->SwiftMailer->errors());
}
echo "Log:";
pr($this->SwiftMailer->transactions());
}
else
{
echo "The mailer failed to connect. Errors:";
pr($this->SwiftMailer->errors());
}
}
}
Comment
3 Thank You
I changed the component
vendor('Swift');
vendor('Swift/Connection/SMTP');
To
vendor('Swift'.DS.'Swift');
vendor('Swift'.DS.'Connection'.DS.'SMTP');
And I am successfull in sending mails also newsletters
Comment
4 Sending Attachment With Swift Mailer
Just a little modification in The sendWrap function() [Which I am using to send my mails] of the component.
This is how it looks now
function sendWrap($subject, $body, $type = plain',$data,$filename,$type) {
$this->wrapBody($body, $type);
$this->mailer->addAttachment($data, $filename, $type);
return $this->send($subject);
}
Comment
5 The Post is not at all abug
This is not at all a bug it is just a simple error. From my part.
Comment
6 Awsome Component
Previously I had had some errors but those are all due to my coding not for the component. I have fixed all of them and my newsletter is working great. Thanks again.
Mithun
Comment
7 loading Swift vs. EasySwift
My first issue was that the tutorial says to copy in Swift.php so you have the following directory structure: /app/vendors/Swift/Swift.php. I found that Swift was not being loaded, as the vendors line in the component is lookinf for Swift.php in the vendors root dir. The solution is to copy the Swift.php file, the EasySwift.php file (more on this later) and Swift folder from the downloaded package to the vendors folder. So now you have
/app/vendors/EasySwift.php
/app/vendors/Swift.php
/app/vendors/Swift/{lots of stuff}
Had issues with notices of undefined constants on things like 'SWIFT_TLS'. So did a search of all the files in the package and found that only EasySwift.php defines these at all. So, I had to change all lines reading
vendors('Swift');to
vendors('EasySwift');That cleared up the notices, and actually started looking like it was trying to connect via tls:// protocol.
Finally, Swift's inbuilt debugging statements told me to go enable tls for php, which I'm working on now. Will write again if that didn't solve everything.
Comment
8 EasySwift continued
/**
* EasySwift: Facade for Swift Mailer Version 3.
* Provides (most of) the API from older versions of Swift, wrapped around the new version 3 API.
* Due to the popularity of the new API, EasySwift will not be around indefinitely.
* @package EasySwift
* @author Chris Corbyn
*/
SO, this means that all calls to 'new Swift' in swift_mailer.php must become 'new EasySwift'
I'm still having issues, but it may be how I'm trying to authenticate, rather than with the component.
Comment
9 Use this component in 1.2.x.x with a minor fix
Make this change from line 40 to line 72. It's just substituting the deprecated vendor() function for the new App::import()
function connect_native() {
App::import( 'vendor', 'Swift', array('file' => 'Swift.php'));
App::import ( 'vendor', 'SwiftConnectionNativeMail', array('file' => 'Swift'.DS.'Connection'.DS.'NativeMail.php') );
$this->mailer = new Swift(new Swift_Connection_NativeMail());
}
function connect_sendmail() {
App::import( 'vendor', 'Swift', array('file' => 'Swift.php'));
App::import( 'vendor', 'SwiftConnectionSendMail' , array('file' => 'Swift'.DS.'Connection'.DS.'SendMail.php'));
if ($this->sendmail_cmd == false) {
$this->sendmail_cmd = SWIFT_AUTO_DETECT;
}
elseif ($this->sendmail_cmd == 'default') {
$this->sendmail_cmd = '/usr/sbin/sendmail -bs';
}
$this->mailer = new Swift(new Swift_Connection_SendMail($this->sendmail_cmd));
}
function connect_smtp() {
App::import( 'vendor', 'Swift', array('file' => 'Swift.php') );
App::import( 'vendor', 'SwiftConnectionSMTP', array('file' => 'Swift'.DS.'Connection'.DS.'SMTP.php'));
Comment
10 Blank emails
$this->SwiftMailer->wrapBody($msg, 'html', false);
the app breaks with:
Fatal error: Call to a member function on a non-object in /path/to/app/controllers/components/swift_mailer.php on line 232
If I set the $return parameter to true, it will return $msg and send the email, but the email is empty and doesn't respect the $type parameter, sending text/plain.
Any assistance anyone can offer would be appreciated. Thanks in advance.
Comment
11 Blank emails
As it turns out, I had the wrapBody() before the connect() statement. DOH!
But the email is still sent empty. It IS now respecting the $type.
Comment
12 Blank emails No More
Component Class:
<?php
<?php
/*
* SwiftMailer Component By othman ouahbi.
* comments, bug reports are welcome crazylegs AT gmail DOT com
* @author othman ouahbi aka CraZyLeGs
* @version 0.1
* @license MIT
*/
class SwiftMailerComponent extends Object {
var $controller = false;
var $mailer = null;
var $connection = 'smtp'; // sendmail, native
var $smtp_host = null; // null auto detect
var $smtp_port = false; // false to let the mailer choose. default 25, 465 for ssl.
var $smtp_type = 'open'; // open, ssl, tls
var $username = null;
var $password = null;
var $layout = 'swift_email';
var $email_views_dir = 'email';
var $sendmail_cmd = false; // false: SWIFT_AUTO_DETECT, 'default': '/usr/sbin/sendmail -bs' etc..
function startup(& $controller) {
$this->controller = &$controller;
}
function connect() {
switch ($this->connection) {
case 'smtp' :
$this->connect_smtp();
break;
case 'sendmail' :
$this->connect_sendmail();
break;
case 'native' :
default :
$this->connect_native();
break;
}
return $this->mailer->isConnected();
}
// function isConnected() {
// return true;
// }
function connect_native() {
App::import( 'vendor', 'SwiftMailer/EasySwift', array('file' => 'EasySwift.php'));
// App::import ( 'vendor', 'SwiftConnectionNativeMail', array('file' => 'Swift'.DS.'Connection'.DS.'NativeMail.php') );
$this->mailer = new EasySwift(new Swift_Connection_NativeMail());
}
function connect_sendmail() {
App::import( 'vendor', 'SwiftMailer/EasySwift', array('file' => 'EasySwift.php'));
// App::import( 'vendor', 'SwiftConnectionSendMail' , array('file' => 'Swift'.DS.'Connection'.DS.'SendMail.php'));
if ($this->sendmail_cmd == false) {
$this->sendmail_cmd = SWIFT_AUTO_DETECT;
}
elseif ($this->sendmail_cmd == 'default') {
$this->sendmail_cmd = '/usr/sbin/sendmail -bs';
}
$this->mailer = new EasySwift(new Swift_Connection_SendMail($this->sendmail_cmd));
}
function connect_smtp() {
App::import( 'vendor', 'SwiftMailer/EasySwift', array('file' => 'EasySwift.php') );
// App::import( 'vendor', 'SwiftConnectionSMTP', array('file' => 'Swift'.DS.'Connection'.DS.'SMTP.php'));
// SWIFT_AUTO_DETECT
if (is_null($this->smtp_host)) {
$this->smtp_host = SWIFT_AUTO_DETECT;
}
if (is_null($this->smtp_port)) {
$this->smtp_port = SWIFT_AUTO_DETECT;
}
$ssl_types = array('open'=>SWIFT_OPEN,'ssl'=>SWIFT_SSL,'tls'=>SWIFT_TLS);
if(in_array($this->smtp_type, array ('open','ssl','tls')))
{
$ssl_type = $ssl_types[$this->smtp_type];
}else
{
$ssl_type = $ssl_types['open'];
}
$this->mailer = new EasySwift(new Swift_Connection_SMTP($this->smtp_host, $this->smtp_port, $ssl_type));
}
function auth() {
return ($this->mailer->authenticate($this->username, $this->password));
}
function errors() {
return $this->mailer->errors;
}
function transactions() {
return $this->mailer->transactions;
}
function close() {
$this->mailer->close();
}
/*
* description:
* Renders a body view located in the emails dir.
* if html, wraps it with a layout and embeds images that have the embed="swift" attribute
* strip tags if plain.
*/
function viewBody($name, $type = 'both', $return = false) {
switch ($type) {
case 'both' :
$plain = true;
$html = true;
break;
case 'html' :
$html = true;
break;
case 'plain' :
$plain = true;
break;
default :
return;
break;
}
if (isset ($html)) {
$name .= "_html";
$view = $this->email_views_dir . DS . $name;
$old_layout = $this->controller->layout;
ob_start();
$this->controller->render(null, $this->layout, $view);
$html_msg = ob_get_clean();
$html_msg = $this->replaceIMG($html_msg);
$this->controller->layout = $old_layout;
}
if (isset ($plain)) {
$view = $this->email_views_dir . DS . $name;
$old_layout = $this->controller->layout;
$this->controller->layout = '';
ob_start();
$this->controller->render(null, null, $view);
$plain_msg = strip_tags(ob_get_clean());
$this->controller->layout = $old_layout;
}
switch ($type) {
case 'both' :
if ($return) {
return array (
$plain_msg,
$html_msg
);
}
$this->mailer->addPart($html_msg, 'text/html');
$this->mailer->addPart($plain_msg, 'text/plain');
break;
case 'html' :
if ($return) {
return $html_msg;
}
$this->mailer->addPart($html_msg, 'text/html');
break;
case 'plain' :
if ($return) {
return $plain_msg;
}
$this->mailer->addPart($plain_msg, 'text/plain');
break;
}
}
function replaceIMG($msg) {
$matches = array ();
$files = array ();
if (preg_match_all('#<img.*src=\"(.*?)\".*?\/>#', $msg, $matches)) {
for ($i = 0; $i < count($matches[0]); $i++) {
$pos = strpos($matches[0][$i], 'embed="swift"');
if ($pos !== false) {
$file = substr($matches[1][$i], strrpos($matches[1][$i], '/') + 1);
if (array_key_exists($file, $files)) {
$replace = $files[$file];
} else {
$replace = $this->mailer->addImage(WWW_ROOT . 'img' . DS . $file);
$files[$file] = $replace;
}
$msg = str_replace($matches[1][$i], $replace, $msg);
}
}
}
return $msg;
}
/*
* description:
* Wraps the body with a layout, strips tags if not html
*/
function wrapBody($msg, $type = 'plain', $return = false) {
$view = $this->email_views_dir . DS . 'default';
$this->controller->set('swiftMailer_data', $msg);
// commented out buffer code... that's what was blanking the email output for some reason.
// ob_start();
$this->controller->render(null, $this->layout, $view);
// $msg = ob_get_clean();
if ($type != 'html') {
$msg = strip_tags($msg);
}
if ($return) {
return $msg;
}
$msgType = ($type == 'html') ? 'text/html' : 'text/plain';
$this->mailer->addPart($msg, $msgType);
}
// original idea Tommy0
function addTo($type, $address, $name = false) {
if (in_array($type, array (
'to',
'from',
'cc',
'bcc'
))) {
if (!$name) {
$val = $address;
} else {
$val = array (
$name,
$address
);
}
if ($type == 'from') {
$this-> $type = $val;
} else {
if (!isset ($this-> $type)) {
$this-> $type = array ();
}
array_push($this-> $type, $val);
}
}
}
// original idea Tommy0
function send($subject) {
if (!empty ($this->cc)) {
$this->mailer->addCc($this->cc);
}
if (!empty ($this->bcc)) {
$this->mailer->addBcc($this->bcc);
}
if (is_array($this->from)) {
$from = '"' . $this->from[0] . '" <' . $this->from[1] . '>';
} else {
$from = $this->from;
}
if (!empty ($this->username) && !$this->auth()) {
return false;
}
if ($this->mailer->send($this->to, $from, $subject)) {
$this->mailer->close();
return true;
}
return false;
}
function sendWrap($subject, $body, $type = 'plain') {
$this->wrapBody($body, $type);
return $this->send($subject);
}
function sendView($subject, $view, $type = 'plain') {
$this->viewBody($view, $type);
return $this->send($subject);
}
}
?>
?>