using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System.Net;
using System.Net.Mail;
using System.Text;
using System.Text.Json;
using FrameworkBLL.ActionProcessor;
namespace FrameworkSL.Controllers.ActionProcessor
{
///
/// Simple dedicated worker: listens on "email-direct" queue → sends email via SMTP.
/// No DB, no tenant, no idempotency — pure RabbitMQ → SMTP pipeline.
///
public sealed class SimpleEmailWorker : BackgroundService
{
public const string QueueName = "email-direct";
private readonly IConfiguration _config;
private readonly SmtpSettings _smtp;
private readonly ILogger _logger;
public SimpleEmailWorker(
IConfiguration config,
IOptions smtp,
ILogger logger)
{
_config = config;
_smtp = smtp.Value;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken ct)
{
_logger.LogInformation("SimpleEmailWorker: starting");
while (!ct.IsCancellationRequested)
{
try
{
await RunAsync(ct).ConfigureAwait(false);
}
catch (OperationCanceledException) when (ct.IsCancellationRequested) { break; }
catch (Exception ex)
{
_logger.LogError(ex, "SimpleEmailWorker: connection lost — retrying in 30s");
await Task.Delay(TimeSpan.FromSeconds(30), ct).ConfigureAwait(false);
}
}
_logger.LogInformation("SimpleEmailWorker: stopped");
}
private async Task RunAsync(CancellationToken ct)
{
var factory = new ConnectionFactory
{
HostName = _config["RabbitMQ:Host"] ?? "localhost",
UserName = _config["RabbitMQ:UserName"] ?? "guest",
Password = _config["RabbitMQ:Password"] ?? "guest",
VirtualHost = _config["RabbitMQ:VirtualHost"] ?? "/",
AutomaticRecoveryEnabled = true
};
_logger.LogInformation("SimpleEmailWorker: connecting to RabbitMQ at {Host}", factory.HostName);
var connection = await factory.CreateConnectionAsync(ct).ConfigureAwait(false);
_logger.LogInformation("SimpleEmailWorker: ✅ RabbitMQ connected");
var channel = await connection.CreateChannelAsync(cancellationToken: ct);
await channel.QueueDeclareAsync(
queue: QueueName,
durable: true,
exclusive: false,
autoDelete: false,
arguments: null,
cancellationToken: ct);
await channel.BasicQosAsync(0, 1, false, ct);
_logger.LogInformation("SimpleEmailWorker: ✅ Listening on queue '{Q}'", QueueName);
var consumer = new AsyncEventingBasicConsumer(channel);
consumer.ReceivedAsync += async (_, ea) =>
{
var raw = Encoding.UTF8.GetString(ea.Body.Span);
_logger.LogInformation("SimpleEmailWorker: 📨 Message received | Body={Body}", raw);
SimpleEmailMessage? msg = null;
try
{
msg = JsonSerializer.Deserialize(ea.Body.Span);
}
catch (Exception ex)
{
_logger.LogError(ex, "SimpleEmailWorker: ❌ Deserialize failed — ACKing bad message");
await channel.BasicAckAsync(ea.DeliveryTag, false);
return;
}
if (msg is null || string.IsNullOrWhiteSpace(msg.To))
{
_logger.LogError("SimpleEmailWorker: ❌ 'To' is empty — ACKing bad message");
await channel.BasicAckAsync(ea.DeliveryTag, false);
return;
}
_logger.LogInformation(
"SimpleEmailWorker: 📧 Sending email | To={To} Subject={Subject} SmtpHost={Host}:{Port}",
msg.To, msg.Subject, _smtp.Host, _smtp.Port);
try
{
#pragma warning disable SYSLIB0006
using var client = new SmtpClient(_smtp.Host, _smtp.Port)
{
Credentials = new NetworkCredential(_smtp.User, _smtp.Password),
EnableSsl = _smtp.EnableSsl
};
#pragma warning restore SYSLIB0006
using var mail = new MailMessage
{
From = new MailAddress(_smtp.User, "GoodBooks ERP"),
Subject = msg.Subject ?? "GoodBooks Notification",
Body = msg.Body ?? string.Empty,
IsBodyHtml = msg.IsHtml
};
mail.To.Add(msg.To);
await client.SendMailAsync(mail, ct);
_logger.LogInformation("SimpleEmailWorker: ✅ EMAIL SENT | To={To}", msg.To);
await channel.BasicAckAsync(ea.DeliveryTag, false);
}
catch (Exception ex)
{
_logger.LogError(ex, "SimpleEmailWorker: ❌ SMTP FAILED | To={To} Host={Host}:{Port}", msg.To, _smtp.Host, _smtp.Port);
await channel.BasicNackAsync(ea.DeliveryTag, false, requeue: false);
}
};
await channel.BasicConsumeAsync(QueueName, autoAck: false, consumer, ct);
await Task.Delay(Timeout.Infinite, ct).ConfigureAwait(false);
}
}
public class SimpleEmailMessage
{
public string To { get; set; } = string.Empty;
public string? Subject { get; set; }
public string? Body { get; set; }
public bool IsHtml { get; set; } = false;
}
}