NServiceBus Mailer

Source
NuGet Package NServiceBus.Mailer (3.x) | License
This is a community maintained project. License and support are independent of Particular Software.
Target NServiceBus Version: 6.x

Enabling

var endpointConfiguration = new EndpointConfiguration("NServiceBusMailSample");
var mailerSettings = endpointConfiguration.EnableMailer();

Usage

In a Handler

public class MyHandler :
    IHandleMessages<MyMessage>
{
    public Task Handle(MyMessage message, IMessageHandlerContext context)
    {
        var mail = new Mail
        {
            To = "to@fake.email",
            From = "from@fake.email",
            Body = "This is the body",
            Subject = "Hello"
        };
        return context.SendMail(mail);
    }
}

SmtpClient factory

The default SmtpClient factory uses the following implementation

new SmtpClient
    {
        EnableSsl = true
    };

This results in the SmtpClient defaulting to reading its settings from the application config.

To configure a custom SmtpClient factory use the following:

var mailerSettings = endpointConfiguration.EnableMailer();
mailerSettings.UseSmtpBuilder(
    buildSmtpClient: () =>
    {
        return new SmtpClient
        {
            EnableSsl = true,
            Port = 1000,
        };
    });

Test/Development SmtpBuilder

For testing and development purposes it can be helpful to route all sent emails to a known directory:

var mailerSettings = endpointConfiguration.EnableMailer();
mailerSettings.UseSmtpBuilder(
    buildSmtpClient: () =>
    {
        var directoryLocation = Path.Combine(Environment.CurrentDirectory, "Emails");
        Directory.CreateDirectory(directoryLocation);
        return new SmtpClient
        {
            DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory,
            PickupDirectoryLocation = directoryLocation
        };
    });

Attachments

For situations where it might not be practical to send binary data as part of messages the attachment finder can be configured as shown below

var mailerSettings = endpointConfiguration.EnableMailer();
mailerSettings.UseAttachmentFinder(
    findAttachments: attachmentContext =>
    {
        var id = attachmentContext["Id"];
        var memoryStream = new MemoryStream(Encoding.ASCII.GetBytes("Hello"));
        var attachment = new Attachment(memoryStream, "example.txt", "text/plain");
        var attachments = new List<Attachment> { attachment };
        return Task.FromResult<IEnumerable<Attachment>>(attachments);
    },
    cleanAttachments: attachmentContext =>
    {
        // Attachment cleanup can be performed here
        return Task.CompletedTask;
    });

The attachment finder needs to be able to determine the unique attachment identification based on the context passed to the finder.

Pass an AttachmentContext when sending the email

Pass an AttachmentContext when calling SendMail. The AttachmentContext should contain enough information to derive how to find and return the attachments for the email.

public Task Handle(MyMessage message, IMessageHandlerContext context)
{
    var mail = new Mail
    {
        To = "to@fake.email",
        From = "from@fake.email",
        Body = "This is the body",
        Subject = "Hello",
        AttachmentContext = new Dictionary<string, string>
        {
            { "Id", "fakeEmail" }
        }
    };
    return context.SendMail(mail);
}

Error handling

Retrying email is difficult because when sending an email to multiple addresses a subset of those addresses may return an error. In this case, re-sending to all addresses would result in some addresses receiving the email multiple times.

When all addresses fail

When the communication with the mail server cannot be initiated, or the server returns an error that indicates all addresses have failed this exception will bubble up to NServiceBus. This will result in falling back on the standard NServiceBus retry logic.

When a subset of addresses fail

This will most likely occur when there is a subset of invalid addresses however there cases where the address can fail once and succeed after a retry. Have a look at SmtpStatusCode for the possible error cases.

In this scenario, it is not valid to retry the message since it would result in the message being resent to all recipients. It is also flawed to resend the verbatim email to the subset of failed addresses as this would effectively exclude them from some of the recipients in the conversation.

So the approach taken is to forward the original message to the failed recipients after prefixing the body with the following text.

This message was forwarded due to the original email failing to send
-----Original Message-----
To: XXX
CC: XXX
Sent: XXX

While this is a little complex, it achieves the desire of letting the failed recipients receive the email contents while also notifying them that there is a conversation happening with other recipients. It also avoids spamming the other recipients.

Samples


Last modified