Email Plugin

The Email Plugin is a tool designed to automate the process of sending email notifications to users who subscribe to specific events. This guide will walk you through the steps to configure the Email Plugin for your Notification Service.

Note: [5.3_25.12] Email Plugin support sending html content. In case the receiver does not support html format, plain text message is generated from the content.

How to use

Configuring the Notification Plugin

For the configuration, there will be seven available parameters which will be configured in theNotification Plugin table in the ConfigurationString section. Each parameter will be separated by a semi-colon ( ; ). Keywords are not case sensitive, values are.

Keyword

Short

Description

ServerAddress

server

The TCP/IP address of the SMTP server

ServerPort (Optional)

port, p

Port for the SMTP server. Default: 587

EmailAddress

email, e

Email address used for sending the notification

Credential

cred

Windows Credential name containing the username/password for the SMTP server. The credential's type must be generic and not windows. Notes: Make sure the credential is on the same account that the service is running. To add credential by command line: cmdkey /generic:[Credential] /user:[Username] /pass:[Password]. To add user credential into vault entry: %app_root%\Config\FeatureInstall\APP_TransferVaultEntries.bat /name "[Credential]"

SenderName

name, n

The name to be set up as the sender of the mail

IgnoreCertError
(Optional)

i

Turn 'True' to ignore certificate errors and log certificate information.
Default value: False

DisableAuth
(Optional)

da

Turn 'True' to disable authentication
Default value: False

TraceLevel (Optional) [from version 5.3_25.01][from version 5.3_25.01]

tracelevel, tl

Define trace level for logging. Default value is 3. (Off = 0, Error = 1, Warning = 2, Info =3, Verbose =4, Very Verbose = 5)

Server Port 587 is recommended as the modern and secure standard for email submission. It supports authentication and encryption through STARTTLS, ensuring that email transmissions are protected.

Below is an example of how the configuration string might look:

An example for the configuration of email plugin

An example of the configuration string for Notification Plugin.

ServerAddress=smtp-mail.outlook.com;ServerPort=587;Credential=NotificationService-Test;SenderName=Tester;

Configuring the Notification Subscription

In the Notification Subscription section, the ConfigurationString will contain a list of email addresses that should receive notifications. Each email address should be separated by a semi-colon.

An example of the configuration string for the Notification Subscription.

An example of the configuration string for the Notification Subscription.

Source Codes:

namespace ABB.Vtrin.NotificationService
{
	public class NotificationServicePlugin_Email : INotificationServicePlugin
	{
		private static 	readonly int								      mDefaultTraceLevel=(int)System.Diagnostics.TraceLevel.Info;
		private static 	readonly Diagnostics.cTraceSwitch	mLog							= new("EmailPlugin", string.Empty, "Info");
		private const						 int											mDefaultPort			=587;
		private					readonly cEmailConfig						  mConfiguration;
		private class cEmailAccount
		{
			public readonly string?                       Username;
			public readonly System.Security.SecureString? Password;
			public cEmailAccount(string? username, System.Security.SecureString? password)
			{
				Username = username;
				Password = password;
			}

		}
		private class cEmailConfig
		{
			public readonly string	ServerAddress;
			public readonly string  SendingEmailAddress;
			public readonly string	ServerPort;
			public readonly string	Credential;
			public readonly string	SenderName;
			public readonly bool		IgnoreCertError;
			public readonly bool		DisableAuth;
			public readonly int		  TraceLevel;
			public cEmailConfig(string serveraddress, string sendingemailaddress, string serverport, string credential, string sendername, bool ignorecerterror, bool disableauth, int tracelevel)
			{
				ServerAddress		 		= serveraddress;
				SendingEmailAddress = sendingemailaddress;
				ServerPort			  	= serverport;
				Credential			  	= credential;
				SenderName			  	= sendername;
				IgnoreCertError	  	= ignorecerterror;
				DisableAuth			  	= disableauth;
			}

			public static cEmailConfig GetConfiguration(string configurationstring)
			{
				string serveraddress			 = string.Empty;
				string sendingemailaddress = string.Empty;
				string serverport			     = string.Empty;
				string credential			     = string.Empty;
				string sendername			     = string.Empty;
				bool	 ignorecerterror	   = false; //Defaut value "false"
				bool   disableauth			   = false; //Default value "false"
				int    tracelevel          = NotificationServicePlugin_Email.mDefaultTraceLevel;
				
				var parts = configurationstring.Split(';', System.StringSplitOptions.RemoveEmptyEntries);
				foreach(string part in parts)
				{
					var keyvalue = part.Split('=');
					if(keyvalue.Length == 2)
					{							
						var key=keyvalue[0].Trim().ToLower();
						var value=keyvalue[1].Trim();
						if(string.IsNullOrEmpty(key) || string.IsNullOrEmpty(value))
						{
							mLog.LogError($"Cannot read configuration string '{part}'. Key or Value is missing.");
							continue;
						}
						switch (key) 
						{
							case "serveraddress":
							case "server":
								serveraddress=value;
								break;
							case "emailaddress":
							case "email":
							case "e":
								sendingemailaddress=value;
								break;
							case "serverport":
							case "port":
							case "p":
								serverport=value;
								break;
							case "credential":
							case "cred":
								credential=value;
								break;
							case "sendername":
							case "name":
							case "n":
								sendername=value;
								break;
							case "ignorecerterror":
							case "i":
								ignorecerterror=Utils.ParseBool(value);
								break;
							case "disableauth":
							case "da":
								disableauth=Utils.ParseBool(value);
								break;
							case "tracelevel":
							case "tl":
								tracelevel=Utils.ParseInt(value, tracelevel);
								break;
							default:
								mLog.LogError($"Unrecognized keyword '{key}'");
								break;
						}
					} else
					{
						mLog.LogError($"Check configuration string: '{part}'");
					}
				}

				mLog.LogInfo(
					$"""
					Email Plugin Configuration:
					- Server Address		: {serveraddress}
					- Email Address      : {sendingemailaddress}
					- Server Port			: {serverport}
					- Credential			: {credential}
					- Sender Name			: {sendername}
					- Ignore Cert Error	: {ignorecerterror}
					- Disable Auth			: {disableauth}
					- Trace Level			: {tracelevel}
					""");

				return new cEmailConfig(serveraddress, sendingemailaddress,serverport, credential, sendername, ignorecerterror, disableauth, tracelevel);
			}
		}
		
		public NotificationServicePlugin_Email(string configuration)
		{
			mConfiguration = cEmailConfig.GetConfiguration(configuration);
			mLog.IntLevel=mConfiguration.TraceLevel;			
		}

		public void HandleMessage(string title, string message, string configurationstring)
		{
			using(var client = new MailKit.Net.Smtp.SmtpClient())
			{
				mLog.LogVerbose("Handling message...");
				try
				{					 
					if(mConfiguration.IgnoreCertError)
					{
						client.ServerCertificateValidationCallback = (sender, cert, chain, errors) => mCheckingCertificate(sender, cert, errors);
					}
					var credential = mFetchCredential(mConfiguration.Credential);
					if(credential is null)
					{
						mLog.LogError($"Failed to fetch credential: '{mConfiguration.Credential}'");
						return;
					}
					if(credential.Username is null)
					{
						mLog.LogError($"UserName in credential '{mConfiguration.Credential}' is null");
						return;
					}
					System.Security.SecureString? password = credential.Password;
					var networkcredential = new System.Net.NetworkCredential(credential.Username, password);
					string[] addresseslist = mGetEmailAddress(configurationstring);
					var connection = mConnect(client, mConfiguration.ServerAddress, mConfiguration.ServerPort, networkcredential, mConfiguration.DisableAuth);
					if(connection)
					{
						foreach(string address in addresseslist)
						{
							try
							{
								mSendMessage(client, title, message, mConfiguration.SendingEmailAddress, mConfiguration.SenderName, address);
							} catch(System.Exception ex)
							{
								mLog.LogError("Couldn't send the message: " + ex);
							}
						}
					}
				} catch(System.Exception e)
				{
					mLog.LogError("Couldn't complete, an exception occured: " + e);
				}
			}
		}
		private static bool mCheckingCertificate(object sender, System.Security.Cryptography.X509Certificates.X509Certificate? cert, System.Net.Security.SslPolicyErrors errors)
		{
			if (cert is not null)
			{			
				mLog.LogInfo("Server Certificate \nSender: " + sender + "\nCertificate Subject: " + cert.Subject + "\nCertificate Issuer: " + cert.Issuer + "\nError: " + errors);
			}
			return true;
		}

		protected static int mParsePort(string p)
		{			
			if(string.IsNullOrEmpty(p)) 
			{ 
				return mDefaultPort;
			}
						
			if(!int.TryParse(p, out var port))
			{
				mLog.LogError($"Couldn't parse port option: '{p}' to an integer. Returning default port: {mDefaultPort}.");
				return mDefaultPort;
			}
			else
				return port;
		}		

		private static bool mConnect(MailKit.Net.Smtp.SmtpClient client, string serveraddress, string serverport, System.Net.NetworkCredential networkcredential, bool disableauth)
		{
			try
			{
				int port = mParsePort(serverport);
				client.Connect(serveraddress, port);
				if(!disableauth)
				{
					client.Authenticate(networkcredential);
				}
				if(client.IsConnected)
				{
					mLog.LogInfo("Connected.");
				}
				else
				{
					mLog.LogInfo("Not connected, giving up...");
					return false;
				}
				return true;
			} catch(System.Exception ex)
			{
				mLog.LogError("Couldn't connect, an exception occured: " + ex);
				return false;
			}
		}

		private static void mSendMessage(MailKit.Net.Smtp.SmtpClient client, string title, string message, string sendingemailaddress, string username, string receivingemailaddress)
		{
			var sentmsg = new MimeKit.MimeMessage();
			sentmsg.From.Add(new MimeKit.MailboxAddress(username, sendingemailaddress));
			sentmsg.To.Add(MimeKit.MailboxAddress.Parse(receivingemailaddress));
			sentmsg.Subject = title;
			sentmsg.Body = new MimeKit.TextPart("plain") {
				Text = message,
			};
			mLog.LogInfo("Sending FROM: '" + sendingemailaddress.ToString() +
									  "', TO: '" + receivingemailaddress.ToString() +
									  "', SUBJECT: '" + title +
									  "', BODY: '" + message + "'.");
			string serverresponse = client.Send(sentmsg);
			mLog.LogInfo("Server's response: " + serverresponse);
		}

		private static cEmailAccount? mFetchCredential(string credential)
		{
			mLog.LogVerbose($"Fetching credential: {credential}");
			try
			{
				var cred = ABB.Vtrin.cDataLoader.cCredential.Read(credential);
				if(cred is not null)
				{
					mLog.LogVerbose($"Succesfully fetched credential: '{credential}'.");
					return new cEmailAccount(cred.UserName, cred.Password);
				} else
				{
					mLog.LogVerbose($"Fetching credential '{credential}' returned NULL.");
				}
			} catch(System.Exception ex)
			{
				mLog.LogError($"Exception raised when fetching credential '{credential}'. Message: {ex.Message}." );				
			}
			return null;
		}

		private static string[] mGetEmailAddress(string emaillists)
		{
			string[] mails = emaillists.Split(new char[] { ';' }, System.StringSplitOptions.RemoveEmptyEntries);
			return mails;
		}
	}
}