How to debug emails in your application with Sendgrid and Mailtrap.io

Email testing is always a pain. One of the “OOOPS” moments I had with email was when on testing instance of the application I started resetting user password. And the application started sending out actual emails to users. People did get “Your password reset link is here” and pointing to the testing instance of the application.

At that point I started wondering how can I isolate the emailing in production and emailing in dev and test environments. Production system must send out real emails to real users. Emails issued by testing and development installation must not send out emails to users, but rather have them collected somewhere. For a long time I had very basic configuration that checks if we are not in production environment, do not send emails at all. That worked fine for some time, but I lacked ability to check if emails were sent out or not.

There are many solutions for fake SMTP servers for dev-machines: you set up SMTP server on your machine, point your smpt credentials to this server and all emails are collected on that fake server. That works if you are just a one-man developer. If you have a team of devs, every single one of them must install this server, and for their home machines. A bit too much work for my liking. Also this approach does not work for testing environment – when the application is deployed to a web-server identical to production. And actual users are having a go on the system. Where do you install that fake server now? How can you show email messages to the user, without sending them?

Recently I came across Mailtrap.io. Mailtrap is hosted fake SMTP server. When you send email to their SMTP, it is not sent out anywhere, it is collected in inbox and can be validated against. Because this service is hosted, you don’t have to install anything, just re-point your SMTP credentials. And this works for developers on their local machines, for testers, for customers checking out testing environment.

Mailtrap has a descent free tier that allows me to have one fake inbox and one user to access the inbox. That is good enough just now. I’m not building email-heavy application and the only emails I’m sending are for password reset and for email confirmations. I’ve used this service for a month now and pretty happy with the way it works. As soon as I need more than a few emails per day, I’ll subscribe to their paid tier.

We use SendGrid for all our emailing needs and it works great for us: official C# client is provided via nuget, emails are sent out via API requests, not via SMTP (we had issues with network ports in our firewall).

We isolate all email sending into EmailService class which looks similar to this:

public class EmailService : IEmailService
{
    private readonly ITransport transportSmtp;

    public EmailService(ITransport transportSmtp)
    {
        this.transportSmtp = transportSmtp;
    }


    public Task SendEmail(MailAddress source, MailAddress destination, string htmlContent, string subject)
    {
        var message = new SendGridMessage();
        message.Html = htmlContent;
        message.From = source;
        message.To = new[] { destination };
        message.Subject = subject;

        return await transportSmtp.DeliverAsync(message);
    }        
}

Note that ITransport object is injected into this service. ITransport is SendGrid interface for email delivery service. It has 2 methods: Deliver(SendGrid message) and DeliverAsync(SendGrid message). And it does what it says on the tin – delivers email messages.

Now, for a long time my DI container (Autofac) was configured to create native SendGrid transport and pass it down to EmailService class, and if not in production environment, replace ITransport with NullTransport that does not do anything.

public class SmtpModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        // Create an SMTP transport for sending email.
        builder.Register(c => GetTransport())
            .As<ITransport>()
            .InstancePerLifetimeScope();

        builder.Register<EmailService>().As<IEmailService>();

        base.Load(builder);
    }


    public static ITransport GetTransport()
    {
        if (/* check if not production. Usually a flag in web.config */)
        {
            return new NullTransport();
        }
        var networkCredential = new NetworkCredential("username", "password");

        var transportWeb = new SendGrid.Web(networkCredential);

        return transportWeb;
    }
}    

public class NullTransport : ITransport
{
    public void Deliver(SendGrid message)
    {
        // do nothing
    }

    public Task DeliverAsync(SendGrid message)
    {
        // do nothing
        return Task.FromResult(0);
    }
}

Now with MailTrap.io I can replace NullTransport for MailtrapTransport:

public class MailtrapTransport : ITransport
{
    public void Deliver(ISendGrid sendGridMessage)
    {
        var client = new SmtpClient
                     {
                         Host = "mailtrap.io",
                         Port = 123456,
                         Credentials = new NetworkCredential("user", "pass"),
                         EnableSsl = true,
                     };

        var mail = new MailMessage(sendGridMessage.From, sendGridMessage.To.First())
                   {
                       Subject = sendGridMessage.Subject,
                       Body = sendGridMessage.Html,
                       IsBodyHtml = true,
                   };

        client.Send(mail);
    }


    public Task DeliverAsync(ISendGrid message)
    {
        return Task.Factory.StartNew(() => Deliver(message));
    }
}

If you are not using DI, similar result can be achieved just in your EmailService:

    public void SendEmail(MailAddress source, MailAddress destination, string htmlContent, string subject)
    {
        var message = new SendGridMessage();
        message.Html = htmlContent;
        message.From = source;
        message.To = new[] { destination };
        message.Subject = subject;

        if(/*check if production */)
        {
            transportSmtp.Deliver(message);
            return;
        }
        // not in production, use mailtrap
        var client = new SmtpClient
                     {
                         Host = "mailtrap.io",
                         Port = 2525, // check port with Mailtrap settings
                         Credentials = new NetworkCredential("user", "pass"), // credentials for mailtrap inbox
                         EnableSsl = true,
                     };

        var mail = new MailMessage(sendGridMessage.From, sendGridMessage.To.First())
                   {
                       Subject = sendGridMessage.Subject,
                       Body = sendGridMessage.Html,
                       IsBodyHtml = true,
                   };

        client.Send(mail);
    }     

And now all my emails from dev and test environments are delivered to Mailtrap inbox. Pretty cool and now I don’t have to think about where emails should go and if I’m sending a test email to a real person.

Mailtrap provides a pretty elaborate API and I’ve managed to build a basic user interface for admin users of my application. Meaning my users will be able to see/read emails going out without emails reaching real people.

In addition to all this, you can write automated tests against their API to check if sent out emails were in correct format. But all my existing tests were using mock objects to mock-out ITransport and I did validate emails against mocks. However, I can see the appeal of testing actual emails. I’ll do that next time I need to validate the correctness of sent-out email.