Keeping track of Emails with EWS and K2

I haven’t posted anything in a while as I have been working on another project, which I am hoping to unveil sometime very soon. In the meantime though  I wanted to talk about K2 and Exchange. Now we all know that K2 can talk to exchange and send emails and receive replies back in the form of SmartActions out of the box.

But what if we wanted to keep track of the emails sent from a K2 app then this gets a bit tricky. We could save the message in a database using  a SmartObject Event and then use the email Event to send the email. Which is an ok approach, but I think something could be done better, where we don’t need to have this two step/event approach.

So lets have a think about about what i want the assembly to do?

  1. Send an email
  2. View the mailbox
  3. View an email

We could modify the existing email event to do what I am suggesting below, but that would be a pain as we would need to do it every time we use the email event and would also require the person building the workflow to be able to write code.  With the approach  I am going to go through, it  will allow anyone to be able to build a workflow where it would track what emails are being sent without having to write code and more importantly every app will be able to see it’s own emails it has sent out.

We are going to create a Email Endpoint Assembly that will allow a workflow to send an email and reference a primary key , SN, Process Instance Id or  application type (see framework) and view it’s mailbox by same type of information.

Getting Started

We will need the following

  1. Visual Studio 2015+
  2. Microsoft exchange web service (EWS URL)
  3. Exchange version
  4. UserAccount specifically setup just to be used for K2 mailbox (I normally create a service account, that just has a mailbox)
  5. User Account Email Address
  6. Microsoft.Exchange.Webservices.dll

To do this i need use the assembly Microsoft.Exchange.Webservices.dll which you can get from here .

Once we have the above we can start building the new email endpoint assembly.

EWS Code

To setup the connection to exchange server,  it is important to identify which version of exchange we are talking too.

ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010_SP2);

When we have created an instance of the exchange service, we then give the instance the exchange web service url.

service.Url = new Uri(“Web service”);

ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010_SP2);
service.Credentials = new WebCredentials("Username", "Password");

service.Url = new Uri("Web service");
service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, "Email Address");

We have now got a connection to exchange server via it’s web service and we can do a number of different things such as

  1. Send Email
  2. View mailbox contents , such as the inbox or sent items
  3. View an email
  4. We can also do things such as create meeting requests

We will look at the basic code for sending an email

Sending an Email

To send an email we need to create an instance of the EmailMessage object and pass in the Exchange Service Object.

EmailMessage email = new EmailMessage(service);

Once we have done that we can access the properties and methods of EmailMessage object.

So we can give are email a subject email.Subject = “Subject”;

We can also give the email body and decide whether we want to send a plain text or a HTML message.

email.Body = new MessageBody(BodyType.HTML, Body);

EmailMessage email = new EmailMessage(service);
email.Subject = "Subject";
email.Body = new MessageBody(BodyType.HTML, Body);

To add recipients (To, Cc, Bcc) we just need to add the following code

  • email.ToRecipients.Add(“address”);
  • email.CcRecipients.Add(“address”);
  • email.BccRecipients.Add(“address”);

If you have more than one email address for ‘To’ or ‘Cc’ or the ‘Bcc’ then we can simply loop through the correct address method parameter. Like in the example below.

 if (To.Contains(";"))
 {
 String[] to = To.Split(';');
 foreach (var address in to)
 {
 email.ToRecipients.Add(address);
 }
 }
 else
 {
 email.ToRecipients.Add(To);
 }

To send the email we simply use .Send(); method

 email.SendAndSaveCopy();

Now we can send a basic email. So let us have a look how we can now extend this email so it can contain some additional properties that relate to the workflow it is being sent from.

The EmailMessage object allows us to add properties called extend properties and they are really simple to create. The only thing you need to remember is that the GUID used to identify the property must be the same every time we an email is sent and needs to be the same for when when we retrieve the mailbox.

So in this example i am going to bind the process instance id to the email message. We will then be able to search the sent items mailbox and retrieve all the messages that relates to that process instance id.

Creating extend properties.

This is the most important part , extend properties is what allows the ability to be able to group emails by the process Instance I’d, business key etc.. 

Create a Guid called ‘ProcessInstanceId’ and assign it a GUID.

Guid ProcessInstanceId_PropertySetId = Guid.Parse("fc0a27be-f463-472e-bea8-648e62d1d7dc")

We then have to define the extend property by giving the property a name in this case the property is called ‘ProcessInstanceId’ and we define the data type of the property as a ‘String’.

 ExtendedPropertyDefinition ProcessInstanceId_ExtendedPropertyDefinition = new ExtendedPropertyDefinition(ProcessInstanceId_PropertySetId, "ProcessInstanceId", MapiPropertyType.String);

 

Now that we have defined the property , we can now populate the email with the process instance id. In code example below I am checking to see if the ‘ProcessInstanceId’ is greater than 0 or is not null and if true it will assign the property the value of the ‘ProcessInstanceId’ and if it is false it will assign the property a 0.

email.SetExtendedProperty(ProcessInstanceId_ExtendedPropertyDefinition, (ProcessInstanceId > 0 | ProcessInstanceId != null ? ProcessInstanceId : 0));

 

Now every time we send an email, it will now contain the process instance id.  In the complete code example of the ‘Send Emall’ method below I have also added some additional properties to contain the following

  1. Primary Key of the main business data
  2. ProcessTypeId (framework see here)
  3. Foilo of the process instance
  4. MessageId, so we can identify each email
public static string SendEmail(string Subject,string Body, string To, string Cc,string Bcc,int importance, string sn,string Folio, int? ProcessInstanceId, string ProcessTypeId, string BusinessKey)
 {
 string result = string.Empty;
 ExchangeService service = ConnectToExchange();
 try
 {
 if (To != null || To.Length != 0)
 {
 EmailMessage email = new EmailMessage(service);
 email.Subject = Subject;
 email.Body = new MessageBody(BodyType.HTML, Body);

Guid SN_PropertySetId = Guid.Parse("fc0a27be-f463-472e-bea8-648e62d1d7dc");
 ExtendedPropertyDefinition SN_ExtendedPropertyDefinition = new ExtendedPropertyDefinition(SN_PropertySetId, "SN", MapiPropertyType.String);
 email.SetExtendedProperty(SN_ExtendedPropertyDefinition, (!String.IsNullOrEmpty(sn) ? sn : "0_0"));

Guid Folio_PropertySetId = Guid.Parse("fc0a27be-f463-472e-bea8-648e62d1d7dc");
ExtendedPropertyDefinition Folio_ExtendedPropertyDefinition = new ExtendedPropertyDefinition(Folio_PropertySetId, "Folio", MapiPropertyType.String);
 email.SetExtendedProperty(Folio_ExtendedPropertyDefinition, (!String.IsNullOrEmpty(Folio) ? Folio : "Email Message"));

Guid ProcessInstanceId_PropertySetId = Guid.Parse("fc0a27be-f463-472e-bea8-648e62d1d7dc");
 ExtendedPropertyDefinition ProcessInstanceId_ExtendedPropertyDefinition = new ExtendedPropertyDefinition(ProcessInstanceId_PropertySetId, "ProcessInstanceId", MapiPropertyType.String);
 email.SetExtendedProperty(ProcessInstanceId_ExtendedPropertyDefinition, (ProcessInstanceId > 0 | ProcessInstanceId != null ? ProcessInstanceId : 0));

Guid BusinessKey_PropertySetId = Guid.Parse("fc0a27be-f463-472e-bea8-648e62d1d7dc");
 ExtendedPropertyDefinition BusinessKey_ExtendedPropertyDefinition = new ExtendedPropertyDefinition(BusinessKey_PropertySetId, "BusinessKey", MapiPropertyType.String);
 email.SetExtendedProperty(BusinessKey_ExtendedPropertyDefinition, (!String.IsNullOrEmpty(BusinessKey) ? BusinessKey : "0"));

Guid ProcessTypeId_PropertySetId = Guid.Parse("d6520129-3c59-4191-b9d7-4f5160329e4f");ExtendedPropertyDefinition ProcessTypeId_ExtendedPropertyDefinition = new ExtendedPropertyDefinition(ProcessTypeId_PropertySetId, "ProcessTypeId", MapiPropertyType.String);
 email.SetExtendedProperty(ProcessTypeId_ExtendedPropertyDefinition, (!String.IsNullOrEmpty(ProcessTypeId) ? ProcessTypeId : "00000000-0000-0000-0000-000000000000"));

Guid MessageId_PropertySetId = Guid.Parse("6e997d14-d9b3-4516-8d14-0a10b0aa74aa");
 string MessageId = Guid.NewGuid().ToString();
 ExtendedPropertyDefinition MessageId_ExtendedPropertyDefinition = new ExtendedPropertyDefinition(MessageId_PropertySetId, "ProcessTypeId", MapiPropertyType.String);
 email.SetExtendedProperty(MessageId_ExtendedPropertyDefinition, MessageId);



if (To.Contains(";"))
 {
 String[] to = To.Split(';');
 foreach (var address in to)
 {
 email.ToRecipients.Add(address);
 }
 }
 else
 {
 email.ToRecipients.Add(To);
 }



if (!string.IsNullOrEmpty(Cc))
 {
 if (Cc.Contains(";"))
 {
 String[] to = Cc.Split(';');
 foreach( var address in to)
 {
 email.CcRecipients.Add(address);
 }
 }
 else
 {
 email.CcRecipients.Add(Cc);

}
 }

if (!string.IsNullOrEmpty(Bcc))
 {
 if (Bcc.Contains(";"))
 {
 String[] to = Bcc.Split(';');
 foreach (var address in to)
 {
 email.BccRecipients.Add(address);
 }
 }
 else
 {
 email.BccRecipients.Add(Cc);

}
 }

if (importance > 0)
 {
 email.Importance = (importance == 1 ? Microsoft.Exchange.WebServices.Data.Importance.Normal : Importance.High);
 }

email.SendAndSaveCopy();

result = email.Id.ToString();
 }
 }
 catch(Exception ex)
 {
 result = "Error: " + ex.Message.ToString(); 
 }
 finally
 {

}
 return result;
 }

Retrieving an Exchange Mailbox

Now that we can send emails with K2 related data we now need to be able to retrieve those emails. So we can then view them in a SmartForm.

The first thing we need

public static List<EmailBox> GetMailBox(string MailBoxType,int PageSize)
 {
 ItemView view = new ItemView(PageSize);
 List<EmailBox> list = new List<EmailBox>();

Guid SN_PropertySetId = Guid.Parse("fc0a27be-f463-472e-bea8-648e62d1d7dc");
 ExtendedPropertyDefinition SN_ExtendedPropertyDefinition = new ExtendedPropertyDefinition(SN_PropertySetId, "SN", MapiPropertyType.String);

Guid Folio_PropertySetId = Guid.Parse("fc0a27be-f463-472e-bea8-648e62d1d7dc");
 ExtendedPropertyDefinition Folio_ExtendedPropertyDefinition = new ExtendedPropertyDefinition(Folio_PropertySetId, "Folio", MapiPropertyType.String);

Guid ProcessInstanceId_PropertySetId = Guid.Parse("fc0a27be-f463-472e-bea8-648e62d1d7dc");
 ExtendedPropertyDefinition ProcessInstanceId_ExtendedPropertyDefinition = new ExtendedPropertyDefinition(ProcessInstanceId_PropertySetId, "ProcessInstanceId", MapiPropertyType.String);

Guid BusinessKey_PropertySetId = Guid.Parse("fc0a27be-f463-472e-bea8-648e62d1d7dc");
 ExtendedPropertyDefinition BusinessKey_ExtendedPropertyDefinition = new ExtendedPropertyDefinition(BusinessKey_PropertySetId, "BusinessKey", MapiPropertyType.String);

Guid ProcessTypeId_PropertySetId = Guid.Parse("d6520129-3c59-4191-b9d7-4f5160329e4f");
 ExtendedPropertyDefinition ProcessTypeId_ExtendedPropertyDefinition = new ExtendedPropertyDefinition(ProcessTypeId_PropertySetId, "ProcessTypeId", MapiPropertyType.String);

Guid MessageId_PropertySetId = Guid.Parse("6e997d14-d9b3-4516-8d14-0a10b0aa74aa");
 ExtendedPropertyDefinition MessageId_ExtendedPropertyDefinition = new ExtendedPropertyDefinition(MessageId_PropertySetId, "ProcessTypeId", MapiPropertyType.String);

ExchangeService service = ConnectToExchange();
 view.PropertySet = new PropertySet(BasePropertySet.IdOnly, ItemSchema.Subject, SN_ExtendedPropertyDefinition, Folio_ExtendedPropertyDefinition, ProcessInstanceId_ExtendedPropertyDefinition, BusinessKey_ExtendedPropertyDefinition, ProcessTypeId_ExtendedPropertyDefinition, MessageId_ExtendedPropertyDefinition);

FindItemsResults<Item> findResults = service.FindItems((MailBoxType == "Sent" ? WellKnownFolderName.SentItems : WellKnownFolderName.Inbox), view);
 foreach(Item email in findResults.Items)
 {
 Item mail = Item.Bind(service, email.Id);
 list.Add(new EmailBox
 {
 MailBoxType = MailBoxType,
 Subject = mail.Subject,
 Body = mail.Body,
 Importance = mail.Importance.ToString(),
 Id = mail.Id.ToString(),
 Categories = mail.Categories.ToString(),
 DateTimeCreated = mail.DateTimeCreated,
 DateTimeReceived = mail.DateTimeReceived,
 DateTimeSent = mail.DateTimeSent,
 Cc = mail.DisplayCc,
 To = mail.DisplayTo,
 SN = (email.ExtendedProperties.Count > 0 ? email.ExtendedProperties[0].Value.ToString():string.Empty),
 Folio = (email.ExtendedProperties.Count > 0 ? email.ExtendedProperties[1].Value.ToString(): string.Empty),
 ProcessInstanceId = (email.ExtendedProperties.Count > 0 ? email.ExtendedProperties[2].Value.ToString(): string.Empty),
 BusinessKey = (email.ExtendedProperties.Count > 0 ? email.ExtendedProperties[3].Value.ToString(): string.Empty),
 ProcessTypeId = (email.ExtendedProperties.Count > 0 ? email.ExtendedProperties[4].Value.ToString(): string.Empty),
 MessageId = (email.ExtendedProperties.Count > 0 ? email.ExtendedProperties[5].Value.ToString(): string.Empty)

});

}
 return list;

}

d

de

Retrieve an Email

Now that we can retrieve a list of emails from a mailbox we now need to be able to retrieve a single email.

We can do this.ww

public static EmailBox GetEmail(string Id)
 {
 EmailBox email = new EmailBox();
 ExchangeService service = ConnectToExchange();

try
 {
 Item mail = Item.Bind(service, (ItemId)Id);
 {
 email.Subject = mail.Subject;
 email.Body = mail.Body;
 email.Importance = mail.Importance.ToString();
 email.Id = mail.Id.ToString();
 email.Categories = mail.Categories.ToString() ;
 email.DateTimeCreated = mail.DateTimeCreated;
 email.DateTimeReceived = mail.DateTimeReceived;
 email.DateTimeSent = mail.DateTimeSent;
 email.Cc = mail.DisplayCc;
 email.To = mail.DisplayTo;
 email.SN = (mail.ExtendedProperties.Count > 0 ? mail.ExtendedProperties[0].Value.ToString(): string.Empty);
 email.Folio = (mail.ExtendedProperties.Count > 0 ? mail.ExtendedProperties[1].Value.ToString(): string.Empty);
 email.ProcessInstanceId = (mail.ExtendedProperties.Count > 0 ? mail.ExtendedProperties[2].Value.ToString(): string.Empty);
 email.BusinessKey = (mail.ExtendedProperties.Count > 0 ? mail.ExtendedProperties[3].Value.ToString(): string.Empty);
 email.ProcessTypeId = (mail.ExtendedProperties.Count > 0 ? mail.ExtendedProperties[4].Value.ToString(): string.Empty);
 email.MessageId = (mail.ExtendedProperties.Count > 0 ? mail.ExtendedProperties[5].Value.ToString(): string.Empty);
}
}
 catch(Exception ex)
 { }
 finally
 {

}
 return email;
 }

 

Now that we have these methods to send an email, retrieve a mailbox and to retrieve an email. We can now register the library as an endpoint assembly. 

We could extend this to be able to add attachments and we could also look at the calendar meeting requests and doing the same with those and extend their properties 

We can then build a SmartObject around it and then we can use it within are workflows and Smartforms. To make it even easier for people to use the new email SmartObject, we could wrap a SmartWizard around the methods.

The full solution can be downloaded from here 

 

How to: For Each Event

Over the last couple of weeks people have asked me how to use the for each loop event in K2. So I thought i would put this quick demo together.

The demo, that i am going to do is going to loop through a list of subscribers and update their status from ‘Not Sent’ to ‘Sent’.

Getting Started

I have created a simple Smart box  SmartObject for demo purposes that has the following properties

  1. 1. Name of type string
  2. Email of type string
  3. Id of type Auto number and Key
  4. Status of type string

smo

I have inserted 3 rows of data with the status of ‘Not Sent’. We will use this SmartObject to use the ‘For Each’ event to loop through the data.

Creating the workflow

for-each-simple

Adding the ‘For Each’ event

  1. Open K2 Studio or K2 for Visual Studio ( I am going to use K2 Studio)
  2.  Create a new workflow solution called ‘For Each Demo’
  3. Rename the workflow from process 1 to ‘ForEachWorkflow’
  4. Once the workflow canvas has loaded up
  5. Drag the ‘for each’ event on to the canvas
  6. In the event wizard, give the event the name ‘Loop demo’
  7. Click on Source ico icon, so the drop down list disappears  and open the context browser
  8. In the 1st tab (Environment) , expand ‘SmartObject Services’
  9. Go to the SmartObject you want to use and expand the list method you want to use
  10. Drag the property that you want to use, normally the primary key into the event box
  11. The SmartObject wizard loads up, click ‘Next’ and ‘Next’ again
  12. In ‘Select a return property’ make sure the ‘id’ value is selected.
  13. Click ‘Next’, make sure ‘Return all Results that match filter’ is selected.
  14. Click ‘Finish’
  15. For the reference name, enter in the name ‘Dummy Data Reference’
  16. For the index name, enter in the name ‘Dummy Index’
  17. Click on ‘Finish’

This slideshow requires JavaScript.

Interacting with the for each item

  1. Back in the canvas, you will notice there are now two line rules coming out of the activity
  2. Drag the placeholder event on the canvas and name both the event and activity with the name ‘End’
  3. Drag the ‘line’ named ‘No more items’ to the end activity
  4. Create a new activity on the canvas and name it ‘Status Change’
  5. Drag the SmartObject Event into the newly created activity
  6. In the event wizard for the SmartObject event, name it ‘Update Status’
  7. Select the update method for the SmartObject that you used with the ‘for each’ event
  8. Click ‘Next’
  9. For the ‘ID’ property, click assign
  10. In the context browser go to the 3rd tab (Data) and expand ‘Item references’ and then expand ‘ Loop demo reference’
  11. Click on ‘Id’ property and click on ‘Add’
  12. Click on ‘Status’ and click ‘Assign’ and enter in the text ‘Sent’
  13. Click on ‘Next’ and ‘Next’ again and then ‘Finish’
  14. Drag the remaining line named ‘Next item’ to this activity and then create a new line from this activity back to the ‘for each’ activity.

This slideshow requires JavaScript.

That is a simple example of using the ‘For each’ event. Anything that follows the ‘Next item’ line is in side the loop and has the context of the current row the loop is on. You can have multiple activities in this part and the workflow  will move on to the next item once it has completed everything in that section. You must always have a line going back to the activity with the ‘for each’ event in.

Other things to remember

  1. Choose 1 property to return in the for each event. Always best to use a primary or foreign key
  2. Remember to select ‘Return all Results that match filter’ as it will return the complete list back and it doesn’t matter if there is no filter applied.

 

Next step using references with the ‘For Each’ Event

So we have created a simple workflow with a simple for each loop. But what if we wanted to get more data from the for each event and then possibly use it in a line rule.

In the next example, the workflow is going to check the current status of the item it is looking at. If it’s status is already ‘Sent’ it will ignore it and move on to the next item in the list and if the status is ‘Not Sent’ it will then update the status as normal

I am going to use the same workflow as before

Creating the reference

  1. In the context browser, go to the 3rd tab (data)
  2. Right click on ‘References’ and click on ‘Add’
  3. In the ‘SmartObject Method Reference’ click on ‘Next’
  4. In name, give the reference a name , for my example i have used ‘Dummy data’
  5. In SmartObject Method, select a read type method that uses value you have selected in the for each as the input parameter. I am pointing my reference to the read method.
  6. Click ‘Next’
  7. In input mapping, click on Assign and select the id from the item reference for the for each loop.
  8. Click ‘Next’ and ‘Finish’
  9. We have now created a reference to get the related details based on the current row of the ‘for each’ event.

This slideshow requires JavaScript.

 

Editing the ‘for each’ line rule for ‘Next Item’

Now that we have the reference setup, we can edit the line rule for ‘Next Item’

  1. Right click on ‘Next item’
  2. Click on ‘Properties’
  3. ‘Line General Properties’ will load up. Click on  greenarrowthe green arrow for line rules
  4. Click on ‘Add’
  5.  In rule editor, select ‘And’ from ‘Boolean Operator’
  6. In ‘First Variable’ click on eclipse
  7. In the context browser,  go the references in the 3rd tab
  8. Expand References, expand your reference you created in the last section
  9. Drag ‘Status’ into the ‘First Variable’
  10. The ‘SmartObject Wizard ‘ pops up, click on ‘Status’
  11. Click ‘Finish’
  12. Select ‘<>’ from ‘Comparison Operator’
  13. In the ‘Second Variable’ enter ‘Sent’
  14. Click on ‘Ok’
  15. Click on ‘Finish’
  16. We now need to create another rule that is similar to the ‘Next Item’ rule but where the ‘Status’ is equal to ‘Sent’

This slideshow requires JavaScript.

Creating a new line rule for ‘Next Item’ and where status is equal to ‘Sent’

  1. Right click and drag a line from the ‘For Each’ activity to ‘Update Status’
  2. Then drag the line from ‘Update Status’ to ‘For Each’. (We have to do this as line can’t go back on it self without being connected to another activity first)
  3. Right click on the line and click on ‘properties’
  4. In ‘Label’ enter ‘New Item and status is sent’
  5. Click on greenarrow and then click on ‘Add’
  6. In the Rule editor in ‘First Variable’
  7. Click on eclipse and expand the process data fields
  8. Click on ‘Dummy index result’ and click ‘Ok’
  9. Select ‘=’ for the operator
  10. In Second variable enter in ‘True’
  11. Click ‘Ok’
  12. Click on ‘Add’
  13.  In rule editor, select ‘And’ from ‘Boolean Operator’
  14. In ‘First Variable’ click on eclipse
  15. In the context browser,  go the references in the 3rd tab
  16. Expand References, expand your reference you created in the last section
  17. Drag ‘Status’ into the ‘First Variable’
  18. The ‘SmartObject Wizard ‘ pops up, click on ‘Status’
  19. Click ‘Finish’
  20. Select ‘<>’ from ‘Comparison Operator’
  21. In the ‘Second Variable’ enter ‘Sent’
  22. Click on ‘Ok’
  23. Click on ‘Finish’

 

This slideshow requires JavaScript.

 

Download example here 

A Week to go

So it’s one week before SharePoint Saturday London, I am busy getting ready and K2D2 is well relaxing, how does that work?

Anyway we will be there presenting a session on using Internet of Things with SharePoint 2013 on premise and also Office 365. The internet of things is becoming a big thing at the moment, with businesses wanting to access information from devices in real time from all over the world and then act on it accordingly. Join me for a fun session where we look at taking data from sensors of all sorts and pushing it into sharepoint with a little help from a workflow engine and hardly any code.

K2 will also be there with a stand,  with a brilliant competition to win a Anke Drive which is a bit like a scalextric meets Mario cart. Which sounds very cool. There will also be other swag too like are famous flashy bouncy balls!! So come by and say ‘Hi’ and enter the competition and you could be going home with this

Also don’t forget there will also be SharePint after the conference which is always brilliant and the whole a event is free!!

You can sign up here