Automatically respond to emails in Microsoft Office 365 Exchange Online using a Windows Service

I recently migrated from Google Apps to Office 365 (included with the Microsoft Action Pack). I had been using the Google Labs Canned Responses tool to send an automatic reply to emails I received from recruitment agencies, containing my current availability and the types of projects I’m looking to work on. Unfortunately there is no similar capability in Office 365 Exchange Online.

So, I wrote a Windows Service to do the equivalent work. The obvious downside to this approach is the need to have somewhere to host the service: in my case an on-premise server. However, it did mean I was able to address a number of limitations with the Canned Response tool, such as:

  • Total control over the HTML in my response email
  • Excluding certain email addresses from receiving an automatic response
  • Ignoring bounce addresses and always replying directly to the sender

I set up a standard Exchange/Outlook rule to drop emails from agencies into a particular folder (I register specific email addresses with agencies, so I know email received to those addresses must originate from an agency).

The service reads each message in that folder, sends the response and moves the message to another folder.

It processes the folder every 15 minutes using the TimerCallback method, and the communication with Exchange is done through the Exchange Web Services (EWS) Managed API.

Here’s the code if you’re looking to do something similar:

// Authenticate with Exchange
ExchangeService service = GetService(USERNAME, PASSWORD);

// Get Recruitment Agencies Folders
Folder raFolderNew = FindFolder(service, "Recruitment Agencies");
Folder raFolderResponded = FindFolder(service, "Recruitment Agencies (Responded)");

// Read Response Email Content from file
string emailContent = File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "agencyresponse.html"));

// Read list of excluded email addresses from file
string[] exclusionList = File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "agencyexclude.txt"));

// Loop through items in folder
foreach (Item item in raFolderNew.FindItems(new ItemView(250)))
{
    // Convert to EmailMessage
    EmailMessage receivedEmail = item as EmailMessage;

    // If conversion fails, skip
    if (receivedEmail == null) continue;

    // Build collection of addresses to mail back
    List<EmailAddress> replyAddresses = new List<EmailAddress>();
    if (receivedEmail.Sender != null) AddAddressToList(replyAddresses, receivedEmail.Sender);
    if (receivedEmail.From != null) AddAddressToList(replyAddresses, receivedEmail.From);
    if (receivedEmail.ReplyTo != null)
    {
        foreach (EmailAddress replyToAddress in receivedEmail.ReplyTo)
        {
            AddAddressToList(replyAddresses, replyToAddress);
        }
    }

    // Drop out if any of these are on the exclusion list
    bool sendEmail = true;
    foreach (string replyAddress in replyAddresses.Select(q => q.Address))
    {
        if (exclusionList.Contains(replyAddress))
        {
            sendEmail = false;
            break;
        }
    }

    // Create the Email message
    if (sendEmail)
    {
        EmailMessage responseEmail = new EmailMessage(service)
        {
            Sender = responseSender,
            Subject = string.Format("RE: {0}", receivedEmail.Subject),
            Body = new MessageBody(BodyType.HTML, emailContent)
        };
        responseEmail.ToRecipients.AddRange(replyAddresses);

        // Send email
        responseEmail.SendAndSaveCopy();
    }

    // Move original email to "Responded" folder
    receivedEmail.Move(raFolderResponded.Id);
}

…and here are the functions used by the above:

private void AddAddressToList(List replyAddresses, EmailAddress emailAddress)
{
    // Add address to list if it is not already there
    if (!replyAddresses.Any(q => q.Address == emailAddress.Address)) replyAddresses.Add(emailAddress);
}

private Folder FindFolder(ExchangeService service, string folderName)
{
    // Create search filter on folder display name
    SearchFilter searchFilter = new SearchFilter.IsEqualTo(FolderSchema.DisplayName, folderName);

    // Find folder
    FindFoldersResults findFolderResults = service.FindFolders(WellKnownFolderName.Root, searchFilter,
        new FolderView(1)
        {
            PropertySet = new PropertySet(BasePropertySet.FirstClassProperties),
            Traversal = FolderTraversal.Deep
        });

    // Return result
    return findFolderResults.Folders.FirstOrDefault();
}

private ExchangeService GetService(string username, string password)
{
    ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2013_SP1);
    service.Credentials = new WebCredentials(username, password);
    service.AutodiscoverUrl(username, RedirectionUrlValidationCallback);
    return service;
}

private bool RedirectionUrlValidationCallback(string redirectionUrl)
{
    return new Uri(redirectionUrl).Scheme == "https";
}

Leave a Reply

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