Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Digest mode #87

Closed
wants to merge 41 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
19da9c3
Implement mail host fallback and DNS delivery, fix To address parsing…
MattMofDoom Aug 12, 2021
189f988
Remove extant code from TryDeliver
MattMofDoom Aug 12, 2021
1a45cc3
Improve DnsMailResult logic
MattMofDoom Aug 12, 2021
bffb82a
Retain lastError if not superseded
MattMofDoom Aug 12, 2021
26261af
Always track list of exceptions for logging
MattMofDoom Aug 12, 2021
6c3f9f3
Corrections from running mail host and DNS test cases
MattMofDoom Aug 12, 2021
e695402
Add email priority and priority property mapping
MattMofDoom Aug 13, 2021
abf8d25
Add cc, bcc, and plain text body
MattMofDoom Aug 13, 2021
2a35adc
Add replyTo, improved email logging
MattMofDoom Aug 13, 2021
4d499d1
Merge remote-tracking branch 'upstream/dev' into DeliveryEnhancements
MattMofDoom Aug 17, 2021
4b726fa
Merge with upstream conflicts
MattMofDoom Aug 17, 2021
7b74c41
Handle self comments made on #81
MattMofDoom Aug 17, 2021
c2ad37e
lastError local var no longer needed, update tests to reflect SecureS…
MattMofDoom Aug 17, 2021
67db746
Minor code issues from Resharper
MattMofDoom Aug 17, 2021
42864c6
Only need to parse list of servers once, handle no delivery methods b…
MattMofDoom Aug 17, 2021
c0f9953
Update help text for clarity
MattMofDoom Aug 17, 2021
5396dae
Merge branch 'dev' into DeliveryEnhancements
MattMofDoom Aug 18, 2021
fbf8a4f
Adjust code for upstream changes
MattMofDoom Aug 18, 2021
e7bc50a
GetServerList doesn't need to be public
MattMofDoom Aug 18, 2021
259d7b9
Merge branch 'DeliveryEnhancements' into EnvelopeEnhancements
MattMofDoom Aug 22, 2021
6733a87
Correct merge error and handle null replyTo/cc/bcc, resolve several R…
MattMofDoom Aug 22, 2021
c6ef0ab
Merge remote-tracking branch 'upstream/dev' into DeliveryEnhancements
MattMofDoom Aug 27, 2021
72cec0b
Harmonise #86 with delivery enhancements
MattMofDoom Aug 27, 2021
b85428b
Correct port
MattMofDoom Aug 27, 2021
23bdff3
Merge branch 'DeliveryEnhancements' into EnvelopeEnhancements
MattMofDoom Aug 27, 2021
3a8531d
Reinstate and extend CorrectSecureSocketOptionsAreChosenForPort from #86
MattMofDoom Aug 28, 2021
349ef77
Merge branch 'DeliveryEnhancements' into EnvelopeEnhancements
MattMofDoom Aug 28, 2021
38d0c5a
Improve error/delivery logging for consistency
MattMofDoom Aug 28, 2021
16fca06
Merge branch 'DeliveryEnhancements' into EnvelopeEnhancements
MattMofDoom Aug 28, 2021
3e2e207
Update SeqApp description
MattMofDoom Aug 28, 2021
bb31e99
Merge Seq.App.DigestEmail functionality as optional configuration
MattMofDoom Aug 31, 2021
d300106
Add lastserrver to unhandled mail error
MattMofDoom Aug 31, 2021
ad780f4
Merge branch 'DeliveryEnhancements' into EnvelopeEnhancements
MattMofDoom Aug 31, 2021
57546b3
Merge branch 'EnvelopeEnhancements' into DigestMode
MattMofDoom Aug 31, 2021
78e2044
Make sure we attempt all domains
MattMofDoom Aug 31, 2021
1ad5b7a
Merge branch 'DeliveryEnhancements' into EnvelopeEnhancements
MattMofDoom Aug 31, 2021
41886be
Catch all recipient domains in the envelope for processing
MattMofDoom Aug 31, 2021
05a0b04
Merge branch 'EnvelopeEnhancements' into DigestMode
MattMofDoom Aug 31, 2021
98e6047
Always catch LastError in the Errors list, always set Success
MattMofDoom Aug 31, 2021
2d8fa19
Merge branch 'DeliveryEnhancements' into EnvelopeEnhancements
MattMofDoom Aug 31, 2021
164ea11
Merge branch 'EnvelopeEnhancements' into DigestMode
MattMofDoom Aug 31, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/Seq.App.EmailPlus/DeliveryType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Seq.App.EmailPlus
{
public enum DeliveryType
{
MailHost,
MailFallback,
Dns,
DnsFallback,
HostDnsFallback,
None = -1
}
}
195 changes: 187 additions & 8 deletions src/Seq.App.EmailPlus/DirectMailGateway.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,202 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using DnsClient;
using DnsClient.Protocol;
using MailKit.Net.Smtp;
using MimeKit;

namespace Seq.App.EmailPlus
{
class DirectMailGateway : IMailGateway
{
public async Task SendAsync(SmtpOptions options, MimeMessage message)
static readonly SmtpClient Client = new SmtpClient();
static readonly LookupClient DnsClient = new LookupClient();

public async Task<MailResult> SendAsync(SmtpOptions options, MimeMessage message)
{
if (message == null) throw new ArgumentNullException(nameof(message));
var mailResult = new MailResult();
var type = DeliveryType.MailHost;
var errors = new List<Exception>();
foreach (var server in options.Host)
{
mailResult = await TryDeliver(server, options, message, type);
if (!mailResult.Success)
{
errors.Add(mailResult.LastError);
type = DeliveryType.MailFallback;
}
else
{
break;
}
}

mailResult.Errors = errors;
return mailResult;
}

public async Task<DnsMailResult> SendDnsAsync(DeliveryType deliveryType, SmtpOptions options,
MimeMessage message)
{
var dnsResult = new DnsMailResult();
var resultList = new List<MailResult>();
var lastServer = string.Empty;
var errors = new List<Exception>();
if (message == null) throw new ArgumentNullException(nameof(message));
var type = deliveryType;

try
{
var domains = GetDomains(message).ToList();
var successCount = 0;

foreach (var domain in domains)
{
type = deliveryType;
lastServer = domain;
var mx = await DnsClient.QueryAsync(domain, QueryType.MX);
var mxServers =
(from mxServer in mx.Answers
where !string.IsNullOrEmpty(((MxRecord)mxServer).Exchange)
select ((MxRecord)mxServer).Exchange).Select(dummy => (string)dummy).ToList();
var mailResult = new MailResult();
foreach (var server in mxServers)
{
lastServer = server;
mailResult = await TryDeliver(server, options, message, type);
var lastError = dnsResult.LastError;
errors.AddRange(mailResult.Errors);

dnsResult = new DnsMailResult
{
LastServer = server,
LastError = mailResult.LastError ?? lastError,
Type = type,
};

resultList.Add(mailResult);

if (mailResult.Success)
break;
type = DeliveryType.DnsFallback;
}

if (mailResult.Success)
{
successCount++;
continue;
}

if (dnsResult.LastError != null) continue;
dnsResult.LastError = mxServers.Count == 0
? new Exception("DNS delivery failed - no MX records detected for " + domain)
: new Exception("DNS delivery failed - no error detected");
dnsResult.Success = false;
dnsResult.Errors.Add(dnsResult.LastError);
}

if (!domains.Any())
{
dnsResult.Success = false;
dnsResult.LastError =
new Exception("DNS delivery failed - no domains parsed from recipient addresses");
}

if (successCount < domains.Count)
{
if (successCount == 0)
{
dnsResult.Success = false;
dnsResult.LastError =
new Exception("DNS delivery failure - no domains could be successfully delivered.");
}
else
{
dnsResult.Success = true; // A qualified success ...
dnsResult.LastError =
new Exception(
$"DNS delivery partial failure - {domains.Count - successCount} of {successCount} domains could not be delivered.");
}

dnsResult.Errors.Add(dnsResult.LastError);
}
else
{
dnsResult.Success = true;
}
}
catch (Exception ex)
{
dnsResult = new DnsMailResult
{
Type = type,
LastServer = lastServer,
Success = false,
LastError = ex
};
}

dnsResult.Errors = errors;
dnsResult.Results = resultList;
return dnsResult;
}


public static IEnumerable<string> GetDomains(MimeMessage message)
{
var domains = new List<string>();
foreach (var to in message.To)
{
var toDomain = to.ToString().Split('@')[1];
if (string.IsNullOrEmpty(toDomain)) continue;
if (!domains.Any(domain => domain.Equals(toDomain, StringComparison.OrdinalIgnoreCase)))
domains.Add(toDomain);
}

foreach (var cc in message.Cc)
{
var ccDomain = cc.ToString().Split('@')[1];
if (string.IsNullOrEmpty(ccDomain)) continue;
if (!domains.Any(domain => domain.Equals(ccDomain, StringComparison.OrdinalIgnoreCase)))
domains.Add(ccDomain);
}


foreach (var bcc in message.Bcc)
{
var bccDomain = bcc.ToString().Split('@')[1];
if (string.IsNullOrEmpty(bccDomain)) continue;
if (!domains.Any(domain => domain.Equals(bccDomain, StringComparison.OrdinalIgnoreCase)))
domains.Add(bccDomain);
}

return domains;
}

private static async Task<MailResult> TryDeliver(string server, SmtpOptions options, MimeMessage message,
DeliveryType deliveryType)
{
if (string.IsNullOrEmpty(server))
return new MailResult {Success = false, LastServer = server, Type = deliveryType};
try
{
await Client.ConnectAsync(server, options.Port, options.SocketOptions);
if (options.RequiresAuthentication)
await Client.AuthenticateAsync(options.Username, options.Password);


await Client.SendAsync(message);
await Client.DisconnectAsync(true);

var client = new SmtpClient();

await client.ConnectAsync(options.Host, options.Port, options.SocketOptions);
if (options.RequiresAuthentication)
await client.AuthenticateAsync(options.Username, options.Password);
await client.SendAsync(message);
await client.DisconnectAsync(true);
return new MailResult {Success = true, LastServer = server, Type = deliveryType};
}
catch (Exception ex)
{
return new MailResult {Success = false, LastError = ex, LastServer = server, Type = deliveryType};
}
}
}
}
16 changes: 16 additions & 0 deletions src/Seq.App.EmailPlus/DnsMailResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
// ReSharper disable UnusedAutoPropertyAccessor.Global

namespace Seq.App.EmailPlus
{
public class DnsMailResult
{
public bool Success { get; set; }
public DeliveryType Type { get; set; }
public string LastServer { get; set; }
public Exception LastError { get; set; }
public List<Exception> Errors { get; set; } = new List<Exception>();
public List<MailResult> Results { get; set; } = new List<MailResult>();
}
}
Loading