NServiceBus Mailer

Project Hosting
NuGet Package NServiceBus.Mailer (3.x) | License
This is a community run 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);
    }
}

In a Saga

public class MySaga :
    Saga<MySaga.SagaData>,
    IAmStartedByMessages<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 construction

By default the following SmtpClient construction is used the code

new SmtpClient
    {
        EnableSsl = true
    };

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

Configure custom SmtpClient construction using 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 rout all email 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

Since it is not practical to send binary data as part of messages there is an alternative mechanism.

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;
    });

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 due to the fact that 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

This case is when there is a generic exception talking to the mail server or the server returns an error that indicates all addresses have failed.

This is handling by letting the exception bubble 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 desired 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