Skip to content

MailKit conversion#78

Closed
MattMofDoom wants to merge 14 commits into
datalust:devfrom
MattMofDoom:dev
Closed

MailKit conversion#78
MattMofDoom wants to merge 14 commits into
datalust:devfrom
MattMofDoom:dev

Conversation

@MattMofDoom
Copy link
Copy Markdown
Contributor

Hey @nblumhardt,

This is the initial MailKit conversion that we discussed in #77 (I removed the SSL validation suppression option that I'd implemented).

I noted that we can make use of async methods for MailKit so I've updated EmailPlus to use onasync, and provided opportunity to handle unsuccessful emails in future (eg. the fallback mailhost idea I mentioned) by implementing a MailResult class.

I used a couple of idea from FluentEmail wrt an 'Options' class (SmtpOptions) and left a couple of unused properties that would likely be used for some of the ideas from #77.

I also ported over the $EventUri template that we added to Seq.App.Opsgenie as a nicety, and updated Handlebars.NET to the latest build (from v2.0.8 to v2.0.9).

As I was doing this I could envisage how we would go about implementing a number of the ideas from my list, but as agreed, I started with MailKit 😁

Cheers,

Matt

Copy link
Copy Markdown
Member

@nblumhardt nblumhardt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great; SmtpOptions is a nice improvement. Had some thoughts and queries inline. Cheers!

Comment thread src/Seq.App.EmailPlus/EmailApp.cs Outdated
var client = new SmtpClient(Host, Port ?? 25) {EnableSsl = EnableSsl ?? false};
if (!string.IsNullOrWhiteSpace(Username))
client.Credentials = new NetworkCredential(Username, Password);
var result = await _mailGateway.Send(_client, _options, new MimeMessage(new List<InternetAddress> {MailboxAddress.Parse(From)},
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since result is unused, we'll no longer get errors logged to the app's diagnostic stream when sending fails. Perhaps until we've looked into the fallback server idea, we should just keep throwing exceptions on failure, here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. I was planning to log errors as part of this but didn't implement a throw in the interim

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated - if !result.Success, we throw result.Errors.

Comment thread src/Seq.App.EmailPlus/EmailApp.cs Outdated
client.Credentials = new NetworkCredential(Username, Password);
var result = await _mailGateway.Send(_client, _options, new MimeMessage(new List<InternetAddress> {MailboxAddress.Parse(From)},
new List<InternetAddress> {MailboxAddress.Parse(to)}, subject,
(new BodyBuilder() {HtmlBody = body}).ToMessageBody()));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we no longer set credentials for the outgoing mail server

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nblumhardt We do, but it's in the SmtpOptions that are set during OnAttached.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's set on _options, but never passed to the client.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, crap. Will fix.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing code found down the back of the sofa

{ "$ServerUri", host.BaseUri }
{ "$ServerUri", host.BaseUri },
// Note, this will only be valid when events are streamed directly to the app, and not when the app is sending an alert notification.
{ "$EventUri", string.Concat(host.BaseUri, "#/events?filter=@Id%20%3D%20'", evt.Id, "'&amp;show=expanded") }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😎

Comment thread src/Seq.App.EmailPlus/SmtpOptions.cs Outdated
public bool UseSsl { get; set; } = false;
public bool RequiresAuthentication { get; set; } = false;
public string PreferredEncoding { get; set; } = string.Empty;
public SecureSocketOptions? SocketOptions { get; set; }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SocketOptions and UseSsl overlap, here. Perhaps we knock out UseSsl, make SocketOptions non-nullable, and set it to SslOnConnect when using SSL, or StartTlsWhenAvailable otherwise? (This is how MailKit interprets Boolean useSsl behind the scenes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nblumhardt I think you're right. I'll take a look.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SocketOptions is now the only way to fly

Comment thread src/Seq.App.EmailPlus/SmtpOptions.cs Outdated
public string Password { get; set; } = string.Empty;
public bool UseSsl { get; set; } = false;
public bool RequiresAuthentication { get; set; } = false;
public string PreferredEncoding { get; set; } = string.Empty;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid confusion and an accumulation of unimplemented bits and pieces, it's probably best to remove the unused properties here until we've worked through the features they are used by

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the unused bitses


[Fact]
public void ToAddressesAreTemplated()
public async void ToAddressesAreTemplated()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should return async Task here, or xUnit can't track completion

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now returning async Task.

client.Send(message);
try
{
await client.ConnectAsync(options.Server, options.Port, options.UseSsl);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we connect and disconnect here, any reason not to just create and release a new SmtpClient each time?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah agree. MailKit initialises quite quickly with minimal overhead and is Async to boot, so that shouldn't be a problem. There are some cases where it's more efficient to hold a single instance of a client (not MailKit) to reduce initialisation overhead, but this was a change before I really got deep into the conversion and proved unnecessary.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now implementing SmtpClient each time. This is now redundant in EmailApp and have removed it from IMailGateway/DirectMailGateway/etc

Comment thread src/Seq.App.EmailPlus/EmailApp.cs Outdated
readonly ConcurrentDictionary<uint, DateTime> _lastSeen = new ConcurrentDictionary<uint, DateTime>();
readonly Lazy<Template> _bodyTemplate, _subjectTemplate, _toAddressesTemplate;
private SmtpClient _client = new SmtpClient();
private SmtpOptions _options = new SmtpOptions();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missed these - should be readonly, no explicit private

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done - no need for OnAttached now.

@MattMofDoom
Copy link
Copy Markdown
Contributor Author

@nblumhardt Might need a little bit more work to ensure that the to address parse doesn't fail for comma-separated strings.

I was working through mailhost fallbacks and possibly DNS delivery so will fix as part of that.

@MattMofDoom
Copy link
Copy Markdown
Contributor Author

@nblumhardt This will need a bit of battle testing to make sure fallbacks and DNS delivery work as expected, but I'm quietly optimistic.

@MattMofDoom
Copy link
Copy Markdown
Contributor Author

@nblumhardt Okay, I've now successfully run through unit tests for delivering via mailhost and delivering via DNS, and made corrections based on the result. I left the test cases that I used commented out for your benefit, although obviously they'll be removed.

In any case - the code is working for both scenarios 😀

@MattMofDoom
Copy link
Copy Markdown
Contributor Author

@nblumhardt Reviewed the rest of the ideas from #77 and pulled in the following enhancements;

  • Priority added, with optional priority mapping ... this is the only property mapping that seems to make sense per list below
  • CC, BCC, ReplyTo added as optional envelope options
  • Added optional plan text body template - if not specified, it won't be used
  • Email logging added for sent and error scenarios ... this is probably the most useful debug logging that could be added

Rejected or parked:

  • Mapping to/cc/bcc properties rejected, since we can use Handlebars to pull in properties.
  • Parking attachment of event json - we have an ability to link EventUri within the Handlebars template
  • Parking event tags since they don't fit an SMTP scenario
  • Validation of email addresses parked - we do parse them to MailKit internet addresses so this may fit the bill

This should make Email Plus more fully featured without overburdening it with unnecessary drudge.

@nblumhardt
Copy link
Copy Markdown
Member

Thanks Matt! Just a heads up that we're deep in some feature work this week and may not emerge for a few more days yet - will get some 👀 back on this ASAP 😅

RE how we shape up PRs - it's often much faster to discuss/review/merge small PRs one at a time; adding features to one large PR can stall the process sometimes.

If you're able to force push the older commit with just the mailkit conversion, but send the remaining commits in a follow-up PR, that would be optimal. Can help to figure out a plan next week, either way.

Have a great weekend!

@MattMofDoom
Copy link
Copy Markdown
Contributor Author

@nblumhardt Fair enough on all points 😊 Will see what I can do.

Have a great weekend yourselves!

@MattMofDoom
Copy link
Copy Markdown
Contributor Author

Closing PR in favour of #79 ... will send the remaining commits in a follow-up PR as discussed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants