Posting eMails w/ Base64 Attachments from SmartApps


(JJG) #1

I have spent an inordinate amount of time setting this up, so I post that in the hope nobody else will have to go through all this pain again.

To send emails from your SmartApp, you have to :

  1. create an account with mandrill.com (free below 12 000 emails/month) and get a “key”
  2. copy the Groovy code below into your SmartApp
  3. do all the necessary code adaptations (key, to_list, body_of_message, title, etc…)
  4. DEBUG IT ! (a pain…)

The real painful part is the debugging, due to the fact that whenever a problem occurs (with mandrill.com or with the ST Groovy framework), you will get a very cryptic and totally useless message from httpPostJson() in the log.
To finally make progress in my debugging, I had to resort to URLencoded parameters, directly passed into my browser, which at least gave me the mandrill.com error messages.
If you do that, beware of the syntax for URLencoded sublists (see web reference in the code comments).

Note that attached files need to be Base64 encoded, so I also had to import a Base64 encoder, somewhat adapted for the ST framework.
And be very careful with the pre-JSON inputs, the Groovy JsonBuilder() has bizarre syntax rules, and furthermore the example given into its web page does not work !

Note also that mandrill.com is supposed also to support “image/pnt” attachments, but for whatever reason, it never worked when I tried to test it.
Since I do not need it, I stopped there, but if somebody finds the issue, please report it there.

Have fun !

/**************************************************************************************
 * Mandrill API Documentation :
 *        https://mandrillapp.com/api/docs/
 *        https://mandrillapp.com/api/docs/messages.html
 *         http://apidocs.mailchimp.com/gettingstarted/serialized_http_arrays.php
 * JsonBuilder Documentation :
 *        http://groovy.codehaus.org/gapi/groovy/json/JsonBuilder.html <- buggy example
 *        http://jsonlint.com/
 *
 * Note : 'attachedContent' input is supposed to already be Base64 encoded
 **************************************************************************************/
def sendEmail(attachedContent) {
    def builder = new groovy.json.JsonBuilder()
    def root = builder(
        key: 'your_Mandrill_key',
        message: [
            text: 'Exampletextcontent',
            subject: 'examplesubject',
            from_email: 'your_email@isp.com',
            from_name: 'your_name',
            to: [[
                 email: 'destinee_email@xxx.yy',
                name: 'destinee_name',
                type: 'to'
            ]],      
            headers: [
                'Reply-To': 'donotreply@isp.com'
            ],
            attachments: [[
                type: 'text/plain',
                name: 'myfile.txt',
                binary: true,
                // For test purpose : content: 'YmxhYmxhYmxh' // : "blablabla" Base64 encoded
                content: attachedContent,
            ]] /* , // Compiles OK, but for whatever reason does not seem to work and breaks execution without any trace ?!!
            images: [[
                type: 'image/png',
                name: 'IMAGECID',
                binary: true,
                content: 'ZXhhbXBsZSBmaWxl'
            ]] */
        ]
    )
    assert root instanceof Map
    def jsonBody = builder.toString()

    def successClosure = { response ->
        log.debug "eMail sent, $response"
    }

    //def postParameters = "?key=<your_Mandrill_key>&message[subject]=example%20subject&message[from_email]=<your_email%40isp.com>&message[from_name]=<your_name>&message[to][0][email]=<destinee_email%40xxx.yy>&message[to][1][name]=<destinee_name>&message[to][2][type]=to&message[text]=Example%20text%20content"       
    def params = [
        uri: "https://mandrillapp.com/api/1.0/messages/send.json", // +postParameters,
        success: successClosure,
        body: jsonBody
    ]

    log.debug "jsonBody : $jsonBody"
    log.debug "sendEmail... ${params}"
    httpPostJson(params)
}


/**************************************************************************************
 * Base64 Encoder adapted from : http://www.wikihow.com/Encode-a-String-to-Base64-With-Java
 * See also : http://en.wikipedia.org/wiki/Base64
 **************************************************************************************/
def public static byte[] zeroPad(int length, byte[] bytes) {
        def byte[] padded = new byte[length]; // initialized to zero by JVM
        //Original, forbidden in ST : System.arraycopy(bytes, 0, padded, 0, bytes.length);
        for (int i = 0; i <= bytes.length-1; i += 1) {
            padded[i] = bytes[i]
            }
        return padded;
}
    
def public static encode(string) {
        //log.debug "encode(string : ${string}"
        def final base64code = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "+/";
        def encoded = "";
        def byte[] stringArray;
        try {
            stringArray = string.getBytes("UTF-8");  // use appropriate encoding string!
        } catch (Exception ignored) {
            stringArray = string.getBytes();  // use locale default rather than croak
        }
        // determine how many padding bytes to add to the output
        def int paddingCount = (3 - (stringArray.length % 3)) % 3;
        // add any necessary padding to the input
        stringArray = zeroPad(stringArray.length + paddingCount, stringArray);
        // process 3 bytes at a time, churning out 4 output bytes
        // worry about CRLF insertions later
        for (int i = 0; i < stringArray.length; i += 3) {
            int j = ((stringArray[i] & 0xff) << 16) +
                ((stringArray[i + 1] & 0xff) << 8) + 
                (stringArray[i + 2] & 0xff);
            encoded = encoded + base64code.charAt((j >> 18) & 0x3f) +
                base64code.charAt((j >> 12) & 0x3f) +
                base64code.charAt((j >> 6) & 0x3f) +
                base64code.charAt(j & 0x3f);
        }
        // replace encoded padding nulls with "="
        return splitLines(encoded.substring(0, encoded.length() -
            paddingCount) + "==".substring(0, paddingCount));
}

def public static splitLines(string) {
        def final int splitLinesAt = 76;
        def lines = "";
        for (int i = 0; i < string.length(); i += splitLinesAt) {
            lines += string.substring(i, Math.min(string.length(), i + splitLinesAt));
            lines += "\r\n";
        }
        return lines;
}

(Geko) #2

There’s an easier way to do base64 encoding in Groovy:

def strBase64 = "whatever".bytes.encodeBase64()

(JJG) #3

Gee, thanks, that’s MUCH simpler ! :smile:
Is there any kind of “high level” documentation which documents the pluses of Groovy on top of Java ?
It is much easier to find plenty of documentation on “bare” Java than on more confidential Groovy…


(Andy Rawson) #4

I was able to get sending images as an attachment working with Mandrill starting from this code, thanks! I am using the following with the Generic Camera Device from http://community.smartthings.com/t/generic-camera-device-using-local-connection/3269 and it works well so far. I am going to use it to get text messages with a camera snapshot when my driveway sensor is triggered.

def sendEmail(attachedContent) {
      def builder = new groovy.json.JsonBuilder()
      def root = builder(key: 'your_Mandrill_key', 
           message: [text: 'Hi',
           subject: 'Camera Alert',
           from_email: 'your_email@isp.com',
           from_name: 'your_name',
           to: [[email: 'sendto@isp.com',
                name: 'sendto_name',
    	    type: 'to'
	        ]],      
 	        headers: ['Reply-To': 'donotreply@isp.com'],
	        attachments: [[type: 'image/jpeg', 
                 name: 'image.jpg', 
                 content: "$attachedContent",
             ]]
      ]
     )                          
     assert root instanceof Map
     def jsonBody = builder.toString()

     def successClosure = { response ->
	  log.debug "eMail sent, $response"
	  }
        
    def params = [
         uri: "https://mandrillapp.com/api/1.0/messages/send.json", 
         success: successClosure,
         body: jsonBody
    ]
    // log.debug "jsonBody : $jsonBody"
    // log.debug "sendEmail... ${params}"
    
    httpPostJson(params)            
}

// Put the sendEmail before the storeImage is done

    def putImageInS3(map) {

	def s3ObjectContent

	try {
		def imageBytes = getS3Object(map.bucket, map.key + ".jpg")

		if(imageBytes)
		{
			s3ObjectContent = imageBytes.getObjectContent()
			def bytes = new ByteArrayInputStream(s3ObjectContent.bytes)
            
            def strBase64Image = bytes.bytes.encodeBase64()
            sendEmail(strBase64Image)

			storeImage(getPictureName(), bytes)
            
		}
	}
	catch(Exception e) {
		log.error e
	}
	finally {
		if (s3ObjectContent) { s3ObjectContent.close() }
	}
}