First Commit
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ADS.Helpers.NotifyCenter.Abstractions;
|
||||
|
||||
namespace ADS.Helpers.NotifyCenter.Providers;
|
||||
|
||||
// Simple in-memory settings store. Later you replace this with a tenant-aware provider (DB/SQLite/etc.)
|
||||
public sealed class InMemoryNotificationSettingsProvider : INotificationSettingsProvider
|
||||
{
|
||||
private readonly ConcurrentDictionary<(Type type, string? tenantId), object> _store = new();
|
||||
|
||||
public Task<TSettings> GetAsync<TSettings>(string? tenantId, CancellationToken ct = default)
|
||||
where TSettings : class, new()
|
||||
{
|
||||
var key = (typeof(TSettings), tenantId);
|
||||
if (_store.TryGetValue(key, out var obj) && obj is TSettings typed)
|
||||
return Task.FromResult(typed);
|
||||
|
||||
// fallback to default (tenantId = null)
|
||||
var keyDefault = (typeof(TSettings), (string?)null);
|
||||
if (_store.TryGetValue(keyDefault, out var objDefault) && objDefault is TSettings typedDefault)
|
||||
return Task.FromResult(typedDefault);
|
||||
|
||||
return Task.FromResult(new TSettings());
|
||||
}
|
||||
|
||||
public void Set<TSettings>(TSettings settings, string? tenantId = null) where TSettings : class
|
||||
{
|
||||
_store[(typeof(TSettings), tenantId)] = settings;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ADS.Helpers.Base.Abstractions;
|
||||
using ADS.Helpers.MultiTenant.Context;
|
||||
using ADS.Helpers.NotifyCenter.Abstractions;
|
||||
using ADS.Helpers.NotifyCenter.Models;
|
||||
using MailKit.Net.Smtp;
|
||||
using MailKit.Security;
|
||||
|
||||
namespace ADS.Helpers.NotifyCenter.Providers.Smtp;
|
||||
|
||||
public sealed class SmtpNotificationProvider : INotificationProvider
|
||||
{
|
||||
private readonly INotificationSettingsProvider _settingsProvider;
|
||||
private readonly ITenantContextAccessor _tenantAccessor;
|
||||
private readonly IAppLogger _log;
|
||||
|
||||
public NotificationChannel Channel => NotificationChannel.SmtpEmail;
|
||||
public string Name => "SMTP";
|
||||
|
||||
public SmtpNotificationProvider(
|
||||
INotificationSettingsProvider settingsProvider,
|
||||
ITenantContextAccessor tenantAccessor,
|
||||
IAppLogger log)
|
||||
{
|
||||
_settingsProvider = settingsProvider;
|
||||
_tenantAccessor = tenantAccessor;
|
||||
_log = log;
|
||||
}
|
||||
|
||||
public async Task<NotificationResult> SendAsync(NotificationMessage message, CancellationToken ct = default)
|
||||
{
|
||||
var tenant = _tenantAccessor.Current;
|
||||
|
||||
if (tenant is null || string.IsNullOrWhiteSpace(tenant.TenantId))
|
||||
{
|
||||
var dbg = "TenantContext is not available for SMTP provider.";
|
||||
|
||||
_log.Error(
|
||||
source: MessageSources.SmtpProvider,
|
||||
code: NotifyCodes.TenantContextMissing,
|
||||
message: dbg,
|
||||
ex: null,
|
||||
data: new { message.TraceId });
|
||||
|
||||
return NotificationResult.Fail(
|
||||
providerName: Name,
|
||||
source: MessageSources.SmtpProvider,
|
||||
code: NotifyCodes.TenantContextMissing,
|
||||
debugMessage: dbg);
|
||||
}
|
||||
|
||||
var opts = await _settingsProvider.GetAsync<SmtpSettings>(tenant.TenantId, ct);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(opts.Host) || opts.Port <= 0 || string.IsNullOrWhiteSpace(opts.FromEmail))
|
||||
{
|
||||
_log.Warn(
|
||||
source: MessageSources.SmtpProvider,
|
||||
code: NotifyCodes.SmtpNotConfigured,
|
||||
message: "SMTP settings are missing or invalid.",
|
||||
data: new { message.TraceId, tenant.TenantId });
|
||||
|
||||
return NotificationResult.Fail(
|
||||
providerName: Name,
|
||||
source: MessageSources.SmtpProvider,
|
||||
code: NotifyCodes.SmtpNotConfigured,
|
||||
debugMessage: "Host/Port/FromEmail missing or invalid.");
|
||||
}
|
||||
|
||||
var mime = new MimeKit.MimeMessage();
|
||||
mime.From.Add(new MimeKit.MailboxAddress(opts.FromName ?? "", opts.FromEmail));
|
||||
|
||||
foreach (var to in message.To.Distinct(StringComparer.OrdinalIgnoreCase))
|
||||
mime.To.Add(MimeKit.MailboxAddress.Parse(to));
|
||||
|
||||
foreach (var cc in message.Cc.Distinct(StringComparer.OrdinalIgnoreCase))
|
||||
mime.Cc.Add(MimeKit.MailboxAddress.Parse(cc));
|
||||
|
||||
foreach (var bcc in message.Bcc.Distinct(StringComparer.OrdinalIgnoreCase))
|
||||
mime.Bcc.Add(MimeKit.MailboxAddress.Parse(bcc));
|
||||
|
||||
mime.Subject = message.Subject ?? "";
|
||||
|
||||
var bodyBuilder = new MimeKit.BodyBuilder
|
||||
{
|
||||
TextBody = message.BodyText,
|
||||
HtmlBody = message.BodyHtml
|
||||
};
|
||||
mime.Body = bodyBuilder.ToMessageBody();
|
||||
|
||||
try
|
||||
{
|
||||
using var client = new SmtpClient { Timeout = opts.TimeoutMs };
|
||||
|
||||
SecureSocketOptions sec;
|
||||
if (opts.UseSsl) sec = SecureSocketOptions.SslOnConnect;
|
||||
else if (opts.UseStartTls) sec = SecureSocketOptions.StartTls;
|
||||
else sec = SecureSocketOptions.None;
|
||||
|
||||
await client.ConnectAsync(opts.Host, opts.Port, sec, ct);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(opts.Username))
|
||||
await client.AuthenticateAsync(opts.Username, opts.Password, ct);
|
||||
|
||||
await client.SendAsync(mime, ct);
|
||||
await client.DisconnectAsync(true, ct);
|
||||
|
||||
_log.Info(
|
||||
source: MessageSources.SmtpProvider,
|
||||
code: NotifyCodes.Ok,
|
||||
message: "SMTP message sent.",
|
||||
data: new { message.TraceId, tenant.TenantId });
|
||||
|
||||
return NotificationResult.Ok(
|
||||
providerName: Name,
|
||||
source: MessageSources.SmtpProvider,
|
||||
code: NotifyCodes.Ok,
|
||||
providerMessageId: null,
|
||||
debugMessage: "SMTP message sent.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Error(
|
||||
source: MessageSources.SmtpProvider,
|
||||
code: NotifyCodes.SmtpSendFailed,
|
||||
message: "SMTP send failed.",
|
||||
ex: ex,
|
||||
data: new { message.TraceId, tenant.TenantId });
|
||||
|
||||
return NotificationResult.Fail(
|
||||
providerName: Name,
|
||||
source: MessageSources.SmtpProvider,
|
||||
code: NotifyCodes.SmtpSendFailed,
|
||||
debugMessage: ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace ADS.Helpers.NotifyCenter.Providers.Smtp;
|
||||
|
||||
public sealed class SmtpOptions
|
||||
{
|
||||
public string Host { get; set; } = "";
|
||||
public int Port { get; set; } = 587;
|
||||
|
||||
public bool UseSsl { get; set; } = false; // implicit SSL (SMTPS)
|
||||
public bool UseStartTls { get; set; } = true; // STARTTLS
|
||||
|
||||
public string Username { get; set; } = "";
|
||||
public string Password { get; set; } = "";
|
||||
|
||||
public string FromEmail { get; set; } = "";
|
||||
public string FromName { get; set; } = "";
|
||||
|
||||
// Optional timeout
|
||||
public int TimeoutMs { get; set; } = 15000;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace ADS.Helpers.NotifyCenter.Providers.Smtp;
|
||||
|
||||
public sealed class SmtpSettings
|
||||
{
|
||||
public string Host { get; set; } = "";
|
||||
public int Port { get; set; } = 587;
|
||||
|
||||
public bool UseSsl { get; set; } = false;
|
||||
public bool UseStartTls { get; set; } = true;
|
||||
|
||||
public string Username { get; set; } = "";
|
||||
public string Password { get; set; } = "";
|
||||
|
||||
public string FromEmail { get; set; } = "";
|
||||
public string FromName { get; set; } = "";
|
||||
|
||||
public int TimeoutMs { get; set; } = 15000;
|
||||
}
|
||||
Reference in New Issue
Block a user