cf_sesmail custom tag for Railo


Lately I have been using Amazon’s Simple Email Service quite a bit. I use the AmazonSES component for a few clients, but it still is not quite as natural as using a cfmail tag.  Running PostFix to relay messages through SES is not hard to setup, but it is a little overkill if you just need to use it from ColdFusion. With these things in mind, I remembered the post Todd Rafferty wrote about writing a cfc custom tag. I thought it would be pretty cool to write a custom tag to allow you to send an email through SES with the familiar feel of cfmail. If you would like to check it out, it is pretty easy to setup.

sesmail.cfc 
component {
/*
* Project: sesmail
* Author : Robert Zehnder
* Date   : 9/2/2011
* Purpose: cfmail-like implementation to make it easier to send emails through Amazon Simple Email Service
*/
 this.metaData.attributeType = "fixed";
 this.metaData.attributes = {
  from        : { required: true, type: "string" },
  to          : { required: true, type: "string" },
  cc          : { required: false, type: "string", default: "" },
  bcc         : { required: false, type: "string", default: "" },
  subject     : { required: true, type: "string" },
  mailerID    : { required: false, type: "string", default: "cfmailses" },
  endPoint    : { required: false, type: "string", default: "" },
  credentials : { required: true, type: "string" },
  name        : { required: false, type: "string", default: "sesResults" }
 };

 public void function init(required boolean hasEndTag, any parent) {

 }

 public boolean function onStartTag(struct attributes, struct caller) {
  return true;
 }

 public boolean function onEndTag(struct attributes, struct caller) {
  var results = {};
  var awsCredentials = createObject("java", "java.io.File").init(attributes.credentials);
  var creds = createObject("java", "com.amazonaws.auth.PropertiesCredentials").init(awsCredentials);
  var emailService = createObject("java", "com.amazonaws.services.simpleemail.AmazonSimpleEmailServiceClient").init(creds);
  var props = createObject("java", "java.util.Properties");
  var verifyRequest = createObject("java", "com.amazonaws.services.simpleemail.model.VerifyEmailAddressRequest").withEmailAddress(attributes.from);
  var sendHeaders = { "User-Agent" : attributes.mailerID };

  // Set properties for establishing connection
  props.setProperty("mail.transport.protocol", "aws");
  props.setProperty("mail.aws.user", creds.getAWSAccessKeyId());
  props.setProperty("mail.aws.password", creds.getAWSSecretKey());

  // Send email message
  var mailSession = createObject("java", "javax.mail.Session").getInstance(props);
  var mailTransport = createObject("java", "com.amazonaws.services.simpleemail.AWSJavaMailTransport").init(mailSession, JavaCast("null", 0));
  var messageObj = createObject("java", "javax.mail.internet.MimeMessage").init(mailSession);
  var messageRecipientType = createObject("java", "javax.mail.Message$RecipientType");
  var messageFrom = createObject("java", "javax.mail.internet.InternetAddress").init(attributes.from);
  var messageTo = listToArray(attributes.to);
  var messageCC = listToArray(attributes.cc);
  var messageBCC = listToArray(attributes.bcc);
  var messageSubject = attributes.subject;
  var messageBody = arguments.generatedContent;
  var verified = arrayToList(emailService.ListVerifiedEmailAddresses().getVerifiedEmailAddresses()).contains(attributes.from);
  var i = 0;

  try {

   // Is the sender verified
   if(!verified){
    var verifyRequest = createObject("java", "com.amazonaws.services.simpleemail.model.VerifyEmailAddressRequest").withEmailAddress(attributes.from);
    try{
     emailService.verifyEmailAddress(verifyRequest);
    }
    catch (any e){
    }
    throw("Email address has not been validated.  Please check the email on account " & attributes.from & " to complete validation.");
   }

   mailTransport.connect();

   messageObj.setFrom(messageFrom);
   for(i = 1; i <= arrayLen(messageTo); i++){
    messageObj.addRecipient(messageRecipientType.TO, createObject("java", "javax.mail.internet.InternetAddress").init(trim(messageTo[i])));
   }

   if(arrayLen(messageCC)){
    for(i = 1; i <= arrayLen(messageCC); i++){
     messageObj.addRecipient(messageRecipientType.CC, createObject("java", "javax.mail.internet.InternetAddress").init(trim(messageCC[i])));
    }
   }

   if(arrayLen(messageBCC)){
    for(i = 1; i <= arrayLen(messageBCC); i++){
     messageObj.addRecipient(messageRecipientType.BCC, createObject("java", "javax.mail.internet.InternetAddress").init(trim(messageBCC[i])));
    }
   }

   if(len(structKeyList(sendHeaders))){
    for(i in sendHeaders){
     messageObj.addHeader(i, sendHeaders[i]);
    }
   }

   messageObj.setSubject(messageSubject);
   messageObj.setContent(messageBody, "text/html");
   messageObj.saveChanges();

   mailTransport.sendMessage(messageObj, JavaCast("null", 0));

   mailTransport.close();

  }
  catch (Any e){
   throw("Error sending message.");
  }
  return false;
 }

}

Now we can send mail through the SES gateway like this:

<cf_sesmail from="user@domain" to="other@domain" subject="subject" credentials="/path/to/awscredentials.properties">
Hello from cf_sesmail!
</cf_sesmail>

It is self-explanatory.  You supply the basics such as the from address, to address and subject. Instead of passing a user name and password for authentication, you pass the full path to your awscredentials.properties file and the tag takes care of the rest. The code is still a little rough around the edges but it is usable. I will get something up on github soon.

Here is a link to the version of the AWS SDK I am using for development. It is a little dated, but it works perfectly with Railo without requiring you to update any additional jars in your installation. Just extact this file and place the sdk jar into your Railo classpath. For Linux systems, this will most likely be /opt/railo/lib and you should be good to go. If you want to really integrate it into your web context you can copy the component into your WEB-INF/railo/library/tags/ folder, this will allow you to use the custom tag just as if it was a built in function (i.e., <cfsesmail ..></cfsesmail>).

Updated 9/4/2011:
Project is now hosted on github here: https://github.com/robertz/cf_sesmail

Advertisements

About Robert Zehnder

Web application developer specializing in ColdFusion/Railo and Open Source development.

Posted on September 2, 2011, in AmazonWS, Railo and tagged , . Bookmark the permalink. Leave a comment.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: