Build WordPress Email Wrapper (With Attachments)

WordPress default email sending function wp_mail is way too basic. It uses "Wordpress" as sender name and doesn't allow you send HTML and plain text mail at the same time. If you are writing a plugin that sends emails this is going to be a problem.

In this quick tutorial I will show you how plugins like BroadFast for Wordpress manage to overwrite the default sender and to send emails with plain text and HTML versions. You simply need a small wrapper function. See the full code first and I will explain it below:

  1. function send($sender$receiver$subject$message$ctype = 'text/html'$attachments = NULL) {  
  2.     $plain_text = strip_tags(str_replace("<br>""\n"$message));  
  4.     // handle text-only and "both" mail types  
  5.     if($ctype=='text/plain'$message = $plain_text;  
  6.     else $message=wpautop($message);  
  8.     if($ctype=='both') {  
  9.         // thanks to http://www.tek-tips.com/faqs.cfm?fid=2681                    
  10.         $semi_rand = md5(time());  
  11.         $mime_boundary = "==MULTIPART_BOUNDARY_$semi_rand";  
  12.         $mime_boundary_header = chr(34) . $mime_boundary . chr(34);  
  14.         // construct the body  
  15.         $body = "This is a multi-part message in MIME format. 
  17.         --$mime_boundary 
  18.         Content-Type: text/plain; charset=\"UTF-8\" 
  19.         Content-Transfer-Encoding: 8bit 
  21.         $plain_text 
  23.         --$mime_boundary 
  24.         Content-Type: text/html; charset=utf8 
  25.         Content-Transfer-Encoding: 8bit 
  27.         $message 
  29.         --$mime_boundary--";  
  31.         $body = str_replace("\t""" ,$body);  
  33.         // now replace the vars  
  34.         $message = $body;  
  35.         $ctype = "multipart/alternative;\n" .   
  36.    "     boundary=" . $mime_boundary_header;              
  37.     }  
  39.     $headers=array();  
  40.     $headers[] = "Content-Type: $ctype";  
  41.     $headers[] = 'From: '.$sender;  
  42.     $headers[] = 'sendmail_from: '.$sender;  
  44.     // prepare attachments if any     
  45.     if($attachments and is_array($attachments)) {  
  46.         $atts = array();  
  47.         foreach($attachments as $attachment$atts[] = $attachment->file_path;  
  48.         $attachments = $atts;  
  49.     }  
  51.     $message = do_shortcode($message);        
  52.     $result = wp_mail($receiver$subject$message$headers$attachments);                 
  53.     return $result;  
  54. }  

Now, let's have a look at this code:

Line 1. The function definition: The function accepts sender's email address that can contain just email address or a string like My Name <myemail@dot.com>. This allows you to send emails with different name than "Wordpress" (how exactly this happens is explained later below. Then $receiver is the receiver email address and $subject and $message are obvious parameters.

The parameter $ctype is the email content type - it can be either "text/html", "text/plain" or "both". Using "both" will generate email with two alternative parts so the email client program can choose the best one. Note that "both" doesn't work together with attachments. This is currently unsolvable - so if you want to send attachments, choose text/html or text/plain.

Now, let's go on with the code.

Line 2. Create a plain text version of the message to be used when sending the email as plain text.

Lines 4 - 5 take care to further prepare the message depending on the selected content type.

We have more work to do when sending both HTML and plain text version. It starts after line 8. First we generate the mime boundary by preparing a pseudo random number. Then from line 15 we start constructing the body of the message. First add the intro text, then the boundary, and then the two headers:

Content-Type: text/plain; charset=\"UTF-8\"
Content-Transfer-Encoding: 8bit

before the plain text version (lines 17 - 21) and:

Content-Type: text/html; charset=utf8
Content-Transfer-Encoding: 8bit

Before the HTML version (lines 23 - 27). Then closing the mime boundary.

Please pay attention to all the formatting and new lines, they are very important.

Then we have to remove any tabulators which come from our code indentation so we do it on line 31.

Lines 34 - 36 replace the proper content type for the email header.

Then we need to construct the headers to pass to wp_mail() function in WordPress.

The headers "from" and "sendmail_from" (lines 41 and 42) are very important. They are the lines that ensure when you set "John <email@dot.com>" as email sender, the email will be sent from "John" and not from "WordPress".

The next lines simply add the attachments (assuming you have the object coming from database or so).

Process shortcodes. I prefer to do this (line 51) because this enables the email author to include shortcodes from other plugins in the email contents. Be careful with this if you are letting end users send emails.

Then line 52 calls wp_mail and the function returns the result. Simple, isn't it?


How To Conditionally Add Fields in MySQL DB Table

The Problem

When building an installation script for your software you usually have to create the required database tables. This is easy to do with running simple DB queries. Works great when installing first version. But once you have to start upgrades it can quickly become a nightmare. Of course you can't always create the tables from scratch because the user might already have some data in them! So you have to update them by adding the new fields only.


The Typical Solution

The usual way of handling this is storing the current version in the database, then running ALTER TABLE queries only if the version installed is older than the version when the new fields are added. This is a decent approach but rather error-prone because you have to be very careful of updating version numbers and assigning new fields to them.


The Better Approach

Instead of this, I am suggesting you the simpler approach we follow with our Wordpress plugins - for example BroadFast for Wordpress. We do frequent upgrades there and instead of trying to match ALTER TABLE statements to version numbers, we choose a conditional approach to adding fields in the tables.

Conditional simply means that we'll check if a field is already in the table, and if not, we'll add it only then. Let's see the function:

  1. // $fields - array of arrays. Each field is associative array having name and type.  
  2. // see the sample call after the function to get better idea  
  3. // $table - the name of the target table  
  4. function add_db_fields($fields$table) {  
  5.     global $wpdb// this is in WordPress, you may use some other object in your system  
  7.     // get existing fields  
  8.     $table_fields = $wpdb->get_results("SHOW COLUMNS FROM `$table`");  
  9.     // let's store the names only in this array  
  10.     $table_field_names = array();  
  11.     foreach($table_fields as $f$table_field_names[] = $f->Field;  
  13.     // and this is the array of fields that we'll need to add         
  14.     $fields_to_add=array();  
  16.     // let's fill $fileds_to_add  
  17.     foreach($fields as $field) {  
  18.          if(!in_array($field['name'], $table_field_names)) {  
  19.               $fields_to_add[] = $field;  
  20.          }   
  21.     }  
  23.     // now if there are fields to add, run the query  
  24.     if(!empty($fields_to_add)) {  
  25.          $sql = "ALTER TABLE `$table` ";  
  27.          foreach($fields_to_add as $cnt => $field) {  
  28.              if($cnt > 0) $sql .= ", ";  
  29.              $sql .= "ADD $field[name] $field[type]";  
  30.          }   
  32.          $wpdb->query($sql);  
  33.     }  
  34. }  
This is the whole function you need. It gets your array with fields, for each field check if it exists, and if not, adds it. The function can be called with multiple fields, but should be called once for each table where you are adding fields. Here is how to call the function:
  1. $fields = array(  
  2.       array("name"=>"sender""type"=>"VARCHAR(255) NOT NULL DEFAULT ''"),  
  3.       array("name"=>"require_name""type"=>"TINYINT UNSIGNED NOT NULL DEFAULT 0"),  
  4.       array("name"=>"auto_subscribe""type"=>"VARCHAR(255) NOT NULL DEFAULT ''")  
  5. );  
  6. add_db_fields($fields"mailing_lists");  
There isn't much to comment here. The table is called "mailing_lists" and we are conditionally adding three fields to it. Note that $fields is array of associative arrays, and each of them has name and type. The field "type" contains the full list of SQL arguments for the fields, not only the type. Obviously this function works only for adding new fields. This is the most common operation when releasing upgrades. Of course, you can expand it further to support CHANGE queries (but it won't change field names).