All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 6m46s
- No se toma la configuración SMTP del .env por falla de lecturas. - Se incluyen las configuraciones en appsettings.json
149 lines
6.6 KiB
C#
149 lines
6.6 KiB
C#
using GestionIntegral.Api.Data.Repositories.Comunicaciones;
|
|
using GestionIntegral.Api.Models.Comunicaciones;
|
|
using MailKit.Net.Smtp;
|
|
using MailKit.Security;
|
|
using Microsoft.Extensions.Options;
|
|
using MimeKit;
|
|
using System.Net.Security;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
|
|
namespace GestionIntegral.Api.Services.Comunicaciones
|
|
{
|
|
public class EmailService : IEmailService
|
|
{
|
|
private readonly MailSettings _mailSettings;
|
|
private readonly ILogger<EmailService> _logger;
|
|
private readonly IEmailLogRepository _emailLogRepository;
|
|
|
|
public EmailService(
|
|
IOptions<MailSettings> mailSettings,
|
|
ILogger<EmailService> logger,
|
|
IEmailLogRepository emailLogRepository)
|
|
{
|
|
_mailSettings = mailSettings.Value;
|
|
_logger = logger;
|
|
_emailLogRepository = emailLogRepository;
|
|
}
|
|
|
|
public async Task EnviarEmailAsync(
|
|
string destinatarioEmail, string destinatarioNombre, string asunto, string cuerpoHtml,
|
|
byte[]? attachment = null, string? attachmentName = null,
|
|
string? origen = null, string? referenciaId = null, int? idUsuarioDisparo = null,
|
|
int? idLoteDeEnvio = null)
|
|
{
|
|
var email = new MimeMessage();
|
|
email.Sender = new MailboxAddress(_mailSettings.SenderName, _mailSettings.SenderEmail);
|
|
email.From.Add(email.Sender);
|
|
email.To.Add(new MailboxAddress(destinatarioNombre, destinatarioEmail));
|
|
email.Subject = asunto;
|
|
|
|
var builder = new BodyBuilder { HtmlBody = cuerpoHtml };
|
|
if (attachment != null && !string.IsNullOrEmpty(attachmentName))
|
|
{
|
|
builder.Attachments.Add(attachmentName, attachment, ContentType.Parse("application/pdf"));
|
|
}
|
|
email.Body = builder.ToMessageBody();
|
|
|
|
await SendAndLogEmailAsync(email, origen, referenciaId, idUsuarioDisparo, idLoteDeEnvio);
|
|
}
|
|
|
|
public async Task EnviarEmailConsolidadoAsync(
|
|
string destinatarioEmail, string destinatarioNombre, string asunto, string cuerpoHtml,
|
|
List<(byte[] content, string name)> adjuntos,
|
|
string? origen = null, string? referenciaId = null, int? idUsuarioDisparo = null,
|
|
int? idLoteDeEnvio = null)
|
|
{
|
|
var email = new MimeMessage();
|
|
email.Sender = new MailboxAddress(_mailSettings.SenderName, _mailSettings.SenderEmail);
|
|
email.From.Add(email.Sender);
|
|
email.To.Add(new MailboxAddress(destinatarioNombre, destinatarioEmail));
|
|
email.Subject = asunto;
|
|
|
|
var builder = new BodyBuilder { HtmlBody = cuerpoHtml };
|
|
if (adjuntos != null)
|
|
{
|
|
foreach (var adjunto in adjuntos)
|
|
{
|
|
builder.Attachments.Add(adjunto.name, adjunto.content, ContentType.Parse("application/pdf"));
|
|
}
|
|
}
|
|
email.Body = builder.ToMessageBody();
|
|
|
|
await SendAndLogEmailAsync(email, origen, referenciaId, idUsuarioDisparo, idLoteDeEnvio);
|
|
}
|
|
|
|
private async Task SendAndLogEmailAsync(MimeMessage emailMessage, string? origen, string? referenciaId, int? idUsuarioDisparo, int? idLoteDeEnvio)
|
|
{
|
|
var destinatario = emailMessage.To.Mailboxes.FirstOrDefault()?.Address ?? "desconocido";
|
|
|
|
var log = new EmailLog
|
|
{
|
|
FechaEnvio = DateTime.Now,
|
|
DestinatarioEmail = destinatario,
|
|
Asunto = emailMessage.Subject,
|
|
Origen = origen,
|
|
ReferenciaId = referenciaId,
|
|
IdUsuarioDisparo = idUsuarioDisparo,
|
|
IdLoteDeEnvio = idLoteDeEnvio
|
|
};
|
|
|
|
using var smtp = new SmtpClient();
|
|
try
|
|
{
|
|
// Se añade una política de validación de certificado personalizada.
|
|
// Esto es necesario para entornos de desarrollo o redes internas donde
|
|
// el nombre del host al que nos conectamos (ej. una IP) no coincide
|
|
// con el nombre en el certificado SSL (ej. mail.eldia.com).
|
|
smtp.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
|
|
{
|
|
// Si no hay errores, el certificado es válido.
|
|
if (sslPolicyErrors == SslPolicyErrors.None)
|
|
return true;
|
|
|
|
// Si el único error es que el nombre no coincide (RemoteCertificateNameMismatch)
|
|
// Y el certificado es el que esperamos (emitido para "mail.eldia.com"),
|
|
// entonces lo aceptamos como válido.
|
|
if (sslPolicyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNameMismatch) && certificate != null && certificate.Subject.Contains("CN=mail.eldia.com"))
|
|
{
|
|
_logger.LogWarning("Se aceptó un certificado SSL con 'Name Mismatch' para el host de confianza 'mail.eldia.com'.");
|
|
return true;
|
|
}
|
|
|
|
// Para cualquier otro error, rechazamos el certificado.
|
|
_logger.LogError("Error de validación de certificado SSL: {Errors}", sslPolicyErrors);
|
|
return false;
|
|
};
|
|
|
|
await smtp.ConnectAsync(_mailSettings.SmtpHost, _mailSettings.SmtpPort, SecureSocketOptions.StartTls);
|
|
await smtp.AuthenticateAsync(_mailSettings.SmtpUser, _mailSettings.SmtpPass);
|
|
await smtp.SendAsync(emailMessage);
|
|
|
|
log.Estado = "Enviado";
|
|
_logger.LogInformation("Email enviado exitosamente a {Destinatario}. Asunto: {Asunto}", destinatario, emailMessage.Subject);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error general al enviar email a {Destinatario}. Asunto: {Asunto}", destinatario, emailMessage.Subject);
|
|
log.Estado = "Fallido";
|
|
log.Error = ex.Message;
|
|
throw;
|
|
}
|
|
finally
|
|
{
|
|
if (smtp.IsConnected)
|
|
{
|
|
await smtp.DisconnectAsync(true);
|
|
}
|
|
|
|
try
|
|
{
|
|
await _emailLogRepository.CreateAsync(log);
|
|
}
|
|
catch (Exception logEx)
|
|
{
|
|
_logger.LogError(logEx, "FALLO CRÍTICO: No se pudo guardar el log del email para {Destinatario}", destinatario);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |