Dec 24

Written by: Michael Washington
12/24/2018 6:34 PM  RssIcon

image

You can create an Azure function that will retrieve emails, every 5 minutes, from a Pop3 email account and create Help Desk Tickets. To demonstrate this, we will implement a solution that creates Help Desk Tickets in the popular open source Help Desk application ADefHelpDesk.com.

image

ADefHelpDesk has an API that provides the following features that we will explore:

  • Requires callers to first obtain a JSON Web Token (JWT) (also called a Bearer Token), using a combination of a UserName, Password, and Application GUID. The token is then passed in the header of the subsequent requests to the other methods to authenticate the requests.
  • Allows external caller to create Help Desk Tickets (and perform all other functionality). We will create a Microsoft Azure Function that will retrieve the Pop3 emails, and call the ADefHelpDesk API to create the Help Desk Tickets.
  • As part of creating Help Desk Tickets, the ADefHelpDesk API allows the external caller to upload files. We will use this API function to upload the original email. If the Email is in the proper MIME format, ADefHelpDesk will display the email and any attachments to the email in its online web interface. Otherwise, it will display a link for the original email to be downloaded.

 

Install ADefHelpDesk

image

You can download and install ADefHelpDesk from the site at: http://ADefHelpDesk.com.

image

After you have the site running, follow the directions at the link: API Security (Swagger Rest API) on the documentation page that can be found at this link to create an account that can call the API.

image

You will need to copy the information on the Connection Information tab to use later.

image

In the ADefHelpDesk site there is a Swagger page (see: https://swagger.io/) that documents the REST based API endpoints at: http://{your default web address}/swagger/ (for example: http://adefhelpdesk.azurewebsites.net/swagger/).

 

Create The Application

image

Open Visual Studio.

image

Create an Azure Function project.

Note: Azure Functions Tools is included in the Azure development workload of Visual Studio 2017 version 15.4, or a later version. Make sure you include the Azure development workload in your Visual Studio 2017 installation (See: Azure Functions Tools for Visual Studio for assistance).

image

Select Azure Functions V2 (.Net Core) and the Timer trigger.

image

The project will be created.

Rename the Function1.cs file to Pop3ADefHelpDeskEmails.cs.

image

Open local.settings.json.

image

Replace the contents with the following (replacing ** Your Setting **  with your own values gathered earlier):

 

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet",
    "POP3Server": "** Your Setting **",
    "POP3Port": "** Your Setting **",
    "POP3UseSSL": "** Your Setting **",
    "POP3Username": "** Your Setting **",
    "POP3Password": "** Your Setting **",
    "APIWebAddress": "** Your Setting **",
    "ApplicationGUID": "** Your Setting **",
    "APIUserName": "** Your Setting **",
    "APIPassword": "** Your Setting **"
  }
}

 

Also, add keys and values to log into your Pop3 email server.

You can get the settings for popular services here: List of SMTP and POP3 Server (also a list here).

Save and close the file.

 

Add MailKit

image

To add MailKit, the open source library that will allow us to retrieve emails, we need to right-click on the Project node in the Solution Explorer, and select Manage NuGet Packages…

image

Search for MailKit, select it, and install it.

 

Add The Model Code

We will copy a lot of the code from the article: Calling a REST API from .Net Core Including Uploading Files. That article goes into depth on how a Ticket is created in AdefHelpDesk using the API, and how to use the Swagger page in ADefHelpDesk to determine what the properties are that the classes should have.

image

Add a folder called Models.

Add the following class files to the Models folder using the following code:

 

DTOApiToken

namespace Pop3ToADefHelpDesk
{
    public class DTOApiToken
    {
        public string userName { get; set; }
        public string password { get; set; }
        public string applicationGUID { get; set; }
    }
}

 

DTOStatus

namespace Pop3ToADefHelpDesk
{
    public class DTOStatus
    {
        public string StatusMessage { get; set; }
        public bool Success { get; set; }
    }
}

 

DTOTask

using System.Collections.Generic;
namespace Pop3ToADefHelpDesk
{
    public class DTOTask
    {
        public int? taskId { get; set; }
        public int? portalId { get; set; }
        public string description { get; set; }
        public string status { get; set; }
        public string priority { get; set; }
        public string createdDate { get; set; }
        public string estimatedStart { get; set; }
        public string estimatedCompletion { get; set; }
        public string dueDate { get; set; }
        public int? assignedRoleId { get; set; }
        public string assignedRoleName { get; set; }
        public string ticketPassword { get; set; }
        public int? requesterUserId { get; set; }
        public string requesterName { get; set; }
        public string requesterEmail { get; set; }
        public string requesterPhone { get; set; }
        public int? estimatedHours { get; set; }
        public bool? sendEmails { get; set; }
        public List<int> selectedTreeNodes { get; set; }
        public List<DTOTaskDetail> colDTOTaskDetail { get; set; }
    }
}

 

DTOTaskDetail

using System.Collections.Generic;
namespace Pop3ToADefHelpDesk
{
    public class DTOTaskDetail
    {
        public string taskDetailDescription { get; set; }
        public int detailId { get; set; }
        public string detailType { get; set; }
        public string contentType { get; set; }
        public string insertDate { get; set; }
        public int userId { get; set; }
        public string userName { get; set; }
        public string emailDescription { get; set; }
        public string startTime { get; set; }
        public string stopTime { get; set; }
        public bool? sendEmails { get; set; }
    }
}

 

Add The Extension Code

A extension method will be required to concatenate all the parameter values into a single string when calling the ADefHelpDesk API.

image

To facilitate this, create a folder called Extensions and add a class file called ObjectExtensions.cs using the following code:

 

using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace Pop3ToADefHelpDesk
{
    public static class ObjectExtensions
    {
        // From: https://bit.ly/2A5R70I
        public static IDictionary<string, string> ToKeyValue(this object metaToken)
        {
            if (metaToken == null)
            {
                return null;
            }
            JToken token = metaToken as JToken;
            if (token == null)
            {
                return ToKeyValue(JObject.FromObject(metaToken));
            }
            if (token.HasValues)
            {
                var contentData = new Dictionary<string, string>();
                foreach (var child in token.Children().ToList())
                {
                    var childContent = child.ToKeyValue();
                    if (childContent != null)
                    {
                        contentData = contentData.Concat(childContent)
                            .ToDictionary(k => k.Key, v => v.Value);
                    }
                }
                return contentData;
            }
            var jValue = token as JValue;
            if (jValue?.Value == null)
            {
                return null;
            }
            var value = jValue?.Type == JTokenType.Date ?
                jValue?.ToString("o", CultureInfo.InvariantCulture) :
                jValue?.ToString(CultureInfo.InvariantCulture);
            return new Dictionary<string, string> { { token.Path, value } };
        }
    }
}

 

Get The Authentication Token

The ADefHelpDesk API requires that you use the UserName and Password (the values are stored in the local.settings.json file as APIUserName and APIPassword), to call the /api/V1/GetAuthToken API method, to obtain a JSON Web Token (JWT) to use as authentication for subsequent calls to the other API methods.

image

To facilitate this, create a folder called Classes and add a class file called GetAuthToken.cs using the following code:

 

using Newtonsoft.Json;
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace Pop3ToADefHelpDesk
{
    public class GetAuthToken
    {
        public static async Task<string> GetAuthTokenFromADefHelpDesk(
            string APIWebAddress, 
            DTOApiToken paramApiToken)
        {
            // Store the final result
            string strResult = "";
            HttpClient client = new HttpClient();
            // Use the HttpClient 
            using (client)
            {
                // Create a new REST request
                using (var request = new HttpRequestMessage())
                {
                    // The Swagger page indicates this must be a "Post"
                    request.Method = HttpMethod.Post;
                    // Set the destination to the method indicated 
                    // on the Swagger page
                    // to: api/V1/GetAuthToken
                    request.RequestUri = 
                        new Uri($"{APIWebAddress}api/V1/GetAuthToken");
                    // Convert the parameters to 
                    // JavaScript Object Notation (JSON) format 
                    var json = JsonConvert.SerializeObject(paramApiToken);
                    request.Content = 
                        new StringContent(json, Encoding.UTF8, "application/json");
                    // Make the request to the API endpoint on the ADefHelpDesk site
                    var response = client.SendAsync(request).Result;
                    // Receive the response
                    var JsonDataResponse =
                        await response.Content.ReadAsStringAsync();
                    // Convert the response (the JWT (Auth Token)) to a String value
                    strResult =
                        JsonConvert.DeserializeObject<string>(JsonDataResponse);
                    // Strip the word Bearer from the token
                    strResult = strResult.Replace("Bearer ", " ").Trim();
                }
            }
            // Return the JWT
            return strResult;
        }
    }
}

 

This code will be called by the main code created in the final step.

 

Create The Help Desk Ticket

image

Now, add the code that will call the AdefHelpDesk API and create the Help Desk Ticket, and attach a file that contains the contents of the email.

Create a class file called CreateTicket.cs using the following code:

 

using Microsoft.AspNetCore.WebUtilities;
using MimeKit;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
namespace Pop3ToADefHelpDesk
{
    public class CreateTicket
    {
        public static async Task<DTOStatus> CreateADefHelpDeskTicket(
            DTOTask paramDTOTask,
            DTOTaskDetail paramDTOTaskDetail,
            MimeMessage paramMimeMessage,
            string APIWebAddress,
            string paramBearerToken)
        {
            // Final response
            HttpResponseMessage response;
            // The contents of the file will be stored in the MemoryStream
            MemoryStream stream = new MemoryStream();
            // Initialize classes and variables
            DTOStatus Result = new DTOStatus();
            HttpClient client = new HttpClient();
            string strFileName = "";
            using (client)
            {
                using (var request = new HttpRequestMessage())
                {
                    client.DefaultRequestHeaders.Authorization =
                        new AuthenticationHeaderValue("Bearer", paramBearerToken);
                    // Add Task parameters
                    // Call the ToKeyValue extension method
                    // to concatonate all the values into a single string
                    var TaskParameters = paramDTOTask.ToKeyValue();
                    // If there are Task Details add them
                    if (paramDTOTaskDetail != null)
                    {
                        // Call the ToKeyValue extension method
                        var TaskDetailParameters = paramDTOTaskDetail.ToKeyValue();
                        foreach (var item in TaskDetailParameters)
                        {
                            // Ass the TaskDetail parameters
                            TaskParameters.Add(item);
                        }
                    }
                    // The destination is the CreateTask API method
                    request.RequestUri =
                        new Uri(QueryHelpers.AddQueryString(
                            $"{APIWebAddress}api/V1/CreateTask"
                            , TaskParameters));
                    // The API method requires the 'verb' 
                    // or 'action' to be a 'Post'
                    request.Method = HttpMethod.Post;
                    // Determine the Email type
                    if (paramMimeMessage.Body.ContentType.MimeType == "text/html")
                    {
                        // This is a "text/html" file
                        // Get Email contents
                        var EmailContent = 
                            ((MimeKit.TextPart)paramMimeMessage.Body).Text;
                        // Save the Message to a stream 
                        byte[] encodedText = Encoding.Unicode.GetBytes(EmailContent);
                        stream.Write(encodedText, 0, encodedText.Length);
                        stream.Position = 0;
                        strFileName = "Attachment.html";
                    }
                    else
                    {
                        // If the email is not of type "text/html"
                        // Save the entire email as a .EML file
                        // ADefHelpDesk has the ability to view a EML file 
                        // and any attachments
                        // Save the .EML Message to a stream                    
                        paramMimeMessage.WriteTo(stream);
                        stream.Position = 0;
                        strFileName = "Attachment.EML";
                    }
                    using (stream)
                    {
                        MultipartFormDataContent _multiPartContent =
                            new MultipartFormDataContent();
                        // Serialize Request
                        StreamContent _fileData =
                            new StreamContent(stream);
                        _fileData.Headers.ContentType =
                            new MediaTypeHeaderValue("application/octet-stream");
                        ContentDispositionHeaderValue _contentDispositionHeaderValue =
                            new ContentDispositionHeaderValue("form-data");
                        _contentDispositionHeaderValue.Name = 
                            "objFile";
                        _contentDispositionHeaderValue.FileName = 
                            strFileName;
                        _fileData.Headers.ContentDisposition = 
                            _contentDispositionHeaderValue;
                        _multiPartContent.Add(_fileData, "objFile");
                        // Set content
                        request.Content = _multiPartContent;
                        // Send request
                        response = client.SendAsync(request).Result;
                    }
                    // If the Bearer token is expired
                    if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
                    {
                        Result.Success = false;
                        Result.StatusMessage = "Unauthorized";
                        return Result;
                    }
                    // ADefHelpDesk will respond with JSON
                    var JsonDataResponse =
                        await response.Content.ReadAsStringAsync();
                    // The final result is the Deserialized response
                    Result =
                        JsonConvert.DeserializeObject<DTOStatus>(JsonDataResponse);
                }
            }
            return Result;
        }
    }
}

 

This code will be called by the main code created in the next step.

 

The Main Code

image

The main code will connect to the mail server and retrieve the emails.

It will then contact the ADefHelpDesk API to obtain an authentication token (also know as a Bearer token or a JWT)). It will use that token to call the ADefHelpDesk API to create a Help Desk Ticket.

Replace all the code in the Pop3ADefHelpDeskEmails.cs file with the following code:

 

using MailKit.Net.Pop3;
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
using System;
using System.Linq;
namespace Pop3ToADefHelpDesk
{
    public static class Pop3ADefHelpDeskEmails
    {
        // "0 */5 * * * *" to run once every 5 minutes
        // "0 */1 * * * *" to run once a minute
        [FunctionName("Pop3ADefHelpDeskEmails")]
        public static void Run(
            [TimerTrigger("0 */5 * * * *")]TimerInfo myTimer,
            ILogger log)
        {
            log.LogInformation("function processed a request.");
            string _POP3Server =
                Environment.GetEnvironmentVariable("POP3Server");
            int _POP3Port =
                Convert.ToInt32(Environment.GetEnvironmentVariable("POP3Port"));
            bool _POP3UseSSL =
                Convert.ToBoolean(Environment.GetEnvironmentVariable("POP3UseSSL"));
            string _POP3Username =
                Environment.GetEnvironmentVariable("POP3Username");
            string _POP3Password =
                Environment.GetEnvironmentVariable("POP3Password");
            string _APIWebAddress =
                Environment.GetEnvironmentVariable("APIWebAddress");
            // ********** GET AuthToken from ADefHelpDesk
            // (needed to send the emails to ADefHelpDesk)
            DTOApiToken paramApiToken = new DTOApiToken();
            paramApiToken.userName =
                Environment.GetEnvironmentVariable("APIUserName");
            paramApiToken.password =
                Environment.GetEnvironmentVariable("APIPassword");
            paramApiToken.applicationGUID =
                Environment.GetEnvironmentVariable("ApplicationGUID");
            // Call the GetAuthToken method and retreive the
            // BearerToken (JWT - Auth Token)
            // (required to make the calls to ADefHelpDesk to save the messages)
            string BearerToken =
                GetAuthToken.GetAuthTokenFromADefHelpDesk(
                    _APIWebAddress, paramApiToken).Result;
            using (var client = new Pop3Client())
            {
                // ********** GET EMAILS
                // Configure connection to Email Server
                // For demo-purposes, accept all SSL certificates 
                // (in case the server supports STARTTLS)
                client.ServerCertificateValidationCallback = (s, c, h, e) => true;
                // Email Server settings
                client.Connect(_POP3Server, _POP3Port, _POP3UseSSL);
                // Note: since we don't have an OAuth2 token, disable
                // the XOAUTH2 authentication mechanism.
                client.AuthenticationMechanisms.Remove("XOAUTH2");
                // Pass username and password to email server
                client.Authenticate(_POP3Username, _POP3Password);
                // Loop through each email
                for (int i = 0; i < client.Count; i++)
                {
                    // message contains the entire contents of the email 
                    // including any attachments
                    var message = client.GetMessage(i);
                    log.LogInformation(
                        $"Got Message: {message.Subject} - {message.MessageId}");
                    // ********** Save Email To ADefHelpDesk
                    // We need to create a Task and a TaskDetail
                    DTOTask objDTOTask = new DTOTask();
                    DTOTaskDetail objDTOTaskDetail = new DTOTaskDetail();
                    // Create a Task
                    objDTOTask.description = message.Subject;
                    objDTOTask.priority = "Normal";
                    objDTOTask.requesterEmail =
                        message.From.Mailboxes.FirstOrDefault().Address;
                    objDTOTask.requesterName =
                        message.From.Mailboxes.FirstOrDefault().Name;
                    objDTOTask.createdDate = DateTime.Now.ToShortDateString();
                    objDTOTask.requesterUserId = -1;
                    objDTOTask.status = "New";
                    objDTOTask.sendEmails = true;
                    // Create a Task Detail
                    objDTOTaskDetail.taskDetailDescription
                        = $"From {objDTOTask.requesterName} [{objDTOTask.requesterEmail}] ";
                    // Send the message to ADefHelpDesk
                    var response = CreateTicket.CreateADefHelpDeskTicket(
                        objDTOTask,
                        objDTOTaskDetail,
                        message,
                        _APIWebAddress,
                        BearerToken);
                    // Check the result
                    if (response.Result.Success)
                    {
                        log.LogInformation(
                            $"Message Saved: {message.Subject} - {message.MessageId}");
                        // [OPTIONAL] Mark the message for deletion
                        // client.DeleteMessage(i);
                    }
                    else
                    {
                        // There was an error
                        log.LogInformation(
                            $"Error saving message: {response.Result.StatusMessage}");
                    }
                }
                log.LogInformation("client.Disconnect");
                client.Disconnect(true);
            }
        }
    }
}

 

Run The Project

image

Hit F5 to run the project.

image

The Azure CLI will run.

image

As long as the project is running, it will check for emails every 5 minutes (unless you change the timer setting – see the comments in the code on how to do this).

image

If the email MIME type is “text/html” the contents will be attached to the Help Desk Ticket as a .html file. That file will display a download link on the Ticket details in ADefHelpDesk.

image

Otherwise the contents of the email will be saved to the Help Desk Ticket as an .EML file. You can see its contents displayed in ADefHelpDesk, and any attachments in the .EML file will have download links.

 

Links

AdefHelpDesk (Documentation)

Develop Azure Functions using Visual Studio

Microsoft Azure Functions

List of SMTP and POP3 Server (also a list here)

Visual Studio 2017

MailKit

Calling a REST API from .Net Core Including Uploading Files

Retrieving Pop3 Emails Using Azure Functions

Download

The project is available at http://lightswitchhelpwebsite.com/Downloads.aspx

You must have Visual Studio 2017 (or higher) installed to run the code.


Your name:
Gravatar Preview
Your email:
(Optional) Email used only to show Gravatar.
Your website:
Title:
Comment:
Security Code
CAPTCHA image
Enter the code shown above in the box below
Add Comment   Cancel 
Microsoft Visual Studio is a registered trademark of Microsoft Corporation / LightSwitch is a registered trademark of Microsoft Corporation