Thursday, 29 January 2015

Mail Gun Service for AIR

I thought I'd start this blog to share a bit more about what I do. So here is the first bit, something I did today; a rest client for the awesome MailGun service written in AS3 for AIR.

Basically you can use this class to send emails using the MailGun service. You can create a new MailGun service with your api key and domain, and then use sendEmail to fire off an email to the specified recipients like so;


 // Get these from your Mail Gun Account, go to the links to find them  
 const API_KEY:String = "YOUR KEY HERE"; // https://mailgun.com/cp  
 const DOMAIN:String = "YOUR DOMAIN HERE"; // https://mailgun.com/app/domains  
 const SMTP_LOGIN:String = "YOUR SMTP LOGIN HERE"; //https://mailgun.com/app/domains/<domain>  
 // Initialize the Mail Gun service  
 var client:MailGunService = new MailGunService(API_KEY, DOMAIN);  
 // Create the email to send  
 var email:Email = new Email();  
 email.from = "Me <"+SMTP_LOGIN+">";  
 email.to.push("Joe Bloggs <joe.bloggs@example.com>");  
 email.subject = "My Cool Email";  
 email.message = "Congratulations! Your email is here!";  
 // Send the email, it will callback when completed  
 client.sendEmail(email,function onComplete(response:Object):void {  
    // This code will run if the email is sent okay  
 }, function onError(error:String, httpStatus:int):void {  
    // This code will run if the device is offline, or something else goes wrong  
 });  

You'll need three classes in all, a base64 utility class, an email helper class and the actual service class. The email class looks like this;

 package  
 {  
    import flash.net.URLVariables;  
   
    public class Email  
    {  
       public var from:String;  
       public var to:Array = [];  
       public var cc:Array = [];  
       public var bcc:Array = [];  
       public var subject:String;  
       public var message:String;  
       public var isHTML:Boolean = false;  
         
       public function Email(from:String=null, to:Array=null, subject:String=null, message:String=null) {  
          this.from = from;  
          if (to) this.to = to;  
          this.subject = subject;  
          this.message = message;  
       }  
         
       public function toURLVariables():URLVariables {  
          var vars:URLVariables = new URLVariables();  
          vars.from = this.from;  
          if (this.to) vars.to = to.join(",");  
          if (this.cc && this.cc.length > 0) vars.cc = cc.join(",");  
          if (this.bcc && this.bcc.length > 0) vars.bcc = bcc.join(",");  
          vars.subject = this.subject;  
          if (this.isHTML) vars.html = this.message;  
          else vars.text = this.message;  
          return vars;  
       }  
    }  
 }  

And the service class like this;

 package  
 {  
    import flash.events.ErrorEvent;  
    import flash.events.Event;  
    import flash.events.HTTPStatusEvent;  
    import flash.events.IOErrorEvent;  
    import flash.events.SecurityErrorEvent;  
    import flash.net.URLLoader;  
    import flash.net.URLRequest;  
    import flash.net.URLRequestHeader;  
    import flash.net.URLRequestMethod;  
      
    import util.Base64;  
   
    public class MailGunService  
    {  
       /** The base api path **/  
       private static const BASE_PATH:String = "api.mailgun.net/v2";  
       /** The protocol to communicate with the api over **/  
       private static const PROTOCOL:String = "https";  
         
       /** The domain to send the email through, see https://mailgun.com/app/domains **/  
       public function get domain():String { return this.domain_; }     
       /** Your MailGun API Key **/  
       public function get apiKey():String { return this.apiKey_; }  
         
       private var apiKey_:String;  
       private var domain_:String;  
         
       /**  
        * @constructor  
        * @param apiKey Your Mail Gun API Key  
        * @param domain The domain for this mailgun service  
        */  
       public function MailGunService(apiKey:String, domain:String):void {  
          this.apiKey_ = apiKey;  
          this.domain_ = domain;  
       }  
          
       /**  
        * Sends the given email  
        * @param email The email to send  
        * @param onComplete The function to call when the request completes, expected as f(response:Object):void  
        * @param onError The function to call when the request fails, expected as f(error:String,httpStatus:int):void  
        */  
       public function sendEmail(email:Email, onComplete:Function, onError:Function):void {  
          makeRequest("/"+this.domain+"/messages",URLRequestMethod.POST,email.toURLVariables(),onComplete,onError);  
       }  
         
       /**  
        * Performs an API request  
        * @param endpoint The url to make the request to, include the leading forward slash eg. "/messages"  
        * @param method The request method to use.  
        * @param params The parameters to include in the request  
        * @param complete The function to call on completion, expected in the form f(response:Object):void  
        * @param error The function to call on error, expected in the form f(error:String, httpStatus:int):void  
        */  
       public function makeRequest(endpoint:String, method:String='GET', params:Object=null, complete:Function=null, error:Function=null):void {  
          // Build the request URL  
          var url:String = PROTOCOL+"://"+BASE_PATH+endpoint;  
          // Create a new request and apply the given parameters  
          var req:URLRequest = new URLRequest(url);  
          req.method = method;  
          req.data = params;  
          // Create the request authorization  
          var credentials:String = Base64.encode("api:"+this.apiKey);  
          var credsHeader:URLRequestHeader = new URLRequestHeader("Authorization", "Basic " + credentials);  
          req.requestHeaders.push(credsHeader);  
          // Create a new request loader and add all the required event listeners  
          var loader:URLLoader = new URLLoader();  
          loader.addEventListener(Event.COMPLETE, onComplete);  
          loader.addEventListener(IOErrorEvent.IO_ERROR, onError);  
          loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError);  
          loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, onHttpStatus);  
          loader.load(req);  
          // Keep track of the HTTP status of the response  
          var httpStatus:int = -1;  
            
          // When a new http status is obtained when performing the request  
          function onHttpStatus(event:HTTPStatusEvent):void {  
             httpStatus = event.status;  
          }  
            
          // When the request is completed  
          function onComplete(event:Event):void {  
             closeLoader();  
             // If the request was successful callback with the result  
             if (httpStatus == 200) {  
                var obj:Object;  
                try { obj = JSON.parse(loader.data); }  
                catch (e:Error) { if (error) error("Failed to parse response: "+loader.data, httpStatus); }  
                  
                if (obj && complete) complete(obj);  
             }  
             // Otherwise callback with the failure  
             else if (error) {  
                error(loader.data, httpStatus);  
             }  
          }  
            
          // If an IO or security error is encountered  
          function onError(event:ErrorEvent):void {  
             closeLoader();  
             if (error) error(event.text, httpStatus);  
          }  
            
          // Cleanup the loader once the request is finished  
          function closeLoader():void {  
             loader.removeEventListener(Event.COMPLETE, onComplete);  
             loader.removeEventListener(IOErrorEvent.IO_ERROR, onError);  
             loader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onError);  
             loader.removeEventListener(HTTPStatusEvent.HTTP_STATUS, onHttpStatus);  
             loader.close();  
          }  
       }  
    }  
 }  

I'm posting this because getting the auth credentials to work and ensuring that the service failed properly was a bit of a mystery, hopefully this helps someone :)

R.

1 comment: