Back to "Envío de correos HTML desde el núcleo de ASP.NET con FluentEmail"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

ASP.NET FluentEmail

Envío de correos HTML desde el núcleo de ASP.NET con FluentEmail

Wednesday, 07 August 2024

Este es un artículo bastante simple, pero cubrirá algo de la odness de usar FluentEmail en ASP.NET Core para enviar correos HTML que no he visto en otra parte.

El problema

Enviar correos HTML es en sí mismo un poco simple con SmtpClient, pero no es muy flexible y no soporta cosas como plantillas o adjuntos. FluentEmail es una gran biblioteca para esto, pero no siempre está claro cómo usarlo en ASP.NET Core.

FluentEmail con Razorlight (está integrado) te permite plantillar tus emails usando la sintaxis de Razor. Esto es genial, ya que le permite utilizar todo el poder de Razor para crear sus correos electrónicos.

La solución

En primer lugar, es necesario instalar las bibliotecas FluentEmail.Core, FluentEmail.Smtp & FluentEmail.Razor:

dotnet add package FluentEmail.Core
dotnet add package FluentEmail.Smtp
dotnet add package FluentEmail.Razor

Configuración de FluentEmail

Para mantener las cosas separadas, creé una extensión de IServiceCollection que establece los servicios de FluentEmail:

namespace Mostlylucid.Email;

public static class Setup
{
    public static void SetupEmail(this IServiceCollection services, IConfiguration config)
    {
          var smtpSettings = services.ConfigurePOCO<SmtpSettings>(config.GetSection(SmtpSettings.Section));

        services.AddFluentEmail(smtpSettings.SenderEmail, smtpSettings.SenderName)
            .AddRazorRenderer();

        services.AddSingleton<ISender>(new SmtpSender( () => new SmtpClient()
        {
            DeliveryMethod = SmtpDeliveryMethod.Network,
            Host = smtpSettings.Server,
            Port = smtpSettings.Port,
            Credentials = new NetworkCredential(smtpSettings.Username, smtpSettings.Password),
            EnableSsl = smtpSettings.EnableSSL,
            UseDefaultCredentials = false
        }));
        services.AddSingleton<EmailService>();
        
    }

}

Configuración ##SMTP

Como verás, también utilicé el método IConfigSection mencionado en mi Artículo anterior para obtener la configuración SMTP.

  var smtpSettings = services.ConfigurePOCO<SmtpSettings>(config.GetSection(SmtpSettings.Section));

Esto viene del archivo appsettings.json:

"SmtpSettings":
  {
    "Server": "smtp.gmail.com",
    "Port": 587,
    "SenderName": "Mostlylucid",
    "Username": "",
    "SenderEmail": "[email protected]",
    "Password": "",
    "EnableSSL": "true",
    "EmailSendTry": 3,
    "EmailSendFailed": "true",
    "ToMail": "[email protected]",
    "EmailSubject": "Mostlylucid"
    
  }

GMAIL / Google SMTP

Nota: Para Google SMTP si utiliza MFA (que usted *¿En serio? Si necesitas hacer un contraseña de aplicación para tu cuenta.

Para dev local, puede añadir esto a su archivo secrets.json:

secrets.png

Configuración de Docker

Para el uso del docker composite normalmente lo incluirías en un archivo.env:

SMTPSETTINGS_USERNAME="[email protected]"
SMTPSETTINGS_PASSWORD="<MFA PASSWORD>" -- this is the app password you created

Luego en el archivo de composición docker se inyectan estas como variables env:

services:
  mostlylucid:
    image: scottgal/mostlylucid:latest
    ports:
      - 8080:8080
    environment:
      - SmtpSettings__UserName=${SMTPSETTINGS_USERNAME}
      - SmtpSettings__Password=${SMTPSETTINGS_PASSWORD}

Tome una nota de la separación, ya que esto puede realmente lío con docker componer. Para comprobar lo que se inyecta se puede utilizar

docker compose config

Para mostrarte cómo se ve el archivo con estos inyectados.

Incomodidades de FluentEmail

Un problema con Fluent Email es que usted necesita agregar esto a su csproj

  <PropertyGroup>
    <PreserveCompilationContext>true</PreserveCompilationContext>
  </PropertyGroup>

Esto es porque FluentEmail utiliza RazorLight que necesita esto para funcionar.

Para los archivos de plantilla, puede incluirlos en su proyecto como archivos de contenido o como lo hago en el contenedor Docker, copiar los archivos a la imagen final

FROM build AS publish
RUN dotnet publish "Mostlylucid.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false

FROM base AS final
WORKDIR /app

COPY --from=publish /app/publish .
# Copy the Markdown directory
COPY ./Mostlylucid/Markdown /app/Markdown
COPY ./Mostlylucid/Email/Templates /app/Email/Templates
# Switch to a non-root user
USER $APP_UID

Servicio de correo electrónico

¡De acuerdo, de vuelta al código!

Ahora lo tenemos todo configurado podemos agregar el servicio de correo electrónico. Este es un servicio simple que toma una plantilla y envía un correo electrónico:

public class EmailService(SmtpSettings smtpSettings, IFluentEmail fluentEmail)
{
    public async Task SendCommentEmail(string commenterEmail, string commenterName, string comment, string postSlug)
    {
        var commentModel = new CommentEmailModel
        {
            PostSlug = postSlug,
            SenderEmail = commenterEmail,
            SenderName = commenterName,
            Comment = comment
        };
        await SendCommentEmail(commentModel);
    }

    public async Task SendCommentEmail(CommentEmailModel commentModel)
    {
        // Load the template
        var templatePath = "Email/Templates/MailTemplate.template";
        await SendMail(commentModel, templatePath);
    }

    public async Task SendContactEmail(ContactEmailModel contactModel)
    {
        var templatePath = "Email/Templates/ContactEmailModel.template";

        await SendMail(contactModel, templatePath);
    }


    public async Task SendMail(BaseEmailModel model, string templatePath)
    {
        var template = await File.ReadAllTextAsync(templatePath);
        // Use FluentEmail to send the email
        var email = fluentEmail.UsingTemplate(template, model);
        await email.To(smtpSettings.ToMail)
            .SetFrom(smtpSettings.SenderEmail, smtpSettings.SenderName)
            .Subject("New Comment")
            .SendAsync();
    }
}

Como se puede ver aquí tenemos dos métodos, uno para los comentarios y otro para el formulario de contacto (¡Envíame un correo! ). En esta aplicación te hago iniciar sesión para que pueda obtener el correo que es de (y para evitar el spam).

Realmente la mayor parte del trabajo se hace aquí:

 var template = await File.ReadAllTextAsync(templatePath);
        // Use FluentEmail to send the email
        var email = fluentEmail.UsingTemplate(template, model);
        await email.To(smtpSettings.ToMail)
            .SetFrom(smtpSettings.SenderEmail, smtpSettings.SenderName)
            .Subject("New Comment")
            .SendAsync();

Aquí abrimos un archivo de plantilla, añadimos el modelo que contiene el contenido del correo electrónico, lo cargamos en FluentEmail y luego lo enviamos. La plantilla es un archivo Razor simple:

@model Mostlylucid.Email.Models.ContactEmailModel

<!DOCTYPE html>
<html class="dark">
<head>
    <title>Comment Email</title>
</head>
<body>
<h1>Comment Email</h1>
<p>New comment from email @Model.SenderEmail name @Model.SenderName</p>

<p>Thank you for your comment on our blog post. We appreciate your feedback.</p>
<p>Here is your comment:</p>
<div>
    @Raw( @Model.Comment)</div>
<p>Thanks,</p>
<p>The Blog Team</p>

</body>
</html>

Estos se almacenan como archivos.template en la carpeta Email/Templates. Usted puede utilizar archivos.cshtml pero causa un problema con la etiqueta @Raw en la plantilla (es una cosa de luz de afeitar).

El Contralor

Finalmente llegamos al controlador; es realmente bastante sencillo

    [HttpPost]
    [Route("submit")]
    [Authorize]
    public async Task<IActionResult> Submit(string comment)
    {
        var user = GetUserInfo();
            var commentHtml = commentService.ProcessComment(comment);
            var contactModel = new ContactEmailModel()
            {
                SenderEmail = user.email,
                SenderName =user.name,
                Comment = commentHtml,
            };
            await emailService.SendContactEmail(contactModel);
            return PartialView("_Response", new ContactViewModel(){Email = user.email, Name = user.name, Comment = commentHtml, Authenticated = user.loggedIn});

        return RedirectToAction("Index", "Home");
    }

Aquí obtenemos la información del usuario, procesar el comentario (Utilizo un simple procesador Markdown con Markdig para convertir Markdown a HTML) y luego enviar el correo electrónico.

logo

©2024 Scott Galloway