SimpleMailer Forum Board The Forum Board for the SimpleMailer extension
#1
Posted 20 May 2012 - 07:02 PM
SimpleMailer is a module that helps the administrator on creating and delivering emails from your application. It is created because the need of a simple emailing system tightly integrated to the Yii apps I develop. After many unsuccessful tries to integrate PHPList to my apps I decided to write a solution that can fit in my shoes.
The module, and its documentation can be found at the SimpleMailer extension page.
Please test my freshly released version 0.1 of this Module and don't hesitate to give me any sort of feedback (Bug Reports, Feature Requests, and so on).
Enjoy.
#2
Posted 20 May 2012 - 08:02 PM
Have you thought about allowing inbound emails too?
#4
Posted 28 June 2012 - 08:30 AM
Thank you for the extension. This is my first implementation of mail-queues, so thanks to you I'm having a good start point

I just started to dig into the code and have a couple of minor notices, mostly documentation related
1. Seems, in command to create migration option 'templateFile' should rather be used right after the command, e.g.
php yiic migrate create --templateFile=application.modules.SimpleMailer.migrations.template add_simple_mailer_tables
2. Small typos in configuration:
'SimpleMailer' => array( 'attachImages' => true[b];[/b] 'sendEmailLimit'=> 500[b];[/b] ),
Should be comma(s) instead.
Also at the moment I'm getting Error 404 trying to reach http://DOMAIN/SimpleMailer. I'll give it a closer look tomorrow and will let you know if it was just a problem in my own implementation (which is highly possible)

Regards,
Yuga
#5
Posted 28 June 2012 - 05:24 PM
I've fixed the glitches you found in the documentation. Thanks for pointing them out.
yugene, on 28 June 2012 - 08:30 AM, said:

Ok, don't forget to keep me updated about the (possible) routing/url error. Maybe you just found another outstanding bug. Who knows?

Also, since you're also looking to implement email queues, if you have any suggestions or ideas about how to improve the extension I'll be more than glad to hear them.
Have a good day Yuga,
#6
Posted 28 June 2012 - 11:42 PM
Well, yes, appeared that's something you'd better to update

I didn't dig deeper yet which way exactly paths are resolved to look for an appropriate module, so can't say for sure the definite cure, but 'simpleMailer' won't work too.
I had the same problem not so long time ago with one more module that had uppercase letters in its name, but that time I didn't have time to investigate the problem and just needed some pieces of the code from it. Happy this time I found the cause

Will work today on mailing system implementation, so will let you know if will find / invent anything interesting.
Sincerely,
Yuga
#7
Posted 29 June 2012 - 12:15 AM
Thanks for your response.
Have a good day Yuga.

#8
Posted 29 June 2012 - 05:09 AM
chirvo, on 29 June 2012 - 12:15 AM, said:
Strange then, on some free time will look into it.
Anyway, I have some results to share with you.
First of all, I really like the functionality provided with your module, it gave me even more then I even was thinking about. I think my client may like the idea of Mailing Lists as well

Second, I found 2 bugs so far, both are db related:
1. sm_queue misses `headers` field, which is used in the code.
2. some indexes are wrongly set to be unique, these ones should be fixed to :
$this->createIndex('idx_sm_queue_to', 'sm_queue', 'to', false); $this->createIndex('idx_sm_queue_subject', 'sm_queue', 'subject', false); $this->createIndex('idx_sm_queue_status', 'sm_queue', 'status', false);
Third, I updated the code a bit as
a. I want/need to support variables in all template attributes.
b. I want/need to support global, application specific variables.
I added
public $globalReplacements = array();
to SimpleMailerModule
and assigned vars to it in init(), e.g.
public function init() { $this->setImport(array( 'mailer.models.*', 'mailer.components.*', )); $this->globalReplacements = array( '__appname__'=>Yii::app()->name ); }
Updated compileMultipartTemplate():
/** * Heavily based on http://stackoverflow.com/questions/1606588/how-to-attach-and-show-image-in-mail-using-php and * http://www.phpeveryday.com/articles/PHP-Email-Using-Embedded-Images-in-HTML-Email-P113.html * @param MailerTemplate $template the template instance * @param array $template_partials HTML code to be inserted in the email. array('__key_for_partial__' => '<h1>html</h1>'). * Defaults to array(); * @return array Contents: array($template, $headers) * @throws CException */ public static function compileMultipartTemplate($template, Array $template_partials = array(), Array $template_vars = array()) { $attach_images = Yii::app()->getModule('admin')->getModule('mailer')->attachImages; if (!is_a($template, 'MailerTemplate')) { throw new CException('Mailer::compileMultipartTemplate(): '. Yii::t( 'mailer', 'Wrong object passed, expected MailerTemplate instance.' )); } if (is_array($template_partials) && !empty($template_partials)) $template->body = strtr($template->body, $template_partials); //Then substitute the template variables with actual data $template_vars = array_merge($template_vars, Yii::app()->getModule('admin')->getModule('mailer')->globalReplacements); if (!empty($template_vars)){ $template->body = strtr($template->body, $template_vars); $template->alternative_body = strtr($template->alternative_body, $template_vars); $template->subject = strtr($template->subject, $template_vars); $template->from = strtr($template->from, $template_vars); } $boundary = md5(uniqid(time())); // Substitute every url for img tags with a cid in $template->body if ($attach_images) { $paths = array(); preg_match_all('~<img.*?src=.([\/.a-z0-9:_-]+).*?>~si', $template->body, $matches); foreach ($matches[1] as $img) { if (strpos($img, "http://") == false) { $content_id = md5($img); $url = parse_url($img); $paths[] = array( 'path' => $_SERVER['DOCUMENT_ROOT'] . $url['path'], 'cid' => $content_id, ); $template->body = str_replace($img, 'cid:' . $content_id, $template->body); } } } // Multipart header $headers = "MIME-Version: 1.0\r\n"; $headers .= "Content-Type: multipart/alternative; boundary=\"PHP-alt-$boundary\"\r\n"; $headers .= "From: " . $template->from . "\r\n"; $headers .= "X-Sender-IP: $_SERVER[SERVER_ADDR]\r\n"; $headers .= 'Date: ' . date('n/d/Y g:i A') . "\r\n"; // Text-only body $multipart = "--PHP-alt-$boundary\n"; $multipart .= "Content-Type: text/plain; charset=utf-8\n"; $multipart .= "$template->alternative_body\n\n"; $multipart .= "--PHP-alt-$boundary\n"; $multipart .= "Content-Type: multipart/related; boundary=\"PHP-related-$boundary\"\n\n"; // HTML body $multipart .= "--PHP-related-$boundary\n"; $multipart .= "Content-Type: text/html; charset=utf-8\n"; $multipart .= "Content-Transfer-Encoding: Quot-Printed\n\n"; $multipart .= "$template->body\n\n"; // Images as attachment if ($attach_images) { foreach ($paths as $path) { if (file_exists($path['path'])) $fp = fopen($path['path'], "r"); if (!$fp) { throw new CException('Mailer::compileMultipartTemplate(): '. Yii::t( 'mailer', 'Cannot open file ') . $path['path']); } $image_type = substr(strrchr($path['path'], '.'), 1); $file = fread($fp, filesize($path['path'])); fclose($fp); $message_part = ''; switch ($image_type) { case 'png': case 'PNG': $message_part .= "Content-Type: image/png"; break; case 'jpg': case 'jpeg': case 'JPG': case 'JPEG': $message_part .= "Content-Type: image/jpeg"; break; case 'gif': case 'GIF': $message_part .= "Content-Type: image/gif"; break; } $message_part .= "; file_name = \"" . $path['path'] . "\"\n"; $message_part .= 'Content-ID: <' . $path['cid'] . ">\n"; $message_part .= "Content-Transfer-Encoding: base64\n"; $message_part .= "Content-Disposition: inline; filename = \"" . basename($path['path']) . "\"\n\n"; $message_part .= chunk_split(base64_encode($file)) . "\n"; $multipart .= "--PHP-related-$boundary\n" . $message_part . "\n"; } $multipart .= "--PHP-related-$boundary\n\n"; } // Closing compiled email template $template->body = $multipart. "--PHP-alt-$boundary--\n"; return array($template, $headers); }
Updated process():
private static function process($to, $template_name, Array $template_vars = array(), Array $template_partials = array(), $action = 'send') { if (is_string($to)) { $to = array($to); } /** @var $template MailerTemplate */ $template = MailerTemplate::model()->findByAttributes(array( 'name' => $template_name, )); if (!$template) { throw new CException(Yii::t('mailer', 'Template does not exists.')); } //Template compilation list($template, $headers) = self::compileMultipartTemplate($template, $template_partials, $template_vars); $statuses = array(); foreach ($to as $receiver) { switch ($action) { case 'send': $statuses[$receiver] = mail($receiver, $template->subject, $template->body, $headers); break; case 'enqueue': $queue = new MailerQueue; $queue->to = $receiver; $queue->subject = $template->subject; $queue->body = $template->body; $queue->headers = $headers; $queue->status = MailerQueue::STATUS_NOT_SENT; $statuses[$receiver] = $queue->save(); break; default: break; } } foreach ($statuses as $email => $status) { if (!$status) { Yii::log(Yii::t('app', 'Failed to deliver email: '.$email), 'error'); return false; } } return true; }
Please let me know if you have any comments on these changes.
Okay, next - are just small comments, maybe you'll get any useful information from it:
1. I moved Send Preview Email above the iframe, as iframe has large height and it's not obvious there's more beneath.
2. I manually updated captions/links everywhere to something more neutral, e.g. 'Manage templates'
3. I use your module as a nested module, so to get properties values I call
Yii::app()->getModule('admin')->getModule('mailer')->sendEmailLimit, which is quite long n such case

4. Also, well, sorry, but I renamed all classes to a shorter variant, e.g. 'MailerXxxx', Mailer::process(), hope you won't mind it

5. Also, my idea is maybe later (if will be needed) to implement SwiftMailer to be used with your module.
That's all for now.
Thank you for your module and hope my comments will be helpful somehow.
Back to mailing..
#9
Posted 29 June 2012 - 07:52 AM
there're some more minor updates, maybe you would like to implement some of them into the library:
1. added getStatusName() to SimpleMailerEnqueue, to show not 0/1 but status text name.
2. in MailerCommand
if (!$emails) { Yii::log(Yii::t('mailer', 'No emails in queue. Exiting.'), 'error', 'application.commands.MailerCommand'); exit(0); }
error level is changed to CLogger::LEVEL_INFO
3. template name is read-only on update, to avoid clashes with one name in the code and the other in templates.
4. Really minor:
$email->update(array('status'));instead of save(), MailerCommand.
Cheers
#10
Posted 29 June 2012 - 08:16 PM

Also I created a Github repository for the module. You can find it here:
https://github.com/b...rv/SimpleMailer
I like the idea of changing the names of classes to something shorter. Being a KISS solution it's funny that one has to type ReallyLongNamesForClasses every time.

You made a great contribution Yuga. Thank you again!

#11
Posted 29 June 2012 - 08:19 PM
Well, you managed to fix that for your application, but I want to have a record of this for future users with the same issue.
#12
Posted 29 June 2012 - 10:06 PM
chirvo, on 29 June 2012 - 08:19 PM, said:
Hi Chirvo,
Well, I first installed the module NOT as a nested one and only moved it after I could reach it successfully, so it's not the case.
#15
Posted 29 June 2012 - 10:37 PM
#16
Posted 30 June 2012 - 12:06 AM
I've found the solution, and it's quite simple.
Usually I don't change
'urlManager' => array( 'caseSensitive' => false, )
this parameter, and I didn't give enough attention the previous developer changed it

Docs are quite descriptive about the change:
Quote
#17
Posted 30 June 2012 - 12:08 AM
Thanks in advance!
Cheers,
#18
Posted 30 June 2012 - 06:20 AM
I mostly finished integration of the library, only final tests on production are left (if client won't request additional functionality).
Module works stable and allowed to implement very convenient features just in few hours, so Thanks a lot one more time

Two more small comments:
1. I finally moved Send Preview right after the caption, and it looks more natural for me there.
2. I added Array $testData to TemplateController, and now previews and test emails are rendered with this test data (+ merged with global vars), it think it gives more natural view of templates, especially taking in notice a site admin ( who will handle templates ) isn't a programmer.
Cheers,
Yuga
#19
Posted 22 August 2012 - 06:40 PM
#20
Posted 22 August 2012 - 08:04 PM
SimpleMailer relies on the //layout/column2 layout file to render its pages correctly. If you're not getting the right menu with the navigation options it can be related to your application layout.
Hope this help you solving your issue.
Cheers,