March 05, 2020

Inside Actionable Messages In Outlook

212 Views
Inside Actionable Messages In Outlook

By now, you must have been accustomed to actionable messages. As a user, you receive a mail with buttons like “Assign a ticket”, “Accept/Reject leave request”, “Mark a task as complete” and so and so forth. All you do is click on the button and you are taken to an appropriate and independent action screen controlled by some other application or the action just gets completed and the result is updated into an external system as well as within the mail itself. Your email acts as a small application! You don’t need to go out of your mailbox at all.

The question is as a developer, how do you do it or do we understand what exactly happens underneath within the Microsoft framework. This is the humble objective of this article.

What do you need to do to configure things?

In order to enable Actionable Message functionality, you need to provide certain information to Microsoft. The developer dashboard helps you to submit and track status of your submission via the web portal. Here you can find a form, that you need to fill up. Followings are some most important configuration settings:
  • Target URLs: HTTPS URLs which will be invoked by the actions from the message card.
  • Scope of submission: Here you can set the scope, i.e. whether you want to send mail to all the Office 365 users within your organization or any Office 365 user.
The email can be sent from any application – like a web application, MS Flow, etc.

Let’s get to the overall picture first. These actionable messages are also known as adaptive cards that you need to design at the onset. Remember that a message is a JSON object. Go to dev.outlook.com/actions to design this object first and then go to Card Playground. Here you can see various predesigned templates. You can use any one of them or you can create it from scratch. As actions, you just need to specify the REST endpoints. Just for verification, after designing your card, you can send it to yourself by clicking on “Send via email” and you will get the email containing the card.

Now let’s understand what happens behind the scene when you click on “Send via email”. When you click on “Send via email”, the card you designed is encapsulated within a script tag as follows:

<script type=”application/AdaptiveCards+json”>
		<!--BODY OF THE CARD -->>
	</script>
This script tag is added to the head section of the html email as follows:
	<html>
	<head>
		<meta http-equiv=”Content-Type” content=”text/html; charset=utf-8”>
		<script type=”application/AdaptiveCards+json”>
			….
		</script>
	</head>
	<body>
		…..
	</body>
	</html>

So, if your service already sends HTML email, you don’t need to rewrite your HTML; all you need to do is just to inject the script tag inside the head section. Also, you do not need to redesign your email delivery mechanism.

How does it work then?

Your service sends HTML email with the actionable message mark-up, it hits Office 365, Office 365 then checks if you are an approved sender to be able to send actionable messages. This check is performed according to your configuration settings as discussed earlier. If this check passes, the user receives the email. When the user clicks on the action, the Http post doesn’t directly land on your service, it is relayed via Office 365. Here Office 365 adds a bearer token, it is a standard JSON web token. Using this token, you can validate that the request has actually come from Office 365. Also, you can get some additional information, such as, who performs the action, etc. Then your service sends the response, maybe in the form of a new adaptive card and it is relayed to the user. Inside actionable messages in Outlook

Let’s take a demo?

In this simple demo, when “Somebody” in an organization requests for leave, an email will be sent to the appropriate manager. Upon receiving the email, the manager can either Approve or Reject the leave from the email itself. After taking the approve/reject action, the email will be replaced by a refreshed card.

The leave request card JSON is as follows:

{
    "$schema": "https://adaptivecards.io/schemas/adaptive-card.json",
    "type": "AdaptiveCard",
    "version": "1.0",
    "body": [
        {
            "type": "TextBlock",
            "text": "**<Somebody> has applied for leave**"
        },
        {
            "type": "TextBlock",
            "text": "Comment:"
        },
        {
            "type": "Input.Text",
            "isMultiline": true,
            "placeholder": "Add your comment",
            "id": "textComment"
        }
    ],
    "actions": [
        {
            "title": "Reject",
            "type": "Action.Http",
            "method": "post",
            "body": "{{textComment.value}}",
            "url": "https://demoapi.com/api/RejectLeave?id=1"
        },
        {
            "title": "Approve",
            "type": "Action.Http",
            "method": "post",
            "body": "{{textComment.value}}",
            "url": "https://demoapi.com/api/ApproveLeave?id=1"
        }
    ]
}

I have taken a token “” which you should replace with an appropriate name. Also, look at the actions section. There are two buttons, “Approve” & “Reject” and corresponding URLs are the appropriate API URLs. You need to replace them by your REST endpoints. I am passing “id” as QueryString, which may be needed to update the database as per your business logic. This “id” can be the unique identifier of the resource associated with the email is sent.

The manager will get the email as follows: Inside actionable messages in Outlook

The refreshed card JSON is as follows:

{
    "$schema": "https://adaptivecards.io/schemas/adaptive-card.json",
    "type": "AdaptiveCard",
    "version": "1.0",
    "body": [
        {
            "type": "TextBlock",
            "text": "**<status>**"
        }
    ],
    "actions": [
        {
            "title": "View",
            "type": "Action.OpenUrl",
            "url": "<URL to view the record in the application>"
        }
    ]
}

Here, I have taken a token "", which can be replaced by appropriate text corresponding to action (Approve/Reject in this case) taken by the user. Also, look at the actions section. Here I have used type as “Action.OpenUrl”. So, when the user clicks on the “View” button, it will open the specific URL.

The email will be refreshed as follows:

Inside actionable messages in Outlook

API:

You can create your API in any language of your choice. Here I have created simple API in asp .net for this demo. You need to host this API. You can host it in IIS or azure or whatever of your choice. This hosted url will be used as “Base url of your API”.

Here I have used a NuGet package, Microsoft.O365.ActionableMessage.Utilities.

Here is the demo code for my API:


using Microsoft.O365.ActionableMessages.Utilities;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
namespace ActionableEmailAPI.Controllers
{
    public class ApproveLeaveController : ApiController
    {
	public HttpResponseMessage Post([FromBody] string value)
        {
		//Checks if the request came from Office365
		if (Request.Headers.Authorization == null
|| !string.Equals(Request.Headers.Authorization.Scheme, "Bearer", StringComparison.OrdinalIgnoreCase)
                || string.IsNullOrEmpty(Request.Headers.Authorization.Parameter))
            {
                //return error response
                return Request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError());
            }
		string bearerToken = Request.Headers.Authorization.Parameter;

ActionableMessageTokenValidator validator = new ActionableMessageTokenValidator();

       ActionableMessageTokenValidationResult result = validator.ValidateTokenAsync(bearerToken, "<Base URL of your API>").Result;

            if (result.ValidationSucceeded == false)
            {
                //return error response
         return Request.CreateErrorResponse(HttpStatusCode.Unauthorized, new    HttpError());
            }
            //Get the id we passed as QueryString
            var allUrlKryValues =this.ControllerContext.Request.GetQueryNameValuePairs();
            string id = allUrlKryValues.LastOrDefault(x => x.Key == "id").Value;
	     //Get action performer, i.e. who takes the action
	     string strActionPerformer = result.ActionPerformer;
	     bool IsValidActionPerformer = false;
           // Check if the Action performer is valid according to your business logic
	    // and set IsValidActionPerformer variable as per your logic
	     if(IsValidActionPerformer == false)
            {
			//return error response
HttpResponseMessage errorResponse =   Request.CreateResponse(HttpStatusCode.Forbidden);
errorResponse.Headers.Add("CARD-ACTION-STATUS", "Invalid action performer");
                return errorResponse;
            }
	     //Perform your business logic using id, strActionPerformer value….
	    //After execution of your business logic, return the refresh card
           //Assuming that the “RefreshCard.json” file is kept in the root folder
           string strRefreshCard = "";
            strRefreshCard = File.ReadAllText(System.Web.Hosting.HostingEnvironment.MapPath("~/RefreshCard.json"));
            strRefreshCard = strRefreshCard.Replace("<status>", "Leave Approved");

            HttpResponseMessage successMessage = Request.CreateResponse(HttpStatusCode.OK);
            successMessage.Headers.Add("CARD-UPDATE-IN-BODY", "true");
            successMessage.Content = new StringContent(strRefreshCard);

            return successMessage;
	 }
    }
}

The actionable messaging in Outlook is a smart way of engaging the user without leaving the context. The goal of this article was to take you through a sample implementation and results and to show how easy and effective it could be towards improving productivity of the business users. This is quite extensible, and I will come back to it later. Go, enrich your mails with actions, make your users happy and more efficient!

Leave a Reply

Your email address will not be published. Required fields are marked *