Lurgle.Alerting - a standardised FluentEmail implementation with extra goodies!

Table of Contents

Another Lurgle

Around the time that I tackled my original Serilog logging implementation, I also looked at our email alerting. Emails can be used for a variety of reasons, and it's not uncommon that they are sent as a simple string that concatenates or formats variables. In this scenario, the emails are typically embedded in the source code and don't exactly lend themselves to easy updates (and aren't too pretty either).

Enter FluentEmail with its templating capabilities. I liked the overall implementation, and the power of Razor templates made a massive difference to how we could approach alerting - and the app I was developing needed a lot of power here. Nowadays FluentEmail also offers Liquid templates using the Fluid project. 

I've tended to treat logging and alerting as having an innate relationship, so when I created Lurgle.Logging and started switching apps over from their inbuilt logging classes to Lurgle.Logging, it seemed apparent that an opportunity existed to give the email code a similar treatment.

Like Serilog, there are a lot of ways to configure FluentEmail. It has quite a lot of options, and you can readily wire it up in any app. And like the logging scenario - email is oft-neglected, yet important, and subject to the same kind of challenges as you move through new applications and multiple developers. In fact, as FluentEmail and the RazorLight library that powers Razor templates had evolved, we had wound up on a much older version that would be challenging to get up to date "in place".

So a common alerting library is useful and avoids the typical pitfalls. Again, like logging, it affords the prospect of both keeping my own app alerting implementations up to date if and when I add new features, as well as providing the ability for all developers in my workplace to use the same standardised implementation. First, some rules to encourage "good" implementation of alerting;

  1. A default "From" address must exist to simplify alerting
  2. A default "To" address must exist to simplify alerting
  3. A "debug" capability must exist that can substitute recipients with the default "To" address to prevent data leakage
  4. A default subject should be specified
  5. An ability to test mail host availability must exist to avoid misconfiguration

and so on.

The result - Lurgle.Alerting

Lurgle alerts

Lurgle.Alerting is a standardised implementation of FluentEmail, which can help to avoid common pitfalls and challenges, with a few extra features. It doesn't replace FluentEmail, but it can help to get you up and running quickly.

It implements several key FluentEmail components:

  • FluentEmail.Liquid
  • FluentEmail.Razor
  • FluentEmail.Smtp
  • FluentEmail.MailKit (default SMTP sender, given that SmtpClient is deprecated)

It also implements;

  • Integrate mail host connectivity check
  • Email validation
  • Optionally retrieve email address configs from a config file
  • Optionally retrieve email template names from a config file

Lurgle an email

One of the most immediate usages for alerting is the ability to tell someone that "something" happened. Lurgle.Alerting aims to make this achievable in as simple a way as possible.

Passing:

Alert.To().Subject("Stuff happened").Send("Send help!");

or

Alert.From().Subject("Stuff happened").Send("Send help!");

will have the same effect - an email from the default From address will be sent to the default To address. That makes it simple to get an alert out to (for example) the team that maintains the app.

Of course, there's plenty of other ways that this could go down. For example, I might want to tell the business and cc the support team;

Alert.To("[email protected]").Cc().Subject"Stuff happened").Send("Send help!");

will do the job.

Maybe we want a different From address?

Alert.From("[email protected]").To("[email protected]").Cc().Subject"Stuff everywhere").Send("Help please!);

Maybe if the app is in debug mode, we want to ensure it doesn't go out to the real address? (NOTE: This was subsequently updated to act globally, as a more rational implementation)

Alert.To("[email protected]", isDebug:true).Cc().Subject"Stuff happened").Send("Send help!");

Maybe I want to reference a key in the app.config for the email address, and give them a formatted name rather than just the email?

Alert.To("BusinessUnitName", "Business Guys", AddressType.FromConfig).Cc().Subject"Stuff happened").Send("Send help!");

Pass a list or array of email addresses and attach a file, list of files, or stream?

Alert.To(myList).Cc().Subject"Stuff happened").Attach(myFile).Attach(myFileList).Attach(myStream).Send("Send help!");

And of course, pass a template.

Alert.To().Subject("Test Liquid Template").SendTemplateFile("Liquid", new
                {
                    Alerting.Config.AppName,
                    Alerting.Config.AppVersion,
                    MailRenderer = Alerting.Config.MailRenderer.ToString(),
                    MailSender = Alerting.Config.MailSender.ToString(),
                    Alerting.Config.MailTemplatePath,
                    Alerting.Config.MailHost,
                    MailTestTimeout = Alerting.Config.MailTestTimeout / 1000,
                    Alerting.Config.MailPort,
                    Alerting.Config.MailUseAuthentication,
                    Alerting.Config.MailUsername,
                    Alerting.Config.MailPassword,
                    Alerting.Config.MailUseTls,
                    MailTimeout = Alerting.Config.MailTimeout / 1000,
                    Alerting.Config.MailFrom,
                    Alerting.Config.MailTo
                });

The above is from the LurgleTest app for Lurgle.Alerting, and passes the config properties to a Liquid template. Razor templates can typically access the namespaces available in your application, so the models can be simpler, but the above is also viable.

Lurgle.Alerting also implements an ability to add inline attachments as a single call, which is convenient for embedding images to your email, using the AttachInline method.

Config a Lurgle

Like Lurgle.Logging, I implement an ability to read the config from the App.Config, but it's not mandatory. You can also call Alerting.SetConfig and use the AlertConfig constructor to pass any property. As with Lurgle.Logging, there are defaults for a number of properties and you can pass in just what's necessary. As an example, in LurgleTest, I switch the config betweeen Razor and Liquid templates:

                Console.WriteLine("Send Razor template ...");
                Alerting.SetConfig(new AlertConfig(Alerting.Config, mailRenderer: RendererType.Razor));
                Alert.To().Subject("Test Razor Template").SendTemplateFile("Razor", new { });
                Console.WriteLine("Send Liquid template ...");
                Alerting.SetConfig(new AlertConfig(Alerting.Config, mailRenderer: RendererType.Liquid));

The prescriptive approach to the FluentEmail implementation means that Lurgle.Alerting compensates by exposing configurability:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings file="C:\Users\mattm\source\repos\Lurgle.Alerting\LurgleTest\secrets.config">
  <add key="AppName" value="Test" />
  <add key="MailRenderer" value="Razor" />
  <add key="MailSender" value="MailKit" />
  <add key="MailTemplatePath" value="" />
  <add key="MailHost" value="mail" />
  <add key="MailPort" value="25" />
  <add key="MailTestTimeout" value="3" />
  <add key="MailUseAuthentication" value="false" />
  <add key="MailUsername" value="" />
  <add key="MailPassword" value="" />
  <add key="MailUseTls" value="true" />
  <add key="MailTimeout" value="60" />
  <add key="MailFrom" value="[email protected]" />
  <add key="MailTo" value="[email protected]" />
  <add key="MailSubject" value="Alert!"/>
</appSettings>
</configuration>

Most of these properties are straightforward enough, but MailTestTimeout bears mention as the property that controls the mail host connectivity test that is performed during initialisation. If it's set to 0, a test won't be performed. Anything higher will cause Lurgle.Alerting to make a TCP connection to the mail host during initialisation; if it fails, it will return an InitResult so you can determine the reason that alerting doesn't occur.

AppName, of course, is also implemented in Lurgle.Logging. It's intended that these are common and available to both libraries. You only need to configure them once, of course. AppVersion is also available for use. These are both special properties that, if not configured, will be determined from the executing assembly.

MailTemplatePath will also be determined from the executing assembly path if not specified. It will default to the Templates folder under this location.

Lurgle that Alert - get Lurgle.Alerting!

Lurgle.Alerting is available from Nuget and the code is on Github.  It is, overall, a simpler implementation than Lurgle.Logging, but it complements it quite well.  You can readily configure either or both of them into your solution, and get going quickly with your alerting needs. You can always directly integrate FluentEmail to your code, but the Lurgle.Alerting implementation might still provide ideas. It does help to avoid a few pitfalls that I've seen with Razor templates, like the missing mshtml.dll error that can arise.

 

Comments

You may also like:

Calculating timeouts with Event Timeout for Seq

We use quite a number of Event Timeout instances in our Seq environment, to detect processes that have not completed in time. The nature of the Seq.App.EventTimeout implementation is one that relies on a timeout in seconds, and this can result in keeping track of quite a few different calculations....

Lurgle.Logging v1.1.14 and Lurgle.Alerting v1.1.9 Released

I've pushed out updates to Lurgle.Logging and Lurgle.Alerting today. The Lurgle.Logging update is minor - I noticed that Log.Add wasn't correctly passing the calling method, source file, and line number. Lurgle.Alerting has received a more substantial update: This helps to make Lurgle.Alerting even more useful and reliable! You can get...

Structured Logging with Seq and Serilog

A few years back, I picked up an old "unloved" business application for document handling, and brought it into the modern era. I completed some work on adding automated OCR, running as a service, and then started to enhance it well beyond its original capabilities, such as moving a manual...