Dev Life

Envío de emails usando C# y la API de Mailgun

Domina el envío de emails transaccionales con la API HTTP de Mailgun en C#. Esta guía te explica paso a paso cómo configurar una cuenta de Mailgun, realizar la integración con .NET y gestionar los errores para lograr una entrega de emails lista para la producción.
Imagen para Envío de emails usando C# y la API de Mailgun

.NET es uno de los frameworks web más populares, estables y admirados que puedes utilizar para crear aplicaciones web modernas. Las herramientas que proporciona .NET pueden ayudarte a empezar rápidamente y a mantener tus aplicaciones en el futuro. Con él, puedes crear desde pequeños microservicios muy específicos hasta aplicaciones web completas.

Sin embargo, a la larga, toda aplicación de la vida real necesita poder enviar emails transaccionales (como la verificación de cuentas, el restablecimiento de contraseñas, los informes de resumen automáticos diarios o mensuales, etc.). Aunque .NET ofrece herramientas que te ayudan a enviar emails, no dispone de la infraestructura de nivel de producción que necesitas para enviarlos de forma fiable y eficaz.

En este artículo, aprenderás a utilizar la API HTTP de Mailgun para enviar emails transaccionales desde tus aplicaciones .NET y asegurarte de que tu integración sea fiable, escalable y segura.

Mailgun es un servicio de envío de emails transaccionales que puede impulsar tus aplicaciones .NET para enviar emails listos para la producción. Puedes elegir entre usar el servicio SMTP de Mailgun o su API HTTP para lograr una entrega de emails transaccionales de mayor rendimiento.

Cómo enviar emails usando la API de Mailgun

Antes de empezar este tutorial, necesitarás los siguientes requisitos previos:

  • Conocimientos básicos de C# y .NET
  • Cualquier IDE moderno como Visual Studio, JetBrains Rider o Visual Studio Code

Si decides no usar Visual Studio y al ejecutar dotnet –info desde una terminal no tienes éxito, entonces tendrás que descargar e instalar el último SDK de .NET.

.NET es un framework multiplataforma, por lo que puedes instalarlo en Windows, Mac y Linux. Si tienes problemas para crear una clave de API, puedes seguir estas instrucciones.

Configura tu cuenta de Mailgun

Empieza por crear una cuenta gratuita de Mailgun. Una vez que hayas iniciado sesión, crea una nueva clave de API y guárdala para usarla más adelante.

Para el envío de emails en producción, debes registrar un dominio de tu propiedad en Mailgun. En este tutorial, usarás el dominio sandbox proporcionado por Mailgun.

Crea un nuevo proyecto en C#

Cuando hayas configurado tu cuenta de Mailgun, tendrás que crear una nueva solución web .NET MVC y configurar algunas herramientas adicionales para que te ayuden a enviar emails transaccionales con .NET.

Abre una terminal y ejecuta dotnet new mvc -n MailDemo. Este comando crea un nuevo proyecto web .NET usando una arquitectura MVC dentro de una nueva carpeta /MailDemo. A continuación, ejecuta cd MailDemo para navegar a la nueva carpeta que se ha creado.

Después de acceder a la carpeta, debes añadir tus valores de configuración en appsettings.json. Añade una nueva entrada JSON llamada “Mailgun”. El valor para Mailgun:ApiKey es la clave de API que creaste en la sección anterior (más adelante aprenderás a proteger tus claves de API). Mailgun:Domain es el dominio sandbox que ya se ha creado para ti.

Tu archivo appsettings.json completo debería ser así:

                                

                                    {rn    "Logging": {rn        "LogLevel": {rn            "Default": "Information",rn            "Microsoft.AspNetCore": "Warning"rn        }rn    },rn    "AllowedHosts": "*",rn    "Mailgun": {rn        "ApiKey": "<Your API Key>",rn        "Domain": "<Your sandbox domain>"rn    }rn}
                                
                            

A continuación, tienes que configurar un HttpClient con nombre para poder reutilizar fácilmente los parámetros predeterminados. Para ello, abre Program.cs. La parte superior del archivo debería ser algo así:

                                

                                    using System.Net.Http.Headers;rnusing System.Text;rnrnvar builder = WebApplication.CreateBuilder(args);rnrn// Add services to the containerrnbuilder.Services.AddControllersWithViews();rnrn// Set up your named HttpClient for easy reusernbuilder.Services.AddHttpClient("Mailgun", client =>rn{rn    // Grab values from the configurationrn    var apiKey = builder.Configuration.GetValue<string>("Mailgun:ApiKey");rn    var base64Auth = Convert.ToBase64String(Encoding.ASCII.GetBytes($"api:{apiKey}"));rn    var domain = builder.Configuration.GetValue<string>("Mailgun:Domain");rnrn    // Set default values on the HttpClientrn    client.BaseAddress = new Uri($"https://api.mailgun.net/v3/{domain}/messages");rn    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64Auth);rn});rnrnvar app = builder.Build();rn
                                
                            

Como solo vas a enviar emails, tu HttpClient se configura con la URL completa de la API HTTP de Mailgun que se utiliza para enviar emails.

Llegados a este punto, todo lo que necesitas para enviar tu primer email está listo.

Más información sobre los puntos de conexión de la API de Mailgun

El HttpClient está configurado para enviar peticiones HTTP al punto de conexión https://api.mailgun.net/v3/{domain}/messages. Mailgun también ofrece otros puntos de conexión para ayudarte a disponer de una herramienta sólida e integral con la que gestionar y enviar emails transaccionales:

  • Enviar un email: POST /v3/{domain_name}/messages
  • Enviar un email en formato MIME: POST /v3/{domain_name}/messages.mime
  • Recuperar un email almacenado: GET /v3/domains/{domain_name}/messages/{storage_key}
  • Ver tus dominios: GET /v4/domains
  • Crear un nuevo dominio: POST /v4/domains
  • Ver una lista paginada de todos los eventos entrantes y salientes: GET /v3/{domain_name}/events
  • Ver una lista paginada de todos los rebotes: GET /v3/{domainID}/bounces
Puedes ver los demás puntos de conexión disponibles consultando la documentación de referencia de la API de Mailgun.

Envía tu primer email con la API de Mailgun

Antes de enviar tu primer email transaccional, asegúrate de que la dirección de email del destinatario que utilizas sea uno de los destinatarios autorizados para tu dominio sandbox. En tu cuenta de Mailgun, ve a tus dominios, abre tu dominio sandbox y, en la parte derecha, añade tu email personal como destinatario autorizado:

Overview Screen

Luego, crea un nuevo archivo en /Controllers/OrderController.cs y reemplaza el contenido del archivo por el siguiente:

                                

                                    using System.Diagnostics;rnusing Microsoft.AspNetCore.Mvc;rnusing MailDemo.Models;rnusing System.Text;rnusing System.Net.Mime;rnrnnamespace MailDemo.Controllers;rnrnpublic class OrderController : Controllerrn{rn    private readonly HttpClient _httpClient;rn    private readonly IConfiguration _config;rnrn    public OrderController(IHttpClientFactory httpClientFactory, IConfiguration config)rn    {rn        // Get your named HttpClient that is preconfiguredrn        this._httpClient = httpClientFactory.CreateClient("Mailgun");rn        this._config = config;rn    }rnrn    public async Task<IActionResult> Confirm()rn    {rn        using MultipartFormDataContent form = new();rnrn        // Local function keeps this code a bit clearerrn        void SetFormParam(string key, string value) =>rn        form.Add(new StringContent(value, Encoding.UTF8, MediaTypeNames.Text.Plain), key);rnrn        SetFormParam("from", $"Test User <postmaster@{this._config.GetValue<string>("Mailgun:Domain")}>");rn        SetFormParam("to", "your_authorized_recipient.com");rn        SetFormParam("subject", "Hello World!");rn        SetFormParam("text", "My first transactional email!");rn        SetFormParam("html", @"<html><body><p style=""color:blue;"">My first transactional email!</p></body></html>");rnrn        var result = await this._httpClient.PostAsync(string.Empty, form);rnrn        if (!result.IsSuccessStatusCode)rn        {rn            return new JsonResult(await result.Content.ReadAsStringAsync());rn        }rnrn        return new JsonResult(@"Your order was confirmed. You should get an email soon!");rn    }rn}rn
                                
                            

Ejecuta dotnet run desde tu terminal para iniciar tu aplicación web. Después, abre un navegador web y ve a /order/confirm. Deberías recibir un email en tu bandeja de entrada en un par de segundos:

Hello World Screen Shot
Es posible que el email se envíe a tu carpeta de spam porque aún no has registrado el dominio. De momento, no pasa nada.

Gestión de respuestas y errores

Por desgracia, a veces las cosas salen mal. Por ejemplo, tu clave de API podría tener un error tipográfico, puede que no añadieras todos los campos obligatorios a la petición HTTP o podrías alcanzar los límites de frecuencia de Mailgun.

Una petición HTTP fallida a la API de Mailgun devolverá uno de cuatro códigos de error HTTP distintos:

  • 400: Hay algo incorrecto en el formato de la petición.
  • 401: Error de autenticación.
  • 429: Has alcanzado el límite de frecuencia de Mailgun.
  • 500: Algo ha fallado en la red o por parte de Mailgun.

Veamos cómo puedes gestionar algunos de estos escenarios de error.

Gestión de los códigos de error 400

Siempre que te devuelvan un código de estado HTTP 400, significa que tu carga útil o formato eran incorrectos. Por ejemplo, Mailgun responderá con un código de estado HTTP 400 si falta un campo obligatorio.

En estos casos, hay un enfoque general que puedes adoptar:

  • Lanza una excepción, ya que se trata de un escenario de fallo que no se puede recuperar.
  • Registra la excepción y los motivos de la misma.

El código para ello podría ser similar a este:

                                

                                    if (!result.IsSuccessStatusCode)rn{rn    var status = (int) result.StatusCode;rnrn    if (status == 400)rn    {rn        var responseContent = await result.Content.ReadAsStringAsync();rn        var exception = new BadHttpRequestException("Mailgun 400 HTTP status code");rnrn        this._logger.LogError(exception, exception.Message, responseContent);rn        throw exception;rn    }rn}rn
                                
                            

Puede que también quieras adoptar un enfoque similar con el código de estado HTTP 401.

Gestión de los códigos de error 500

Siempre que recibas un código de estado HTTP 500, puede significar que algo ha fallado por parte de Mailgun o que se ha producido un problema general de red. En estos casos, hay distintos enfoques que puedes adoptar:

  • Pon el email en cola para volver a intentarlo más tarde.
  • En la misma ruta del código, vuelve a intentarlo tras un breve periodo.
  • Emplea una técnica avanzada como un cortacircuitos

A continuación se muestra cómo sería un enfoque básico de reintentos:

                                

                                    if (!result.IsSuccessStatusCode)rn{rn    var status = (int) result.StatusCode;rnrn    if (status == 500)rn    {rn        // Retry the email after 1 second in hopes that the error was transientrnrn        await Task.Delay(1000);rn        result = await _httpClient.PostAsync(string.Empty, form);rnrn        if (!result.IsSuccessStatusCode)rn        {rn            // Log and throw an exception like you did in the previous examplern        }rn    }rn}rn
                                
                            

En este escenario, si recibes un código de estado 500, esperarás un segundo y volverás a intentar la petición HTTP. Si la petición reintentada también falla, la registrarás y lanzarás una excepción del mismo modo que hiciste en el caso del código de estado 400.

Para darte una idea de cómo sería el código en general, aquí tienes un esquema de cómo tu código podría empezar a gestionar distintos escenarios de error:

                                

                                    if (!result.IsSuccessStatusCode)rn{rn    var status = (int)result.StatusCode;rnrn    if (status == 400)rn    {rn        // Log the error and notify the development team that thern        // request was not formatted properly.rn    }rn    else if (status == 401)rn    {rn        // Log the error and notify the development team that thern        // request's authentication failed.rn    }rn    else if (status == 429)rn    {rn        // Use a .NET library like Polly to throttle or try this requestrn        // again with an exponential back-off, etc.rn    }rn    elsern    {rn        // Something went wrong: log the error and apply a distributed rn        // error handling technique like a circuit breaker.rn        // See https://learn.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker for more.rn    }rn}rn
                                
                            

Integración de emails lista para la producción

En este punto, ya has enviado un email con .NET y C#, pero para estar listo para la producción, hay algunos aspectos importantes a tener en cuenta.

Protege tu clave de API

No puedes almacenar tu clave de API de producción real en el código fuente. Además, nadie debería poder acceder a ella en archivos de texto sin formato.

Hay muchas herramientas que puedes utilizar para proteger tu clave de API. Cada proveedor en la nube cuenta con su propio sistema de gestión de claves que permite que tu aplicación recupere tus secretos de forma segura. Por ejemplo, Azure Key Vault o AWS Secrets Manager funcionan a la perfección.

Otras funciones para entornos de producción

Mailgun también ofrece muchas de las funciones de nivel de producción que puedas necesitar, como:

Integración con el ecosistema .NET

Para demostrar lo fácil que es integrarlo con herramientas listas para la producción del ecosistema .NET, anímate a usar una biblioteca de código abierto como Coravel para mejorar la reutilización y la experiencia de desarrollo de tu solución.

Ejecuta dotnet add package coravel.mailer desde tu terminal en la carpeta MailDemo.

A continuación, añade la siguiente entrada JSON a tu archivo appsettings.json:

                                

                                    "Coravel": {rn    "Mail": {rn        "From": {rn            "Name": "Test User",rn            "Email": "postmaster@<Your sandbox domain>"rn        }rn    }rn}rnNext, create a class file called MailgunMailer.cs that implements the ICanSendMail interface. You might notice that this is essentially the same logic you originally had in OrderController:rnusing System.Net.Mime;rnusing System.Text;rnusing Coravel.Mailer.Mail;rnrnnamespace MailDemo;rnrnpublic class MailgunMailer : ICanSendMailrn{rn    private readonly HttpClient _httpClient;rn    private readonly IConfiguration _config;rnrn    public MailgunMailer(IHttpClientFactory httpClientFactory, IConfiguration config)rn    {rn        this._httpClient = httpClientFactory.CreateClient("Mailgun");rn        this._config = config;rn    }rnrn    public async Task SendAsync(MessageBody message, string subject, IEnumerable<MailRecipient> to, MailRecipient from, MailRecipient replyTo, IEnumerable<MailRecipient> cc, IEnumerable<MailRecipient> bcc, IEnumerable<Attachment>? attachments = null, MailRecipient? sender = null)rn    {rn        using MultipartFormDataContent form = new();rnrn        void SetFormParam(string key, string value) =>rn        form.Add(new StringContent(value, Encoding.UTF8, MediaTypeNames.Text.Plain), key);rnrn        if (from is not null)rn        {rn            SetFormParam("from", $"{from.Name} <{from.Email}>");rn        }rn        elsern        {rn            // This gives you a default "from" field if not defined by the callerrn            SetFormParam("from", $"{this._config.GetValue<string>("Coravel:Mail:From:Name")} <{this._config.GetValue<string>("Coravel:Mail:From:Email")}>");rn        }rnrn        foreach (var recipient in to)rn        {rn            SetFormParam("to", recipient.Email);rn        }rn        SetFormParam("subject", subject);rnrn        if (message.HasHtmlMessage())rn        {rn            SetFormParam("html", message.Html);rn        }rnrn        if (message.HasPlainTextMessage())rn        {rn            SetFormParam("text", message.Text);rn        }rnrn        var result = await _httpClient.PostAsync(string.Empty, form);rnrn        // Error handling logic...rn    }rn}rn
                                
                            

Después, tienes que indicarle a Coravel que use este servicio de correo. En tu archivo Program.cs, antes de llamar a var app = builder.Build(), añade lo siguiente:

                                

                                    builder.Services.AddScoped<MailDemo.MailgunMailer>();rnbuilder.AddCustomMailer<MailDemo.MailgunMailer>();rnFinally, replace the code for OrderController with the following:rnusing Microsoft.AspNetCore.Mvc;rnusing Coravel.Mailer.Mail.Interfaces;rnusing Coravel.Mailer.Mail;rnrnnamespace MailDemo.Controllers;rnrnpublic class OrderController : Controllerrn{rn    private readonly IMailer _mailer;rnrn    public OrderController(IMailer mailer)rn    {rn        this._mailer = mailer;rn    }rnrn    public async Task<IActionResult> Confirm()rn    {rn        var mailable = Mailable.AsInline()rn            .To("jamesmichaelhickey@gmail.com")rn            .Subject("Hello World!")rn            .Html(@"<html><body><p style=""color:blue;"">My first transactional email!</p></body></html>")rn            .Text("My first transactional email!");rnrn        await _mailer.SendAsync(mailable);rnrn        return new JsonResult(@"Your order was confirmed. You should get an email soon!");rn    }rn}rn
                                
                            

Tras una pequeña configuración que se realiza una sola vez, notarás que la lógica para enviar emails desde el código de tu aplicación es mucho más fácil de entender y menos extensa.

También has añadido asistencia mediante Mailgun y Coravel para definir cuerpos de email tanto en HTML como en texto sin formato. Mailgun recomienda enviar tanto HTML como texto sin formato de forma conjunta. Lo mejor es enviar emails multiparte usando texto y HTML, o solo texto. Los ESP no suelen recibir bien el envío de emails exclusivamente en HTML.

Mailgun y Coravel también ofrecen asistencia para otras funciones como CC, CCO y adjuntos para emails transaccionales más enfocados a la producción.

En resumen

Mailgun ofrece una forma sencilla API HTTP de enviar emails de manera eficiente y segura desde tus soluciones en .NET. En este artículo, has aprendido a integrar la API HTTP de Mailgun en una aplicación web en C# y a empezar a añadir herramientas de nivel de producción, junto con las capacidades listas para la producción de Mailgun.

Al utilizar Mailgun y .NET de forma conjunta, tus emails transaccionales pueden escalar eficientemente con la funcionalidad async/await de .NET, la infraestructura web de alto rendimiento general del framework, herramientas adicionales del ecosistema .NET y la API HTTP sencilla y de alto rendimiento de Mailgun.

¿Te ha resultado útil? Suscríbete a nuestra newsletter para recibir actualizaciones sobre tutoriales, noticias sobre emails e información de nuestros expertos en email.