Dec
24
Written by:
Michael Washington
12/24/2018 6:34 PM
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.
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
You can download and install ADefHelpDesk from the site at: http://ADefHelpDesk.com.
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.
You will need to copy the information on the Connection Information tab to use later.
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
Open Visual Studio.
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).
Select Azure Functions V2 (.Net Core) and the Timer trigger.
The project will be created.
Rename the Function1.cs file to Pop3ADefHelpDeskEmails.cs.
Open local.settings.json.
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
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…
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.
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.
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.
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
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
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
Hit F5 to run the project.
The Azure CLI will run.
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).
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.
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.