First Commit
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ADS.Helpers.Base\ADS.Helpers.Base.csproj" />
|
||||
<ProjectReference Include="..\ADS.Helpers.MultiTenant\ADS.Helpers.MultiTenant.csproj" />
|
||||
<ProjectReference Include="..\ADS.Helpers.Localization\ADS.Helpers.Localization.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MailKit" Version="4.15.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ADS.Helpers.NotifyCenter.Models;
|
||||
|
||||
namespace ADS.Helpers.NotifyCenter.Abstractions;
|
||||
|
||||
public interface INotificationProvider
|
||||
{
|
||||
NotificationChannel Channel { get; }
|
||||
string Name { get; }
|
||||
|
||||
Task<NotificationResult> SendAsync(NotificationMessage message, CancellationToken ct = default);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ADS.Helpers.NotifyCenter.Abstractions;
|
||||
|
||||
public interface INotificationSettingsProvider
|
||||
{
|
||||
Task<TSettings> GetAsync<TSettings>(string tenantId, CancellationToken ct = default)
|
||||
where TSettings : class, new();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ADS.Helpers.NotifyCenter.Models;
|
||||
|
||||
namespace ADS.Helpers.NotifyCenter.Abstractions;
|
||||
|
||||
public interface INotifyCenter
|
||||
{
|
||||
Task<NotificationResult> SendAsync(NotificationMessage message, CancellationToken ct = default);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using ADS.Helpers.NotifyCenter.Abstractions;
|
||||
using ADS.Helpers.NotifyCenter.Models;
|
||||
using ADS.Helpers.NotifyCenter.Providers;
|
||||
using ADS.Helpers.NotifyCenter.Providers.Smtp;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace ADS.Helpers.NotifyCenter.Extensions;
|
||||
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddAdsNotifyCenter(this IServiceCollection services)
|
||||
{
|
||||
services.AddOptions<NotifyCenterOptions>();
|
||||
services.AddSingleton<INotifyCenter, NotifyCenter>();
|
||||
|
||||
// Default settings provider (replace later with tenant-aware provider)
|
||||
services.AddSingleton<InMemoryNotificationSettingsProvider>();
|
||||
services.AddSingleton<INotificationSettingsProvider>(sp => sp.GetRequiredService<InMemoryNotificationSettingsProvider>());
|
||||
|
||||
// Providers
|
||||
services.AddSingleton<INotificationProvider, SmtpNotificationProvider>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
// Helper to configure SMTP quickly when using InMemoryNotificationSettingsProvider
|
||||
public static IServiceCollection ConfigureAdsSmtp(this IServiceCollection services, SmtpOptions options, string? tenantId = null)
|
||||
{
|
||||
var sp = services.BuildServiceProvider();
|
||||
var mem = sp.GetRequiredService<InMemoryNotificationSettingsProvider>();
|
||||
mem.Set(options, tenantId);
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace ADS.Helpers.NotifyCenter.Models;
|
||||
|
||||
public static class MessageSources
|
||||
{
|
||||
public const string NotifyCenter = "ADS.NotifyCenter";
|
||||
public const string SmtpProvider = "ADS.NotifyCenter.Smtp";
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace ADS.Helpers.NotifyCenter.Models;
|
||||
|
||||
public static class NotifyCodes
|
||||
{
|
||||
public const string Ok = "NOTIFY_OK";
|
||||
public const string NoProvider = "NOTIFY_NO_PROVIDER";
|
||||
public const string InvalidMessage = "NOTIFY_INVALID_MESSAGE";
|
||||
|
||||
public const string SmtpNotConfigured = "SMTP_NOT_CONFIGURED";
|
||||
public const string SmtpSendFailed = "SMTP_SEND_FAILED";
|
||||
public const string TenantContextMissing = "TENANT_CONTEXT_MISSING";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace ADS.Helpers.NotifyCenter.Models;
|
||||
|
||||
public enum NotificationChannel
|
||||
{
|
||||
SmtpEmail = 1,
|
||||
SignalR = 2,
|
||||
Sms = 3,
|
||||
WhatsApp = 4,
|
||||
Webhook = 5,
|
||||
Push = 6
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
|
||||
|
||||
namespace ADS.Helpers.NotifyCenter.Models;
|
||||
|
||||
public sealed class NotificationMessage
|
||||
{
|
||||
public required NotificationChannel Channel { get; init; }
|
||||
|
||||
// Generic recipients. Providers can interpret these as they need.
|
||||
// For SMTP: To/Cc/Bcc are emails.
|
||||
public List<string> To { get; init; } = new();
|
||||
public List<string> Cc { get; init; } = new();
|
||||
public List<string> Bcc { get; init; } = new();
|
||||
|
||||
public string? Subject { get; init; }
|
||||
public string? BodyText { get; init; }
|
||||
public string? BodyHtml { get; init; }
|
||||
|
||||
// Optional provider-specific metadata (e.g. phone numbers formatting, template ids, etc.)
|
||||
public Dictionary<string, string> Metadata { get; init; } = new();
|
||||
|
||||
// Optional correlation id from caller
|
||||
public string? TraceId { get; init; }
|
||||
public string? Culture { get; init; }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
namespace ADS.Helpers.NotifyCenter.Models;
|
||||
|
||||
public sealed class NotificationResult
|
||||
{
|
||||
public bool Success { get; init; }
|
||||
|
||||
public string Source { get; init; } = ""; // quien emitió
|
||||
public string Code { get; init; } = ""; // keyword estable
|
||||
|
||||
public string? Message { get; init; } // ya traducido (si se resolvió)
|
||||
public string? DebugMessage { get; init; }
|
||||
|
||||
public string? ProviderName { get; init; }
|
||||
public string? ProviderMessageId { get; init; }
|
||||
|
||||
public static NotificationResult Ok(
|
||||
string providerName,
|
||||
string source,
|
||||
string code,
|
||||
string? providerMessageId = null,
|
||||
string? debugMessage = null)
|
||||
=> new()
|
||||
{
|
||||
Success = true,
|
||||
ProviderName = providerName,
|
||||
Source = source,
|
||||
Code = code,
|
||||
ProviderMessageId = providerMessageId,
|
||||
DebugMessage = debugMessage
|
||||
};
|
||||
|
||||
public static NotificationResult Fail(
|
||||
string providerName,
|
||||
string source,
|
||||
string code,
|
||||
string? debugMessage = null)
|
||||
=> new()
|
||||
{
|
||||
Success = false,
|
||||
ProviderName = providerName,
|
||||
Source = source,
|
||||
Code = code,
|
||||
DebugMessage = debugMessage
|
||||
};
|
||||
|
||||
public NotificationResult WithMessage(string message)
|
||||
=> new()
|
||||
{
|
||||
Success = Success,
|
||||
ProviderName = ProviderName,
|
||||
ProviderMessageId = ProviderMessageId,
|
||||
Source = Source,
|
||||
Code = Code,
|
||||
DebugMessage = DebugMessage,
|
||||
Message = message
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace ADS.Helpers.NotifyCenter.Models;
|
||||
|
||||
public sealed class NotifyCenterOptions
|
||||
{
|
||||
// If true, NotifyCenter will throw when no provider is registered for the requested channel.
|
||||
public bool ThrowIfNoProvider { get; set; } = true;
|
||||
|
||||
// If true, NotifyCenter will validate basic fields and fail fast.
|
||||
public bool ValidateMessage { get; set; } = true;
|
||||
}
|
||||
179
Helpers/ADS.Helpers.NotifyCenter/NotifyCenter.cs
Normal file
179
Helpers/ADS.Helpers.NotifyCenter/NotifyCenter.cs
Normal file
@@ -0,0 +1,179 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ADS.Helpers.Base.Abstractions;
|
||||
using ADS.Helpers.Localization.Abstractions;
|
||||
using ADS.Helpers.MultiTenant.Context;
|
||||
using ADS.Helpers.NotifyCenter.Abstractions;
|
||||
using ADS.Helpers.NotifyCenter.Models;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace ADS.Helpers.NotifyCenter;
|
||||
|
||||
public sealed class NotifyCenter : INotifyCenter
|
||||
{
|
||||
private readonly IReadOnlyList<INotificationProvider> _providers;
|
||||
private readonly NotifyCenterOptions _options;
|
||||
private readonly IMessageCatalog _catalog;
|
||||
private readonly IAppLogger _log;
|
||||
private readonly ITenantContextAccessor _tenantAccessor;
|
||||
|
||||
private const string DefaultCulture = "es-MX";
|
||||
|
||||
public NotifyCenter(
|
||||
IEnumerable<INotificationProvider> providers,
|
||||
IOptions<NotifyCenterOptions> options,
|
||||
IMessageCatalog catalog,
|
||||
IAppLogger log,
|
||||
ITenantContextAccessor tenantAccessor)
|
||||
{
|
||||
_providers = providers.ToList();
|
||||
_options = options.Value;
|
||||
_catalog = catalog;
|
||||
_log = log;
|
||||
_tenantAccessor = tenantAccessor;
|
||||
}
|
||||
|
||||
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. Ensure TenantMiddleware runs before using NotifyCenter.";
|
||||
|
||||
_log.Error(
|
||||
source: MessageSources.NotifyCenter,
|
||||
code: NotifyCodes.TenantContextMissing,
|
||||
message: dbg,
|
||||
ex: null,
|
||||
data: new { message.TraceId });
|
||||
|
||||
var fail = NotificationResult.Fail(
|
||||
providerName: "NotifyCenter",
|
||||
source: MessageSources.NotifyCenter,
|
||||
code: NotifyCodes.TenantContextMissing,
|
||||
debugMessage: dbg);
|
||||
|
||||
return Localize(fail, DefaultCulture);
|
||||
}
|
||||
|
||||
var culture = string.IsNullOrWhiteSpace(tenant.Culture) ? DefaultCulture : tenant.Culture;
|
||||
|
||||
if (_options.ValidateMessage)
|
||||
{
|
||||
var validation = Validate(message);
|
||||
if (!validation.Success)
|
||||
return Localize(validation, culture);
|
||||
}
|
||||
|
||||
var provider = _providers.FirstOrDefault(p => p.Channel == message.Channel);
|
||||
|
||||
if (provider is null)
|
||||
{
|
||||
var debug = $"No notification provider registered for channel '{message.Channel}'.";
|
||||
|
||||
if (_options.ThrowIfNoProvider)
|
||||
{
|
||||
_log.Error(
|
||||
source: MessageSources.NotifyCenter,
|
||||
code: NotifyCodes.NoProvider,
|
||||
message: debug,
|
||||
ex: null,
|
||||
data: new { message.TraceId, tenant.TenantId });
|
||||
|
||||
throw new InvalidOperationException(debug);
|
||||
}
|
||||
|
||||
_log.Warn(
|
||||
source: MessageSources.NotifyCenter,
|
||||
code: NotifyCodes.NoProvider,
|
||||
message: debug,
|
||||
data: new { message.TraceId, tenant.TenantId });
|
||||
|
||||
var fail = NotificationResult.Fail(
|
||||
providerName: "NotifyCenter",
|
||||
source: MessageSources.NotifyCenter,
|
||||
code: NotifyCodes.NoProvider,
|
||||
debugMessage: debug);
|
||||
|
||||
return Localize(fail, culture);
|
||||
}
|
||||
|
||||
// Important: providers can still use tenant via TenantContextAccessor if needed,
|
||||
// or you can pass tenant id through message.Metadata.
|
||||
// Here we pass it so providers that are settings-driven can resolve settings by tenant.
|
||||
message.Metadata["tenantId"] = tenant.TenantId;
|
||||
|
||||
var result = await provider.SendAsync(message, ct);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
_log.Error(
|
||||
source: result.Source,
|
||||
code: result.Code,
|
||||
message: "Notification provider failed.",
|
||||
ex: null,
|
||||
data: new
|
||||
{
|
||||
message.TraceId,
|
||||
tenant.TenantId,
|
||||
result.ProviderName,
|
||||
result.DebugMessage
|
||||
});
|
||||
}
|
||||
|
||||
return Localize(result, culture);
|
||||
}
|
||||
|
||||
private NotificationResult Localize(NotificationResult result, string culture)
|
||||
{
|
||||
var localizedMessage = _catalog.Get(
|
||||
source: result.Source,
|
||||
code: result.Code,
|
||||
culture: culture);
|
||||
|
||||
return result.WithMessage(localizedMessage);
|
||||
}
|
||||
|
||||
private static NotificationResult Validate(NotificationMessage message)
|
||||
{
|
||||
if (message.To.Count == 0 && message.Cc.Count == 0 && message.Bcc.Count == 0)
|
||||
{
|
||||
return NotificationResult.Fail(
|
||||
providerName: "NotifyCenter",
|
||||
source: MessageSources.NotifyCenter,
|
||||
code: NotifyCodes.InvalidMessage,
|
||||
debugMessage: "To/Cc/Bcc are all empty.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(message.Subject) &&
|
||||
message.Channel == NotificationChannel.SmtpEmail)
|
||||
{
|
||||
return NotificationResult.Fail(
|
||||
providerName: "NotifyCenter",
|
||||
source: MessageSources.NotifyCenter,
|
||||
code: NotifyCodes.InvalidMessage,
|
||||
debugMessage: "Subject is required for SMTP.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(message.BodyText) &&
|
||||
string.IsNullOrWhiteSpace(message.BodyHtml))
|
||||
{
|
||||
return NotificationResult.Fail(
|
||||
providerName: "NotifyCenter",
|
||||
source: MessageSources.NotifyCenter,
|
||||
code: NotifyCodes.InvalidMessage,
|
||||
debugMessage: "BodyText and BodyHtml are empty.");
|
||||
}
|
||||
|
||||
return NotificationResult.Ok(
|
||||
providerName: "NotifyCenter",
|
||||
source: MessageSources.NotifyCenter,
|
||||
code: NotifyCodes.Ok,
|
||||
providerMessageId: null,
|
||||
debugMessage: "Validation passed.");
|
||||
}
|
||||
}
|
||||
@@ -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