Using the o365 APIs and ADAL to Send Email from an Unattended Process
UPDATE: We just posted a blog on a new and better way to do this! Check out the blog post at https://blogs.msdn.com/b/exchangedev/archive/2015/01/22/building-demon-or-service-apps-with-office-365-mail-calendar-and-contacts-apis-oauth2-client-credential-flow.aspx and use that method going forward.
One of the things you frequently want to do in your custom applications is send out emails. Historically this could be solved by using an SMTP server you have on site, using Exchange Web Services with an Exchange server either on prem or in the cloud, using a public SMTP service, etc. As I was thinking about this recently I of course thought, wow, I should just do this using the new o365 APIs. This proved a little more challenging that I was originally expecting so I figured I’d blog about it here and try and lower another barrier of use.
I haven’t actually written anything on using the o365 APIs and email yet (although I’m thinking about another little sample app for it like I did with the OneDrive browser…let me know in the comments if this is of any interest to you). The o365 APIs for working with email (so far) has actually been refreshingly easy…not as full featured as Exchange Web Services, but honestly a lot easier for the common tasks. The real challenge in getting this going actually was more on the authentication side – getting the application authenticated and able to use a mailbox. We do have a sample out on github that I was originally looking at that talks about an application getting an access token and using that to impersonate a user (https://msdn.microsoft.com/en-us/library/azure/dn499820.aspx#BKMK_Server). Unfortunately that scenario assumes you have a user that’s already authenticated to your application so you can use that access token; that’s not going to be the case with a typical unattended console type application. Then there was an app that uses an application token instead of a user token (https://github.com/AzureADSamples/Daemon-DotNet), but that doesn’t help either because an app doesn’t have a mailbox.
Finally I settled upon the magic overload for acquiring an AuthenticationResult that you should use in this scenario:
public AuthenticationResult AcquireToken(string resource, string clientId, UserCredential userCredential);
The code for this using it is relatively simple and looks like this:
UserCredential uc = new UserCredential(EMAIL_SERVICE_ACCOUNT, EMAIL_SERVICE_PWD);
ar = AuthContext.AcquireToken(resourceId, ClientID, uc);
Now here’s part of the problem that I ran into and what this tougher to figure out. When I started my application in Visual Studio, I had it create the application in Azure for me using the Add Connected Service wizard, which I explained in this blog post: https://blogs.technet.com/b/speschka/archive/2014/12/08/oauth-o365-apis-and-azure-service-management-apis-using-them-all-together.aspx. Well when it created the app it set up the permissions I asked for related to Outlook – basically reading, writing and sending emails. That was the only set of permissions it granted though. So when I first called AcquireToken as shown above, it threw an exception like this:
AADSTS65001: No permission to access user information is configured for 'f276aaaa-aaaa-aaaa-aaaa-bbbbbbbbbbbb' application, or it is expired or revoked.
(In this case 'f276aaaa-aaaa-aaaa-aaaa-bbbbbbbbbbbb' is the client ID of my application in Azure)
So I went to look at the application in Azure and saw that it had the correct o365 permissions configured; my hunch though was that it needed Azure AD permissions too. Here’s where it got ugly – I tried adding permissions to Azure AD to login and read users profiles, but it would not let me do it. It kept failing in fact when I tried to add any Azure AD permission to my application. Good grief.
Long story short, that sent me on a multi-hour goose chase trying out different authentication options, but none of them work. I really thought I had the right one though, so what I ended up doing was going back into the Azure management portal and manually creating a new application. I created it for as a Native Client application, and it was automatically given permissions to Azure AD to sign-on and read users’s profiles. I then added my o365 application permissions to it, tried my code again and – voila’ – the call to get an AuthenticationResult started working. Yay! The hardest part of this story is now done.
With that code out of the way, the process goes something like this:
Get an access token using a service account username and password. Where and how you store and retrieve the username and password is up to you; to simplify my demo I just hard-coded it in my console application.
Get an instance of the OutlookServicesClient. There’s really two things you need to know about doing this – the resource ID and endpoint URI. Again, normally one would probably use the Discovery service to get these values and then use that to get your OutlookServicesClient. In my case I actually did that originally and then found the values to be so generic that I decided (for now) to just use them directly instead of querying the Directory service every time. I’m sure someone will take great joy in pointing out how “unfuture proof” such an approach is but hey – it’s a demo, you know what you’re working with here, so adjust to fit your needs. To complete this thought, for Outlook the resource ID is https://outlook.office365.com/ and the endpoint URI is https://outlook.office365.com/api/v1.0.
Create your email message.
Save it as a draft (so you can get the message ID)
Send it.
There are two other things worth noting for #4 and #5. In a winforms or ASP.NET application I would do those operations asynchronously with the await keyword. However, that doesn’t really work in a console app since there is just the single main app thread (the app continues without waiting basically). To work around that in a console app I used a simple pattern to wait on a task, so for example #4 looks like this:
Task t = oc.Me.Messages.AddMessageAsync(draftMessage);
t.Wait();
The rest of it is pretty straightforward. I’ve included the complete project attached to this post. You just need to:
Go create your own application in Azure
Use the client ID and return URI from it to replace the values in the app
Use the username and password of your own service account that has a mailbox in o365
Comments
- Anonymous
January 10, 2015
Thanks for your blog post. It helped me resolve my AADSTS65001 error quick.