Creating sane plain text sales transactional e-mails in Magento
So, as I’ve described previously, we’ve set up a Magento store to clear out some of the older inventory at the company I work for: consider Men’s Underwear Discounters to be the bargain bin, Sunday! Sunday! Sunday! closeout section for Skiviez. Using a free, open source platform like Magento has saved us a lot of time and money, especially for a store that we’re not really making huge amounts of money off of anyway (it’s there to recoup the costs of products that didn’t sell well, sort of like damage control).
Aside: You probably have configured your sales e-mails incorrectly
I have seen three live Magento installations. I have also seen three Magento installations where sales e-mails have been customized improperly. Since it has happened three times, by the engineer’s first law of truth, it must be true that many people have been configuring the sales e-mails in their Magento stores improperly.
By default, all of the sales e-mails are written in less-than-stellar English and contain references to things like “Magento Demo Store” and “(555) 555-5555.” And these sales e-mail templates live by default in the /app/design/frontend/default/default/template/email directory.
If you’re like us, you made the mistake of editing these files directly, replacing the references to “Magento Demo Store” with references to your own store, like “Men’s Underwear Discounters.” And this would work fine until you, like us, apply the next security update or upgrade via Magento Connect. If they’ve updated the default template, then you can be slightly bemused that, like us, all of your sales e-mails have reverted to the defaults, and you are now thanking customers for shopping at the “Magento Demo Store.” The result? You, like us, look like an idiot.
What we need is a way to make sure that our e-mail customizations persist across security updates and upgrades.
The way to do that is to set up new copies of each and every e-mail. From the Magento Admin screen, you need to
- Choose System > Transactional E-mails from the menu.
- Click “Add New Template.”
- Under “Load default template”, select the template you would like to customize, then hit “Load Template”. The “Template Subject” and “Template Content” fields are populated with a copy of the template you selected.
- Edit that template to your heart’s content, give it a name, and hit “Save Template”.
- Under System > Configuration > Sales E-mails, be sure to select your new template instead of “Default from locale”.
Hooray! Now the changes that we make will stick around.
Formatting those e-mails as plain text
For Skiviez proper, we send multipart e-mails, where both HTML- and text-formatted messages are sent within the same e-mail. This works great because, depending on the customer’s preference and e-mail capabilities, they can view the e-mail in the format that they prefer.
Unfortunately, Magento doesn’t (at the time of this writing) have the capability for these multipart e-mails. Instead, you get to choose between HTML (the default) and plain text. I decided to go with plain text because
- I had seen issues on the forums where the HTML e-mails have problems displaying in some Web-based e-mail clients; and
- Nothing pisses me off more than HTML e-mail. I don’t know why. I just hate it.
Converting most of the e-mails to plain text is straight-forward; you just hit the “Convert to Plain Text” button and then reformat the template content so that it is not hideous. But when you get to the order e-mail, you’ll discover that some of the variables and references are returning the content in HTML; particularly, the address, payment, and item summary fields.
You could do something like changing the reference from
{{ var order.getBillingAddress().format('html') }}
to
{{ var order.getBillingAddress().format('text') }}
and that would work, but the output would not be what you expect:
Nicholas Piasecki 3005 Some Street Sillytown, California, 92683 United States T: 5555555555
That is, if an element in the address is empty, it still receives a blank space, so you end up with the odd address formatting that you see in the above example.
You could then spend an hour and a half just trying to figure out what that magic ->format('html') or ->format('text') call actually does and where it’s coming from. That is an adventure in and of itself, but to make a long story short, there is a config.xml file in the /app/code/core/Mage/Customer/etc/ directory that contains entries like the following:
<text translate="title" module="customer"> <title>Text</title> <defaultFormat><![CDATA[ {{depend prefix}}{{var prefix}} {{/depend}}{{var firstname}} {{depend middlename}}{{var middlename}} {{/depend}}{{var lastname}}{{depend suffix}} {{var suffix}}{{/depend}} {{depend company}}{{var company}}{{/depend}} {{var street1}} {{depend street2}}{{var street2}}{{/depend}} {{depend street3}}{{var street3}}{{/depend}} {{depend street4}}{{var street4}}{{/depend}} {{depend city}}{{var city}}, {{/depend}}{{depend region}}{{var region}}, {{/depend}}{{var postcode}} {{var country}} T: {{var telephone}} {{depend fax}}F: {{var fax}}{{/depend}} ]]></defaultFormat> </text> <oneline translate="title" module="customer"> <title>Text One Line</title> <htmlEscape>true</htmlEscape> <defaultFormat> <![CDATA[{{depend prefix}}{{var prefix}} {{/depend}}{{var firstname}} {{depend middlename}}{{var middlename}} {{/depend}}{{var lastname}}{{depend suffix}} {{var suffix}}{{/depend}}, {{var street}}, {{var city}}, {{var region}} {{var postcode}}, {{var country}}]]> </defaultFormat> </oneline> <html translate="title" module="customer"> <title>HTML</title> <htmlEscape>true</htmlEscape> <defaultFormat><![CDATA[ {{depend prefix}}{{var prefix}} {{/depend}}{{var firstname}} {{depend middlename}}{{var middlename}} {{/depend}}{{var lastname}}{{depend suffix}} {{var suffix}}{{/depend}}<br/> {{depend company}}{{var company}}<br />{{/depend}} {{var street1}}<br /> {{depend street2}}{{var street2}}<br />{{/depend}} {{depend street3}}{{var street3}}<br />{{/depend}} {{depend street4}}{{var street4}}<br />{{/depend}} {{depend city}}{{var city}}, {{/depend}}{{depend region}}{{var region}}, {{/depend}}{{var postcode}}<br/> {{var country}}<br/> {{depend telephone}}T: {{var telephone}}{{/depend}} {{depend fax}}<br/>F: {{var fax}}{{/depend}} ]]></defaultFormat> </html>
Once you stumble across that, it’s not so difficult to figure out what is going on. The Magento address object returns a Varien_Filter_Template object that takes one of the above defaultFormat entries as its template based on the parameter that you pass into the format() function. So you can see that valid parameters are ‘html’, ‘text’, ‘oneline’, and so on.
The {{depend}} syntax means that if the enclosed variable is empty, then that output won’t appear. For HTML, this works fine, since newlines aren’t signficant. But for the text template, this doesn’t work, because even though things like {{street2}} get omitted, the newline is still sitting there in the template. This is why we get the fugly output in our plain text e-mails.
My solution is to define my own block that I just reference in my e-mails. Here’s the e-mail template for Men’s Underwear Discounters, for example:
Dear {{var order.getCustomerName()}},
Thank you for your order from Men's Underwear Discounters.
Once your package ships, we will send an email with a link
to track your order. You can check the status of your order
by logging into your account.
If you have any questions about your order please contact
us at support@mensunderweardiscounters.com.
Your order confirmation is below. Thank you again for your
business.
ORDER {{var order.increment_id}}
Placed on {{var order.getCreatedAtFormated('long')}}
BILLING INFORMATION
{{block type='core/template' area='frontend' template='email/order/addresstext.phtml' address=$order.getBillingAddress()}}
SHIPPING INFORMATION
{{block type='core/template' area='frontend' template='email/order/addresstext.phtml' address=$order.getShippingAddress()}}
via {{var order.getShippingDescription()}}
ORDER DETAILS
{{block type='core/template' area='frontend' template='email/order/itemstext.phtml' order=$order}}
Thanks for shopping at Men's Underwear Discounters.
Sincerely,
The Men's Underwear Discounters Team
support@mensunderweardiscounters.comIf you compare this with the default e-mails, you can see that I’ve added these new {{block}} entries in several places. All they are saying is “hey, create a new static block with the file at /app/design/frontend/default/default/template/email/order/itemstext.phtml (or addresstext.phtml) and pass a variable into it. Whatever variable I pass in here will be accessible in the .phtml file by referencing $this->getWhatever(), where “Whatever” is the name of the parameter. For example, my itemstext.phtml file could reference $this->getOrder() and my addresstext.phtml file could reference $this->getAddress().
These “itemstext.phtml” and “addresstext.phtml” files don’t exist in the default installation. I had to create them and FTP them to that appropriate directory. The contents aren’t exotic; they’re just PHP files that emit output that’s suitable for dropping into a plain text e-mail.
Here’s the itemstext.phtml file:
<?php $order = $this->getOrder(); if ($order) { foreach ($order->getAllItems() as $item) { if ($item->getParentItem()) { continue; } echo '('; echo $item->getQtyOrdered() * 1; echo ')'; echo ' '; echo $item->getName(); echo ' @ '; echo number_format($item->getPrice(), 2); echo ' ea = '; echo number_format($item->getRowTotal(), 2); echo "\n"; } echo "\n"; echo 'Subtotal: $'; echo number_format($order->getSubtotal(), 2); echo "\n"; echo 'Discount: $'; echo number_format(0.00 - $order->getDiscountAmount(), 2); echo "\n"; echo 'Shipping & Handling: $'; echo number_format($order->getShippingAmount(), 2); echo "\n"; echo 'Tax: $'; echo number_format($order->getTaxAmount(), 2); echo "\n"; echo 'Grand Total: $'; echo number_format($order->getGrandTotal(), 2); echo "\n"; } ?>
And here’s the addresstext.html file:
<?php $address = $this->getAddress(); if ($address) { if (strlen($address->getPrefix()) > 0) { echo $address->getPrefix() . ' '; } echo $address->getFirstname(); if (strlen($address->getMiddlename()) > 0) { echo ' ' . $address->getMiddlename(); } echo ' '. $address->getLastname(); if (strlen($address->getSuffix()) > 0) { echo ' ' . $address->getSuffix(); } echo "\n"; if (strlen($address->getCompany()) > 0) { echo $address->getCompany() . "\n"; } echo $address->getStreet1() . "\n"; if (strlen($address->getStreet2()) > 0) { echo $address->getStreet2() . "\n"; } if (strlen($address->getStreet3()) > 0) { echo $address->getStreet3() . "\n"; } if (strlen($address->getStreet4()) > 0) { echo $address->getStreet4() . "\n"; } if (strlen($address->getCity()) > 0) { echo $address->getCity() . ', '; } if (strlen($address->getRegion()) > 0) { echo $address->getRegion() . ' '; } echo $address->getPostcode() . "\n"; echo $address->getCountry(); if (strlen($address->getTelephone()) > 0) { echo "\n" . 'T: ' . $address->getTelephone(); } } ?>
Conclusions and delusions
This story has been typical of my experiences with Magento. I eventually end up finding something that was thoughtfully designed (if slightly overarchitected), but poor documentation and difficulty testing impede progress more than necessary. Let’s hope that this gets better in future versions of the framework.
Good luck.






Great post! very helpful.
I am trying to change the customers’ name in the templates so it only shows the first name. This way it’s more personal and not so serious.
The problem I am having is with the variable {{var order.getCustomerName()}}, which I replaced with {{var customer.firstname}} but it doesn’t show anything…
Any idea how to do this?
Thank you very much for your help!
Simon
@Simon
It would probably be {{var order.getCustomerName().firstName}} or something like that. To really know you need to fire up your FTP client and go spelunking through the Magento codebase, as these types of things don’t seem to be documented anywhere. That’s how I put together this post–copy and paste, trial and error–so the best that I can do is wish you luck! =)
Hey Nicholas,
Thanks a lot for your post. I’ve been searching for a way to pass a variable to a dynamic block and now I found your solution with simply putting the variable name in it. This has annoyed me for at least a week now.
I’ve created my own module which creates salutations for newsletter subscribers if they are registered. So now I simply put in
{{block type=’arlookeeffe_newsletter/salutation’ value=$subscriber.getEmail()}}
instead of the standard salutation and in the block I search for a customer and output the salutation.
If anyone tries this themselves watch out when using a template. Magento removes your line with the block. But if you put it back in when you’re queuing everything works fine.
Thanks and Greetings from Germany,
Arlo O’Keeffe
I love your article! A very simple and effective solution to my simple text email problem. HOWEVER…
My Magento has a custom checkout module installed (custom fields in the review step of checkout), and I have spent many hours already trying to figure out how to get the data generated by these fields to print up in the new order email along with the shipping info and order details. If I know the name of the database table where the custom info is being stored (custom_attribute_description) what sort of code should I use to echo the data onto the new order email? Your help is greatly appreciated!
Thanks a lot for this, I posted a link and some of the code in a magento discussion here:
http://www.magentocommerce.com/boards/viewthread/25075/P15/
Hope you don’t mind.
Thanks, you’ve really helped me a lot!
The only thing I need now is to get the payment method in the mail, because it contains the banking details for payment-in-advance-orders.
But {{var payment_html}} outputs everything with tags and {{var paymentMethod}} is empty…