Scott Hanselman

Installing Sendy (a PHP app) on Windows Azure to send inexpensive newsletter email via Amazon SES

March 19, '13 Comments [28] Posted in Azure | Web Services
Sponsored By

TL;DR Summary

  • Sendy.co is a lovely and polished PHP app that uses Amazon's SES (Simple Email Service) to send email on the cheap.
  • It's easy to setup PHP apps on Windows Azure.
  • Azure Websites don't support mod_rewrite so you port the rules to a web.config. There's a great Sendy web.config for Windows in this post you are welcome to.
  • Sendy works well on Azure although they don't officially support Windows. I'm sure Sendy works great everywhere.
  • I'm now running my Newsletter on Windows Azure with mails sent my Amazon SES
  • Technical details below.

In search of a Cheaper Newsletter Solution

Why not a Rube Goldbergian solution? Well, it's not THAT bad. Here's the back story.

I started a little link blog newsletter a few months back, just for fun. You can subscribe at http://hanselman.com/newsletter if you like. It's a low-traffic once-or-twice-a-month little ditty, mostly to share the things I've bumped into on the internet with friends.

I started at TinyLetter.com which is brilliant for little low-traffic newsletters. However, this one has picked up steam and now it's hit the maximum number of subscribers that TinyLetter allows.  TinyLetter is a front for MailChimp, so I look at their pricing. Looks like 5k-10k subscribers is $75 a month! Eek. Let me check SendGrid. They have a $79 a month option for up to 100k emails, but that's still $960 a year for a newsletter that sells nothing and serves no useful purpose. Yet.

I suppose I could charge people or get sponsors, but, meh, that takes work. I just want to send my list out. I could use my blog. Well, I do, but I like the high connectivity that a direct letter offers so I post the letter a few weeks letter so subscribers get the early scoop. Cleary folks dig it or they wouldn't sign up.

A twitter person told me about Sendy.co. It's a PHP app that you host yourself. It fronts Amazon's Simple Email Service (SES) which is dirt cheap for email. The app is REALLY polished and just lovely. It's $59 to buy, but they said on their site "If you encounter problems, we will help you. If it doesn't work out, we'll refund you." That matters to me, so I bought it on the spot.

I know nothing about PHP, though, but I know the web, so I'm sure I can figure this out.

The Sendy site says this MASSIVE DISCLAIMER:

What are the requirements?

You need PHP & mySQL support on a Unix like server, eg. Linux on Apache. Almost all hosting companies support them. IT ISN'T SUPPORTED ON WINDOWS AND YOU'RE A FOOL TO TRY.

Ok, I admit, I added that part at the end myself. But, I don't really feel like spinning up a Linode as I have Azure credits I'm not using each month. I'm sure this will work. Plus, if it doesn't, I'll spin up a PHP app at any of a thousand little hosts for minimum money. If it works, it'll be nice to have everything in once place.

Making a Sendy PHP app instance on the Windows Azure Cloud

I go over to Azure and make a new website with a MySQL database:

Making a new website with a new MySQL DB

Next, inside of Azure I download the publish profile for my site. I also view the connection strings to the database because I'll need them to connect to the Sendy instance.

Connection Strings and publish profile

Then I download Sendy (after paying), unblock the zip and unzip it into a folder. I open the folder in WebMatrix. It installs PHP on my local machine so I can run it locally (even though I won't bother). I am using WebMatrix in this instance as a super easy way to publish to Azure directly.

The Sendy PHP app opens nicely in WebMatrix

I hit the Remote tab, then Settings to Import the publish profile I downloaded. Don't publish yet! I need to add a web.config since we are running this PHP app on Windows.

Sendy on Windows - .htaccess vs. web.config URL rewrite

I noticed there's an .htaccess file in my Sendy install. That means they've likely got mod_rewrite stuff going on to make the URLs pretty. Here's their file:

ErrorDocument 404 "[404 error] If you're seeing this error after install, check this thread on our forum for the fix: http://sendy.co/forum/discussion/5/404-error-after-install/p1"

Options +FollowSymLinks
Options -Multiviews

RewriteEngine On
RewriteCond %{SCRIPT_FILENAME} !-d
RewriteCond %{SCRIPT_FILENAME} !-f
RewriteRule ^([a-zA-Z0-9-]+)$ $1.php [L]

# Link tracker
RewriteRule ^l/([a-zA-Z0-9/]+)$ l.php?i=$1 [L]

# Open tracker
RewriteRule ^t/([a-zA-Z0-9/]+)$ t.php?i=$1 [L]

# Web version
RewriteRule ^w/([a-zA-Z0-9/]+)$ w.php?i=$1 [L]

# unsubscribe
RewriteRule ^unsubscribe/(.*)$ unsubscribe.php?i=$1 [L]

# subscribe
RewriteRule ^subscribe/(.*)$ subscribe.php?i=$1 [L]

Windows Azure Websites with PHP doesn't (yet?) support mod_rewrite so I have to make web.config that does the same thing with UrlRewrite.  Fortunately importing mod_rewrite rules into web.config files has been straightforward for over 5 years in IIS.

Since I put Sendy in the root of a site, I don't have any sub-directories or paths. If it was in /sendy or something I might have to be a little more specific in my Regular Expressions below. You have to consider where Sendy is and where your web.config is. In this case, the easiest thing was the root and putting Sendy in its own site then adding this web.config. You'll notice is a pretty straight port.

<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="Sendy all" stopProcessing="true">
<match url="^([a-zA-Z0-9-]+)$" ignoreCase="true" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false">
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
</conditions>
<action type="Rewrite" url="{R:1}.php" appendQueryString="true" />
</rule>

<rule name="Sendy: link tracker" stopProcessing="true">
<match url="^l/([a-zA-Z0-9/]+)$" ignoreCase="true" />
<action type="Rewrite" url="l.php?i={R:1}" appendQueryString="true" />
</rule>

<rule name="Sendy: open tracker" stopProcessing="true">
<match url="^t/([a-zA-Z0-9/]+)$" ignoreCase="true" />
<action type="Rewrite" url="t.php?i={R:1}" appendQueryString="true" />
</rule>

<rule name="Sendy: web version" stopProcessing="true">
<match url="^w/([a-zA-Z0-9/]+)$" ignoreCase="true" />
<action type="Rewrite" url="w.php?i={R:1}" appendQueryString="true" />
</rule>

<rule name="Sendy: unsubscribe" stopProcessing="true">
<match url="^unsubscribe/(.*)$" ignoreCase="true" />
<action type="Rewrite" url="unsubscribe.php?i={R:1}" appendQueryString="true" />
</rule>

<rule name="Sendy: subscribe" stopProcessing="true">
<match url="^subscribe/([a-zA-Z0-9/]+)$" ignoreCase="true" />
<action type="Rewrite" url="subscribe.php?i={R:1}" appendQueryString="true" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>

I mentioned to the Azure Websites team that we should either directly support mod_rewrite or automatically turn existing ones into a format like this.

NOTE: This web.config is different, better, and much improved over the one that is mentioned on the Sendy Forums. It also works nicely and completely. The one on the forums is more than little iffy.

This web.config needs to be in the same folder as the Sendy app. I just made this file from WebMatrix directly.

Windows Azure Sendy Configuration

Next, change the includes/config.php and include the details on your application's location as well as your database connection details.

<?php 
    //==================================================================================//    
    // Configuration
    //==================================================================================//
    
    //path to your Sendy installation (without the trailing slash)
    define('APP_PATH', 'http://pantspantspants.hanselman.com');
    
    //database connection credentials
    $dbHost = 'somefunkyurl.cleardb.com'; //mySQL Hostname
    $dbUser = 'ladaladalada'; //mySQL Username
    $dbPass = 'pardypary'; //mySQL Password
    $dbName = 'sendyDBName'; //mySQL Database Name
    //$dbPort = 3306; //mySQL port (only if you need to change it)
    
    //domain of cookie (99.99% chance you don't need to edit this)
    define('COOKIE_DOMAIN', '');
    
    //==================================================================================//
?>

I chose to scale my site up to Shared mode so I could add a custom CNAME for the domain. I just went over to DNSimple where I host my DNS and added a CNAME for Hanselman.com that pointed to my fancypantsmail.azurewebsites.net site. Then in Azure I hit Manage Domains and added this new subdomain.

NOTE: The domain name that you tell the Sendy guys must be the same one that's in your config.php and the same one you run under. I bought it for hanselman.com so it will only run there. Phrased differently, I couldn't get Sendy to run until the CNAME subdomain resolved correctly.

Finishing Sendy Installation

The Getting Started checklist at Sendy is REALLY well written. Follow it carefully.

I hit my URL and tell Sendy about my license key. You'll know the app ISN'T installed correctly if you can't see any CSS or images or you get 404s. That means the web.config (my fake mod_rewrite) isn't there.

I skipped Step 4 of their Getting Started as I believe it's already done for me, even though I don't plan on uploading any files. I setup Amazon SES and verified my email addresses. Getting this right, as well as bounce and complaint handling is super important so read carefully.

You'll want to make sure your Amazon SES emails are verified, and that Sendy has endpoints setup for complaints and bounces.

Gotcha: I had to make sure both the SES and SNS were in Amazon East 1.

Once these endpoints are setup, again as the Getting Started checklist at Sendy explains, you're ready to do some tests.

Setting up a Newsletter Campaign

The Sendy application is really nice, easy to use and easy to move around in.  I found it as easy as using TinyLetter, while it's clear there's more power underneath I have yet to tap into.

image

I was able to move my subscribers over with minimal trouble. I exported them from TinyLetter and imported them into Sendy. I wonder how long until the MySQL database gets big enough that I have to pay for it? Right now I'm still using the free MySQL database I created with my website.

Making a Subscribe Form

There isn't a Subscribe Form out of the box that I can find built-in to the Sendy app (can you?) so I made one at http://hanselman.com/newsletter that just posts to the Sendy API subscribe endpoint. Details here, it's just an HTTP POST! http://sendy.co/api. You can integrate it with whatever you like. I just made a simple form, myself.

Sending mails!

Hopefully this will be a reasonable and economical solution for http://hanselman.com/newsletter for the foreseeable future!

* SOME DISCLAIMERS AND DETAILS: The Sendy links are a referral link, but they don't know me over at Sendy. I just like them. Maybe I'll get some soda money if you buy it. Also, note again that installing Sendy on Windows is explicitly not supported until they say it is. Don't bother those nice people with your Windows questions. I am assuming that you are reasonably technical and are willing to fiddle. I installed it on Azure because I've already got 12 sites at Azure. You might have success on a Linux machine at Amazon or at Linode. Good luck!

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. I am a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web
Tuesday, March 19, 2013 8:34:01 AM UTC
Scott,

Great find, but why on earth doesn't Azure have something similar to SES? It's such a natural fit.

For me, I just fired up a VM on Azure and ran an email server, and everything worked fine and for free (until the Azure guys find out and ban me forever).
Ali
Tuesday, March 19, 2013 8:57:29 AM UTC
If you use Windows Azure Store, you can get the Free version of Sendgrid which allows 25K emails pr. month for $0.

Check out http://sendgrid.com/windowsazure.html
Ronny Hansen
Tuesday, March 19, 2013 9:03:15 AM UTC
Ali, Ronny is right on, and I have a SendGrid account. They also do newsletters but for $80 a month. There isn't a great app like Sendy for .NET so putting this PHP app up was super each and cheap.
Tuesday, March 19, 2013 12:47:44 PM UTC
Brilliant post, and a great tip about converting the URL rewrite rules to web.config.

As an aside, I'm sure you know that the alternative of spinning up a Linux VM could also be done in Azure, in addition to the providers you mentioned (Linode and AWS).
Tuesday, March 19, 2013 2:22:29 PM UTC
Why not just use a Google group or Yahoo group?
Tuesday, March 19, 2013 2:33:32 PM UTC
Wouldn't you know it...I finally get around to registering fancypantsmail.azurewebsite.com and find that it's taken. Damn you, Hanselman!

Jeff
CEO
Fancy Pants Mail, Inc.
"It's mail, in your fancy pants"
Jeff Key
Tuesday, March 19, 2013 4:11:56 PM UTC
Did you import all the addresses from TinyLetter or do people need to subscribe again?
Davin
Tuesday, March 19, 2013 4:19:03 PM UTC
Davin -I imported all the contacts over. No one should have to do anything in order to continue getting the newsletter.

Jeff - :)

Eric - Given the recent death of Google reader, I am less likely to want to depend upon intermediaries to do work for me. I am increasingly convinced that email is the only way to make a direct connection to a customer
Scott Hanselman
Tuesday, March 19, 2013 5:09:12 PM UTC
Seems to me that you may have identified the potential for a small business venture?
Eric Malamisura
Tuesday, March 19, 2013 6:12:44 PM UTC
Didn't know about the free Sendgrid on Azure. Thanks for the tip!
Ali
Wednesday, March 20, 2013 2:39:00 AM UTC
Thanks for a great walkthrough!
Wednesday, March 20, 2013 3:28:10 AM UTC
Nice read, thanks for sharing info on setting up Sendy. Would be interesting to know if you're newsletter subscriber count has had a big bump as a result of this blog post. I just subscribed :-)

Daniel
Thursday, March 21, 2013 1:05:49 PM UTC
good stuff , I may use it in future for a cheap service
Sam
Saturday, March 23, 2013 1:03:28 AM UTC
Hey Scott,

I use Amazon SES all the time and built some custom web-based .NET apps to send HTML newsletters to 10,000-20,000 subscriber lists.

How do you get around sending limits? I.e. Amazon SES has a max throttle of emails you can send per second. If you go over, say sending 20 per second, they will block you and halt the mailing. We use either thread.sleep to put a second between each send loop (can be problematic with page timeout issues) or use a SQL Stored Proc to kickoff a .bat file to kickoff Gammadyne mailer installed on IIS7 called from a web page.

I was just wondering if Sendy had a solution for sending limit per second restrictions.

Chris
Saturday, March 23, 2013 3:51:29 AM UTC
Ask them on twitter at @getSendy but I think they have a throttle.
Saturday, March 23, 2013 11:13:12 AM UTC
Hey Scott, if you want to use the connectionstring Azure gives you instead of hardcode your MySQL (or any) auth in PHP, use this:


foreach(explode(";", $_SERVER['MYSQLCONNSTR_DefaultConnection']) as $setting) {
$connectionSettings[explode("=", $setting)[0]] = explode("=", $setting)[1];
}


Note that 'DefaultConnection' is the name of my MySQL connection as configured in Azure (Your website -> Configure -> Connection Strings).

Then you can use all of the vars from your connectionstring without having them in code:


$dbConnString = "mysql:host=" . $connectionSettings["Data Source"] . "; dbname=" . $connectionSettings["Database"];
try {
$conn = new PDO($dbConnString, $connectionSettings["User Id"], $connectionSettings["Password"]);
$conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
}
catch(Exception $e){
die("could not connect to database");
}


Its that simple. Now all I miss is disabling remote access to MySQL from the Azure dashboard so whatever happens, my db is safe.


Tobias
Tobias Oort
Saturday, March 23, 2013 12:50:22 PM UTC
Thanks Scott! Will do...
Friday, April 12, 2013 10:28:15 PM UTC
So I can register my copy on azurewebsites.net and that's fine?..
Jim
Wednesday, April 17, 2013 7:09:53 AM UTC
Scott,
You said "I exported them from TinyLetter and imported them into Sendy."
I did try to upload and import a plain Name,Email list but failed with a sendy message "Please upload csv file"
Sendy guys told me that windows is not supported. OK, got that, but still...So the question is, did you have to do anything special on you win/sendy install to enable those uploads?
Wednesday, April 17, 2013 7:31:40 AM UTC
Vlad - Did you make the web.config changes that I made here? I literally just updated the web.config and it worked in Azure, exactly as I wrote up here. I made the CSV and manually added the name, email row at the top and imported the list.
Wednesday, April 17, 2013 7:44:48 AM UTC
Yup, took web.config. Tried adding that first row too. No luck. Import using multi line text in that form work though. Only upload reports that error. Thanks anyway.
Sunday, April 28, 2013 2:01:18 PM UTC
Thanks for that great post Scott. Got that all up and running in less than two hours.
Nigel Ainscoe
Monday, May 27, 2013 3:28:04 AM UTC
Hi Scott,

On version 1.1.6.3 of Sendy (current one at the time of writing) there is a change that will prevent the application from running correctly on Azure websites (and Windows in general). Basically there are a few places within the code where $_SERVER['SCRIPT_FILENAME'] is used to retrieve the current path and forward slash is assumed as path separator, but in Windows paths have backslashes as you would expect. When bounces and complaints are set up on Amazon, the endpoints will remain on PendingConfirmation because the calls to http://yourdomain/sendy/includes/campaigns/bounces.php and complaints.php fail at function file_get_contents_curl, hence the certs/cacert.pem can't be validated.

A quick solution is to wrap $_SERVER['SCRIPT_FILENAME'] with strtr or str_replace in the code to replace all the occurrences of backslash with forward slash. In other words, do a text replace of

$_SERVER['SCRIPT_FILENAME']


with

strtr($_SERVER['SCRIPT_FILENAME'], '\\', '/')


PHP is happy with either character used as path separator on Windows, so that should do the trick.

(HT to Tim James for quickly identifying the issue and suggesting a solution: https://github.com/timbjames/Sendy.Net)
Tuesday, May 28, 2013 7:58:19 PM UTC
@Leonardo

That is a much nicer solution. Will adopt that one within my installation :)

Also let me know if you have any feedback on Sendy.Net
Sunday, June 09, 2013 11:07:04 AM UTC
Just downloaded the latest version 1.1.6.3 and installed on a Windows 2008 server with IIS 7.

All I had to do to get it working was convert the .htaccess file to a web.config file (i.e. the web.config file mentioned up the top of this page), and then change any occurrence of:

$_SERVER['SCRIPT_FILENAME']
to
strtr($_SERVER['SCRIPT_FILENAME'], '\\', '/')

Thanks for the help.

Dan
Monday, July 15, 2013 7:58:34 AM UTC
Nice Post. Save me lot of unnecessary hassle and cost.

Thanks
Friday, October 04, 2013 1:38:40 PM UTC
When i , install sendy on iis7, it gives me following error

Use of undefined constant PHP_VER - assumed 'PHP_VER' in C:\sendy\_compatibility.php on line 3
Abdul
Tuesday, October 15, 2013 11:12:01 PM UTC
I've followed your instructions and Leonardo Cortez tip and i successfully installed sendy on a windows machine but I'm struggling to configure a cron job. I've try to implement it as a schedule task but with no success.

Anyone was able to configure it so autoresponders start working?
Sergio
Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.