From cdeddf3279acb65641fb5f2dee79d21e2ac49ca8 Mon Sep 17 00:00:00 2001 From: Paul Hagspiel Date: Thu, 25 Jan 2024 14:22:34 +0000 Subject: [PATCH] improvement: Headers in SendEmail don't overwrite default headers anymore; added ReplyTo --- docs/Email/README.md | 4 ++-- src/Directory.Build.props | 2 +- src/Email/src/EmailHeaders.cs | 4 ++-- src/Email/src/SendEmail.cs | 39 ++++++++++++++++++++------------ src/Email/test/SendEmailTests.cs | 27 +++++++++++++++++----- 5 files changed, 51 insertions(+), 25 deletions(-) diff --git a/docs/Email/README.md b/docs/Email/README.md index f632260..ae66f58 100644 --- a/docs/Email/README.md +++ b/docs/Email/README.md @@ -47,5 +47,5 @@ container.Collections.Append() ## Headers -No special Headers are sent per default, however with the `Headers` overload on `SendEmail`, `MimeKit.Header`s can be added to the email (overrides all Headers defined in `EmailOptions.DefaultHeaders`). With `EmailOptions.DefaultHeaders` default headers can be set for all emails. Predefined sets of headers can be found in `EmailHeaders`: -- `DiscourageAutoReplies` includes [`Precedence:list`](https://www.rfc-editor.org/rfc/rfc3834), [`AutoSubmitted:generated`](https://www.rfc-editor.org/rfc/rfc3834), and [`X-Auto-Response-Suppress:All`](https://learn.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxcmail/e489ffaf-19ed-4285-96d9-c31c42cab17f). They discourage email servers to sent auto replies. \ No newline at end of file +No special Headers are sent per default, however with the `Headers` parameter on `SendEmail`, `MimeKit.Header`s can be added to the email (overrides all Headers defined in `EmailOptions.DefaultHeaders`). With `EmailOptions.DefaultHeaders` default headers can be set for all emails. Predefined sets of headers can be found in `EmailHeaders`: +- `DiscourageAutoReplies` includes [`Precedence:list`](https://www.rfc-editor.org/rfc/rfc3834#section-3.1.8), [`AutoSubmitted:generated`](https://www.rfc-editor.org/rfc/rfc3834#section-3.1.7), and [`X-Auto-Response-Suppress:All`](https://learn.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxcmail/e489ffaf-19ed-4285-96d9-c31c42cab17f). They discourage email servers to sent auto replies. diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 2adb988..f65265e 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,6 +1,6 @@ - 7.3.0 + 7.4.0 net7.0 diff --git a/src/Email/src/EmailHeaders.cs b/src/Email/src/EmailHeaders.cs index 534a12a..db5a38f 100644 --- a/src/Email/src/EmailHeaders.cs +++ b/src/Email/src/EmailHeaders.cs @@ -9,9 +9,9 @@ public static class EmailHeaders { public static readonly IReadOnlyDictionary DiscourageAutoReplies = new Dictionary { - // https://www.rfc-editor.org/rfc/rfc3834 RFC3834 Section 2 §1 + // https://www.rfc-editor.org/rfc/rfc3834#section-3.1.8 RFC3834 Section 3.1.8 [HeaderId.Precedence.ToHeaderName()] = "list", - // https://www.rfc-editor.org/rfc/rfc3834 RFC3834 Section 2 §8 + // https://www.rfc-editor.org/rfc/rfc3834#section-3.1.7 RFC3834 Section 3.1.7 [HeaderId.AutoSubmitted.ToHeaderName()] = "generated", // https://learn.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxcmail/e489ffaf-19ed-4285-96d9-c31c42cab17f MS-OXCMAIL Section 2.2.3.2.14 ["X-Auto-Response-Suppress"] = "All" diff --git a/src/Email/src/SendEmail.cs b/src/Email/src/SendEmail.cs index cef3630..b91ccdd 100644 --- a/src/Email/src/SendEmail.cs +++ b/src/Email/src/SendEmail.cs @@ -11,15 +11,16 @@ namespace Fusonic.Extensions.Email; /// /// Sends an email in the background. /// -/// Email-address of the recipient -/// Display name of the recipient -/// Culture to render the email in +/// Email-address of the recipient. +/// Display name of the recipient. +/// Culture to render the email in. /// View model for the email. The model must have an [EmailView]-attribute, providing the path to the email to render (cshtml). /// Subject key to get the subject of the email from the ViewLocalizer. If null, the SubjectKey from the EmailViewAttribute will be used. /// Email-address of the BCC recipient. Optional. /// Attachments for the email. /// String formatting parameters for the translated subject. subject = string.Format(subject, SubjectFormatParameters) -/// Adds the specified Headers to the email and overrides all default headers from the . +/// Adds the specified headers to the email and overrides the default headers from the . +/// Sets the value as the Reply-To header in the email. public record SendEmail( string Recipient, string RecipientDisplayName, @@ -29,7 +30,8 @@ public record SendEmail( string? BccRecipient = null, Attachment[]? Attachments = null, object[]? SubjectFormatParameters = null, - IReadOnlyDictionary? Headers = null) : ICommand + IReadOnlyDictionary? Headers = null, + string? ReplyTo = null) : ICommand { [OutOfBand] public class Handler : AsyncRequestHandler, IAsyncDisposable @@ -63,21 +65,19 @@ protected override async Task Handle(SendEmail request, CancellationToken cancel Subject = subject }; - var headers = request.Headers ?? emailOptions.DefaultHeaders; - - if (headers != null) + if (!string.IsNullOrWhiteSpace(request.BccRecipient)) { - foreach (var (field, value) in headers) - { - message.Headers.Add(field, value); - } + message.Bcc.Add(new MailboxAddress(request.BccRecipient, request.BccRecipient)); } - if (!string.IsNullOrWhiteSpace(request.BccRecipient)) + if (!string.IsNullOrWhiteSpace(request.ReplyTo)) { - message.Bcc.Add(new MailboxAddress(request.BccRecipient, request.BccRecipient)); + message.ReplyTo.Add(new MailboxAddress(string.Empty, request.ReplyTo)); } + SetHeaders(emailOptions.DefaultHeaders); + SetHeaders(request.Headers); + try { await smtpClient.SendMailAsync(message); @@ -86,6 +86,17 @@ protected override async Task Handle(SendEmail request, CancellationToken cancel { await DisposeStreams(); } + + void SetHeaders(IReadOnlyDictionary? headers) + { + if (headers == null) + return; + + foreach (var (field, value) in headers) + { + message.Headers[field] = value; + } + } } private async Task GetMessageBody(string htmlBody, Attachment[]? attachments, CancellationToken cancellationToken) diff --git a/src/Email/test/SendEmailTests.cs b/src/Email/test/SendEmailTests.cs index 5cf8d15..d82b927 100644 --- a/src/Email/test/SendEmailTests.cs +++ b/src/Email/test/SendEmailTests.cs @@ -148,25 +148,40 @@ public async Task SendEmail_DefaultHeadersAdded() } [Fact] - public async Task SendEmail_DefaultHeadersOverridden() + public async Task SendEmail_DefaultHeadersOverriddenIfSetTwice() { Fixture.SmtpServer!.ClearReceivedEmail(); var options = GetInstance(); - options.DefaultHeaders = new Dictionary { ["my-header"] = "value" }; + options.DefaultHeaders = new Dictionary { ["replaced"] = "value", ["default"] = "value" }; var model = new SendEmailTestEmailViewModel { SomeField = "Some field." }; - await SendAsync(new SendEmail("recipient@fusonic.net", "The Recipient", new CultureInfo("de-AT"), model, Headers: new Dictionary { ["new-header"] = "new-value" })); + await SendAsync(new SendEmail("recipient@fusonic.net", "The Recipient", new CultureInfo("de-AT"), model, Headers: new Dictionary { ["replaced"] = "new-value" })); Fixture.SmtpServer.ReceivedEmailCount.Should().Be(1); var email = Fixture.SmtpServer.ReceivedEmail.Single(); - email.Headers.AllKeys.Should().NotContain("my-header"); - email.Headers.AllKeys.Should().Contain("new-header"); - email.Headers["new-header"].Should().Be("new-value"); + email.Headers.AllKeys.Should().Contain("replaced"); + email.Headers["replaced"].Should().Be("new-value"); + email.Headers.AllKeys.Should().Contain("default"); + email.Headers["default"].Should().Be("value"); } + [Fact] + public async Task SendEmail_ReplyToAdded() + { + Fixture.SmtpServer!.ClearReceivedEmail(); + + var model = new SendEmailTestEmailViewModel { SomeField = "Some field." }; + await SendAsync(new SendEmail("recipient@fusonic.net", "The Recipient", new CultureInfo("de-AT"), model, ReplyTo: "reply@mail.com")); + + Fixture.SmtpServer.ReceivedEmailCount.Should().Be(1); + var email = Fixture.SmtpServer.ReceivedEmail.Single(); + + email.Headers.AllKeys.Should().Contain("Reply-To"); + email.Headers["Reply-To"].Should().Be("reply@mail.com"); + } [Fact] public async Task SendEmail_InvalidBccEmailAddress_ThrowsException()