Yesterday I wrote about how I implemented a simple message queue-like approach for handling emails in CakePHP 2. Today I want to share some lessons I learned post-implementation and some changes I had to make to my classes.
The first issue occurred when a user tried to send an email with a couple of large attachments (several MB). By default,
max_packet_size is set to 1MB, and when CakeEmail calls send(), it reads the data from the attachments and
stores it in the
_message property. Thus, when you serialize it and try to persist it in the database, MySQL can have issues with the size of the packet you
are sending it.
My initial solution to this was to simply increase
max_packet_size. Our SMTP server has a hard limit of 20MB on emails
and our application is set to automatically reject any email greater than that size, so I decided that setting
max_packet_size = 30M
would be fine - it would cover the largest possible email, plus some head-room. But then I started thinking about what
could happen if two users sent 17MB emails at once and then a queue worker tried to retrieve 30+ MB of data in one query.
Would it fail then?
I decided that it was best not to find out and decided to tackle the problem another way. As mentioned above, the reason the stringified object is so large is that CakeEmail’s send method “stringifies” the attachments. I thought it would be optimal to do this only when actually trying to send the email, and that it wasn’t necessary to do this when saving in the database.
Our app was already using an extended version of CakeEmail called
CustomEmail, so I had to override the
send() method as follows:
This is a very simple piece of code. All it does is determine what transport class our email object is using, and if it
DbQueueTransport rather than
SmtpTransport (or anything else for that matter), it skips the rest of Cake’s
usual send() routine and goes straight to the send method of our
DbQueueTransport class (see previous post for this).
That’s it! I found when testing this on my development environment that sending an email with a 4MB attachment and a long
message body resulted in a stored string of more than 6MB; using this method the stored value was far smaller - ~200KB.
Most importantly, when the worker retriever the email later, it sent it with the attachments successfully.
You may notice that I’ve also added a protected property
$_content to this custom class (along with a getter method) and set it before calling send
on the transport class. This is to address the second issue I encountered. You may recall that in my original post I had the following code
in the worker class:
The reason for these two lines of code was simple: you can call
CakeEmail::send() with or without a parameter
Most of the time you will want to build a pretty HTML email and you would use the
tempate() method for that, calling
send() without a parameter. But for some
very short notifications it can be useful to just do something like
CakeEmail::send("Your message here"). If you were to call
$email->send() in the worker without passing in the content parameter, you would lose “Your message here” completely.
However, I discovered that
CakeEmail::message() returns more than simply what you pass in as the parameter to
if you are also attaching files,
message() returns the “content” of those files and you end up with an email full of
garbled junk data that is meaningless to the sender or recipient.
To solve this, I added the
$_content property to
CustomEmail and then set it using the
$content value passed in to
CustomEmail::send(), and then added a getter method to retrieve the value. The updated worker class looks like this: