Connecting IFTTT to Lync – getting IMs when stuff happens
It’s been a while since I posted anything technical. I’m actually working on something pretty cool which I can’t show you yet, but in the meantime I wanted to share this, which I’ve been playing with for a few days. Â As developers, sometimes it’s fun to do something for ourselves: something that makes our lives easier, or just something we think is a bit cool. This is one of those things.
You must have heard of IFTTT (if this then that). If not, go and play with it, it will change your life. It’s glue for your SaaS applications. Need to get an email if the weather tomorrow is raining, or turn your lights on when the temperature drops? IFTTT is for you.
We were chatting on Twitter about IFTTT and suddenly thought – why not integrate Lync with IFTTT? That could be handy, getting instant messages when things happen. Let’s connect one cool thing with another, for super awesome useful!
IFTTT works by having lots of different channels, which you then link together into recipies (if this channel does something, do something on this other channel). I very briefly looked at making a Lync channel, but it’s something which Every Single small startup service wants to do, and there’s a huge and private waiting list. It wasn’t going to happen.
However, IFTTT does have an email channel, so I can pipe IFTTT events to email. What if I wrote something to check an email alias and process new messages, then send them as Lync instant messages? That sounds fun.
Update: I’ve put the source code for this project on GitHub so you can either take it and do something like this yourself, or fork it and make it better!
Show me the pictures!
Some people don’t want to read the whole technical post about how it’s done, they just want to see pictures of the finished solution. Here you go:
Why not read on to find out how to do this for yourself?
The Design
The design is pretty simple:
- Periodically check an email alias for emails to send as messages.
- For each message, take the body of the email, store it, then delete the email.
- Start an instant message conversation, sending each message body as a new instant message.
- Repeat for
profitsatisfaction.
I’m not going to worry about the Lync contact being online, or try and separate out different sorts of messages from IFTTT at this stage: this is just about getting something working.
Choosing the right Lync API
Of course, the first thing I did next was go and read about choosing the right Lync API. 😉
Initially I thought about using the Lync Client SDK, however this would mean I would need to run the application on my machine, where I’m logged into Lync. Then I realized that wouldn’t work either: I can’t IM myself!
I could have created a full-on UCMA application, but I would need a UCMA server to run it on. That’s a lot of infrastructure for such a small application.
However, if I used a UCMA application with a User Endpoint, then the application wouldn’t need to run on a UCMA server (any machine with the UCMA SDK installed would work). I would need to log in as  Lync user and wouldn’t get any of the magic powers of a “full” UCMA application, but all I want to do is send an instant message, which is well within the capabilities of a User Endpoint. Decision made!
Writing the IM Sending Part
I’m using UCMA 4 for this, which supports Tasks for asynchronous methods, although they’re not written into the UCMA SDK. Michael Greenlee has written a bunch of the more common Tasks as extension methods though and hosted them on CodePlex, making it super-easy to add them to any project. They’re fantastic and I use them regularly to make life easier and the code simpler. For instance, in the Collaboration Platform code below, to start the platform I would usually have to call BeginStartup, then write and pass an EndStartup to finish off the asynchronous method. Because UCMA contains lots of these asynchronous methods, the code can end up looking pretty fractured, with one End.. method calling another Begin.. method and everything being chained together. However, with the Tasks extensions I can await StartupAsync. The execution of the code is similar (it’s NOT executed synchronously, it runs on a new thread and calls back), it just looks like it does, so the code is a lot simpler to read.
There are three main parts to this Lync code: setting up the platform, signing in (known as Establishing), and sending the message.
Setting up the platform is pretty simple – all it really does is line up the internal code ready for logging on. I do this once when the application first starts up. Once it’s done, you don’t need to do it again, although you should shut it down when your application closes.
private async Task EstablishCollaborationPlatform() { string userAgent = "IFTTT/Email Lync Instant Message Sender"; var platformSettings = new ClientPlatformSettings(userAgent, Microsoft.Rtc.Signaling.SipTransportType.Tls); try { Console.WriteLine("Trying to start collab platform"); _collabPlatform = new CollaborationPlatform(platformSettings); _collabPlatform.ProvisioningFailed += _collabPlatform_ProvisioningFailed; _collabPlatform.AllowedAuthenticationProtocol = Microsoft.Rtc.Signaling.SipAuthenticationProtocols.Ntlm; await _collabPlatform.StartupAsync(); Console.WriteLine("Collab Platform Started."); } catch (Exception ex) { Console.WriteLine("Error starting collab platform"); Console.WriteLine(ex.ToString()); }
and, when you’re done:
_collabPlatform.ShutdownAsync();
Signing in means establishing an Endpoint, using some credentials. There are quite a few different authentication credential options – these work for me, but you may need something different for your environment. You can use any Lync user, apart from the user you want to send the IM to, of course. I set up a new user just for this, because I wanted to give it its own profile picture, because I like things that look pretty.
I’m signing in and out each time, not staying signed in for long periods of time on the off-chance there’s something to send. That seems cleaner, and it’s easier to code for:
private async Task Establish() { Console.WriteLine("Establishing with endpoint:" + _sipaddress); var settings = new UserEndpointSettings(_sipaddress); settings.Credential = new System.Net.NetworkCredential(_username, _password); _endpoint = new UserEndpoint(_collabPlatform, settings); await _endpoint.EstablishAsync(); }
Later, after the IM has been sent I log the user back out again:
private async Task LogOut() { await _endpoint.TerminateAsync(); }
Sending the message involves setting up a new conversation with a remote participant. I’m using a single destination SIP address and pre-defined toast message, both of which I’m specifying in settings and passing into the method. Once the conversation is established, for each message I need to deliver I send it out on the instant message flow. Once done I end the conversation. There’s no checking whether the user at the other end is online, or can accept these messages – this is only a simple application and I didn’t want to make it too complicated. (by the way, if that does happen, an exception will be raised and the messages will be lost. I’m OK with that as this is designed to deliver me timely messages as they happen). The code for doing that looks like this:
private async Task SendIM(List<string> messages, string toastMessage, string destinationSip) { Conversation conversation = new Conversation(_endpoint); InstantMessagingCall call = new InstantMessagingCall(conversation); ToastMessage toast = new ToastMessage(toastMessage); CallEstablishOptions options = new CallEstablishOptions(); await call.EstablishAsync(destinationSip, toast, options); foreach (var msg in messages) { await call.Flow.SendInstantMessageAsync(msg); } await conversation.TerminateAsync(); Console.WriteLine("IM Sent"); }
Writing the Email part
It’s nice to find out how to do new things, but sometimes you just want to Get Stuff Done. That’s where NuGet comes in brilliantly handy. I headed over to nuget.org, searched for “IMAP” and then installed ImapX using Package Manager and the command: Install-Package ImapX.
That means I can (without knowing anything about how it works) sign into my email account using:
_client = new ImapClient(_IMAPClientUri, true); if (_client.Connect() &amp;amp;amp;&amp;amp;amp; _client.Login(_IMAPUsername, _IMAPPassword)) { //do stuff with email }
and then use simple looking methods which I discovered using IntelliSense to navigate around my inbox and do what I need to do. Abstracting on the shoulders of giants.
You could set up a new email alias for this code, but I’ve decided to use my existing one. Therefore, I only want it to process emails with a specific subject, which I’m going to specify when setting up the IFTTT rules. (I’m using a subject line of “IFTTTIM”). The only thing I had to really think about was writing the filter to pick the keyword out the Subject line – I couldn’t find much in the documentation, but Google filled in the gaps.
The email code in full (because there isn’t much of it) is
using ImapX; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; namespace EmailToIMClassLibrary { public class NewEmailEventArgs : EventArgs { public NewEmailEventArgs(List<string> messages) { Messages = messages; } public List<string> Messages { get; set; } } public class Email { public event EventHandler<NewEmailEventArgs> NewMessages = delegate { }; private ImapClient _client; private string _IMAPClientUri; private string _IMAPUsername; private string _IMAPPassword; private string _subjectKeyword; public Email(string imapClientURI, string imapClientUsername, string imapClientPassword, string subjectKeyword) { _IMAPClientUri = imapClientURI; _IMAPUsername = imapClientUsername; _IMAPPassword = imapClientPassword; _subjectKeyword = subjectKeyword; } public void CheckNow() { var newMessages = CheckForMessages(); if (newMessages.Any()) { NewMessages(this, new NewEmailEventArgs(newMessages)); } } private List<string> CheckForMessages() { var messages = new List<string>(); _client = new ImapClient(_IMAPClientUri, true); if (_client.Connect() &amp;amp;amp;&amp;amp;amp; _client.Login(_IMAPUsername, _IMAPPassword)) { try { var keyword = _subjectKeyword; var emails = _client.Folders.Inbox.Search(string.Format("UNSEEN SUBJECT \"{0}\"", keyword), ImapX.Enums.MessageFetchMode.Full); Console.WriteLine(string.Format("{0} emails", emails.Count())); foreach (var email in emails) { messages.Add(email.Body.Text); email.Remove(); } } catch (Exception ex) { Console.WriteLine(ex.ToString()); } finally { _client.Logout(); _client.Disconnect(); } } else { Console.WriteLine("Bad email login"); } return messages; } } }
Putting it together and Deploying
Now that we have something that checks email and something which can send IMs. I’ve chosen to put those two things into separate classes, but you could have lumped them all together in one. Either way, we need a way of running things in the right order, continuously. I’ve decided to check for new messages every 30 seconds. I’m using a never-ending while loop, because this is sample, prototype, demo code. It’s not for production and there’s no easy way to stop it. Lecture over:
static void Main(string[] args) { try { GetConfigValues(); emailChecker = new EmailToIMClassLibrary.Email(imapclient, imapusername, imappassword, subjectKeyword); emailChecker.NewMessages += emailChecker_NewMessages; Console.WriteLine("Initialising lync im"); lyncIM = new EmailToIMClassLibrary.LyncIM(); lyncIM.Initialise(sipAddress, sipUsername, sipPassword); while (true) { emailChecker.CheckNow(); Thread.Sleep(30000); } } catch (Exception ex) { Console.WriteLine(ex.ToString()); } } static void emailChecker_NewMessages(object sender, EmailToIMClassLibrary.NewEmailEventArgs e) { Console.WriteLine("emailChecker_NewMessages"); lyncIM.SendIMToLyncUser(e.Messages, IMToastMsg, destinationSIP); }
The only thing I didn’t include above is the method GetConfigValues(). This just reads all the required values (such as login details, email, SIP addresses etc) into a bunch of parameters which then get used.
At this point, the code runs locally and I know it works. Let’s deploy!
I really wanted to create an Azure Worker Role out of this, because I think Platform as a Service (PaaS) is awesome and the way forward. However, I just couldn’t get it to work – I was hitting really odd UCMA run-time errors. Not being able to install the UCMA SDK, I don’t know if maybe I was missing some DLLs, or maybe the base image for worker roles doesn’t include some server prerequisites that UCMA has. Either way, I tried it for a few days and then gave up.
Instead, I created an Azure Virtual Machine to run this code for me. I went for the smallest one they do, an A0. It has a shared core and 768MB RAM. But it’s a tenth of a penny to run per hour, or £8 / $13 a month. I could halve that cost as well if I wrote a scheduler to shut it down at night.
Because by this point I was getting fed up with how long this project was taking, I just installed the UCMA SDK on the server, uploaded my Console App and double-clicked it. Because I’m just playing, this works fine. However, if you’re using Azure VMs in production then you need to read up on why you should check if you’re installation prerequisites have been met, what stateless VMs are and what happens when your VM get re-imaged.
Finally, making some recipies
At last, time to create some IFTTT recipes to activate my new connector. Here are some I made, to save you some time. I’m sure you can think of more exciting ones that fit your day-to-day life. If you create any good ones, publish them and let me know, I’ll update this list.
New Lync blog post on the Office Blog:
Mentioned on Twitter:
Weather below freezing tomorrow:
0 Comments
Trackbacks/Pingbacks