My most recent conversion was for our online football management game, MyFootballNow.com. In this system, there are two sources that send e-mails to users. Our PHP website sends various notifications and confirmations as users interact with the site. But the main engine that drives the simulation was built in C++, and it needs to send e-mail notifications to the appropriate players when it completes. The first generation of our game engine sent e-mails through the server's mail command, but that was complicated to build the HTML e-mail, and didn't offer much flexibility. I needed a way to send e-mails from my C++ application though Mandrill, but wasn't able to find any information about how that could be done. There are SDK's for several languages, but C++ was not among them. I decided to buckle down and figure out how to send an e-mail through Mandrill directly from my application. As it turns out, it was remarkably easy to do, and this entry is a short tutorial on how to do it.
First, I'll briefly talk about Mandrill. While not limited to sending from a template, that is where it really shines. You build your template once and can use it again and again. You can even build the template in MailChimp and transfer it over to Mandrill. You can make it a simple e-mail that has no dynamic content, or you can use merge variables to fill in parts of the message. You can even do complex logic such as if-then statements. I encourage you to check out MailChimp's support page for Mandrill at http://kb.mailchimp.com/article/mandrill-for-mailchimp-users to learn more; I'm not going to go too much more into how to create a template, but here's what my template looks like:
<p>Hello, *|NAME|*,</p>*|NAME|* in this e-mail is our merge variable, we will be inserting the recipient's name into the salutation.
<p>This is a friendly e-mail to let you know that we can send an e-mail through Mandrill using a C++ application.</p>
Mandrill receives its command via an HTTP Post request, where the post data payload is a JSON-encoded string of information. The call we're going to be using is called "send-template." You can see all of the options for the JSON string you pass here. This tutorial will barely scratch the surface of what you can send, but you should be able to add anything else you need to your heart's content. You should also be able to apply these same techniques to the other API calls available.
So, the first thing we need to figure out how to do is to create a JSON string in C++. This could be easy enough if you want to build it from scratch, but that is error prone especially with nested JSON data. No, we need something that will do it for us. And I have just the tool for the job: the Boost library.
When I first started writing MyFootballNow.com, I fell in love with the Boost library. And I probably use less than 1% of what it offers. When I need to figure out how to do something new, the first place I check is Boost. For this application, we are going to use two of Boost's modules: the Property Tree and the JSON Parser.
If you need to install Boost, you can do it from the link above. Or, you probably can install it via your package manager. In CentOS and Amazon's AMI, it's just:
yum install boost-develWhile we're installing libraries we'll need, we need to make sure we have the CURL library so we can execute the HTTP Post request:
yum install curl-develNow we should have what we need. (This is of course assuming we already have the C++ compiler installed - before jumping into this tutorial make sure you can build and execute a simple "Hello World" application in C++)
One gotcha for using CURL is that you need to link the CURL libraries when you compile. Depending on your setup, you'll want to add -lcurl to your compile command. I normally use a Makefile which is generated by my IDE, but the compile command should look something like this:
g++ -o mandrill_test main.o -lcurl
Now for the heavy lifting. The first thing we need to do is get the include files lined up. You need to include the boost property tree and json parser files, as well as the curl library:
#include <boost/property_tree/ptree.hpp> #include <boost/property_tree/json_parser.hpp> #include <curl/curl.h>Another housekeeping note, I'm using namespace std:
using namespace std;Now to the body of our function. For this tutorial, we are creating a simple application that sends a message to a pre-determined e-mail address every time it runs. In a real application, of course, you would be collecting the information from elsewhere and probably calling a function that sends a message with the requested information.
The main variables we'll be using are the API Key, the Template Name, the e-mail address, and the recipient's name:
char mandrill_key[] = "[Your API Key Here]"; char template_name[] = "[The Template Name]"; char email[] = "test@example.com"; char name[] = "Joe Smith";We also need some variables for our CURL:
CURL *curl; CURLcode res; struct curl_slist *headerlist = NULL;and our property tree:
boost::property_tree::ptree req; boost::property_tree::ptree template_content, template_content_value, to_data, to; boost::property_tree::ptree message; boost::property_tree::ptree global_merge_vars; boost::property_tree::ptree merge_var; stringstream ss; char payload[5000];This one doesn't look quite as straight forward; I've got a ton of property tree variables, why is that? Boost's property tree is built using key/value pairs. As you may know, a JSON string can have multiple nested arrays and objects. So we need to build the nested properties from the inside out, making the deepest element a child of the next-deepest, and so on. To keep things clear, I'm using a different ptree item for all pieces of information.
"ss" is of type std::stringstream; this is where we will be storing our JSON string. The Boost JSON parser dumps the encoded JSON to a file, not a variable. std::stringstream gives us a stream that acts like a file but we can then use as a variable.
The payload is where we're storing the final JSON string. I'm going to guess that I don't need this intermediary variable, but was unable to get it working otherwise ... so you're welcome to try to eliminate this one.
Now it's time to build the property list:
// First item in our property list will be the mandrill key req.put("key", mandrill_key); // The template we will be using req.put("template_name", template_name); // template_content must exist but can be empty template_content_value.put("name", "Template"); template_content_value.put("content", "Template Content"); template_content.push_back(make_pair("", template_content_value)); req.add_child("template_content", template_content); // To data to_data.put("email", email); to_data.put("name", name); to_data.put("type", "to"); to.push_back(make_pair("", to_data)); // Repeat above for as many recipients as you want for this e-mail message.add_child("to", to); // Merge variables message.put("merge", "true"); merge_var.put("name", "name"); merge_var.put("content", name); global_merge_vars.push_back(make_pair("", merge_var)); // Repeat the above for all merge variables message.add_child("global_merge_vars", global_merge_vars); req.add_child("message", message);Note that the top-level parent tree is "req" and we are ultimately appending everything to that tree. The command to attach an item to a tree is "put" - so the first line, 'req.put("key", mandrill_key);' is putting our API key into the tree with the key "key" ... (maybe that wasn't the clearest example)
Things change up a little bit when we get to the template_content item. Mandrill appears to require this even though we're not using it, so we're going to include it to quiet the error message. But I have something odd in the way that template_content_value is being applied to template_content: 'template_content.push_back(make_pair("", template_content_value));' Why not just do 'template_content.put("", template_content_value);'? The answer is that the put() command requires a key - and in this instance, we don't want a key, we want an array of objects without keys. The way to make this happen is to use std::make_pair() with an empty string, and push that back onto the template_content tree. We do this same thing as we add the "To" data and merge variables.
Next up, we need to create the JSON string. This is a simple call to write_json(), which as I mentioned above will write the JSON out to a file stream, in our case we're usinng the stringstream ss; then immediately we'll convert this into a null-terminated c string to use in our request:
// Generate the json from our property tree write_json(ss, req); strcpy(payload, ss.str().c_str());Now all we have left to do is to send the HTTP Post request to the Mandrill API endpoint. Here's the code for that:
// Send the message curl = curl_easy_init(); headerlist = curl_slist_append(headerlist, "Content-Type: application/json"); curl_easy_setopt(curl, CURLOPT_URL, "https://mandrillapp.com/api/1.0/messages/send-template.json"); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload); res = curl_easy_perform(curl); // Check for errors if (res != CURLE_OK) { // Handle error. Error string can be retrieved via curl_easy_strerror(res) } // Cleanup curl_easy_cleanup(curl); curl_slist_free_all(headerlist);The first thing we do is initialize the CURL instance. Next, we apply a header to identify the content type as application/json. Then we tell the CURL instance the URL it will connect to, we attach the header, and attach the payload. After that we're ready to send, check for an error, and then clean up curl and headerlist.
When this is compiled and run, the response from Mandrill will be sent to stdout - you should see a verification of the e-mail being sent, or an error message as to why it didn't work.
I hope this short tutorial helps you if you are needing to send e-mails through Mandrill via C++! I will leave you with the full listing of my main.cpp that I used in this tutorial.
#include <cstdlib> #include <boost/property_tree/ptree.hpp> #include <boost/property_tree/json_parser.hpp> #include <curl/curl.h> using namespace std; /* * */ int main(int argc, char** argv) { // Basic stuff char mandrill_key[] = "[Your Mandrill API Key]"; char template_name[] = "[Template Name]"; char email[] = "test@example.com"; char name[] = "Joe Smith"; // CURL Stuff CURL *curl; CURLcode res; struct curl_slist *headerlist = NULL; // Boost stuff boost::property_tree::ptree req; boost::property_tree::ptree template_content, template_content_value, to_data, to; boost::property_tree::ptree message; boost::property_tree::ptree global_merge_vars; boost::property_tree::ptree merge_var; stringstream ss; char payload[5000]; // First item in our property list will be the mandrill key req.put("key", mandrill_key); // The template we will be using req.put("template_name", template_name); // template_content must exist but can be empty template_content_value.put("name", "Template"); template_content_value.put("content", "Template Content"); template_content.push_back(make_pair("", template_content_value)); req.add_child("template_content", template_content); // To data to_data.put("email", email); to_data.put("name", name); to_data.put("type", "to"); to.push_back(make_pair("", to_data)); // Repeat above for as many recipients as you want for this e-mail message.add_child("to", to); // Merge variables message.put("merge", "true"); merge_var.put("name", "name"); merge_var.put("content", name); global_merge_vars.push_back(make_pair("", merge_var)); // Repeat the above for all merge variables message.add_child("global_merge_vars", global_merge_vars); req.add_child("message", message); // Generate the json from our property tree write_json(ss, req); strcpy(payload, ss.str().c_str()); // Send the message curl = curl_easy_init(); headerlist = curl_slist_append(headerlist, "Content-Type: application/json"); curl_easy_setopt(curl, CURLOPT_URL, "https://mandrillapp.com/api/1.0/messages/send-template.json"); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload); res = curl_easy_perform(curl); // Check for errors if (res != CURLE_OK) { // Handle error. Error string can be retrieved via curl_easy_strerror(res) } // Cleanup curl_easy_cleanup(curl); curl_slist_free_all(headerlist); return 0; }
No comments:
Post a Comment