Unify email/SMS via ProcessWeb Mailer API, remove legacy
Replaces legacy email/SMS logic with a new IComService abstraction using the ProcessWeb Mailer API for all outbound communication. Removes FuchsFdsEmail, FuchsEmailService, IEmailService, SmtpAccountSettings, and FuchsEmailSettings. Updates controllers to use IComService. Refactors appsettings.json to use a new "Mailer" section. Adds ProcessWebComSettings and a stub for secret management. Removes OCORE.sms.SMS77 and direct SMTP/MailKit usage. Cleans up solution file references to OCORE projects.
This commit is contained in:
@@ -1,197 +0,0 @@
|
||||
using Fuchs.intranet;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MimeKit;
|
||||
using Newtonsoft.Json;
|
||||
using OCORE.SQL;
|
||||
using static OCORE.SQL.sql;
|
||||
|
||||
namespace Fuchs.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Email service implementation. Replaces the static <c>FuchsFdsEmail</c> class.
|
||||
/// Reads settings from <c>IOptions<FuchsEmailSettings></c> (appsettings.json)
|
||||
/// instead of <c>System.Configuration.ConfigurationManager</c>.
|
||||
/// </summary>
|
||||
public class FuchsEmailService : IEmailService
|
||||
{
|
||||
private readonly ILogger<FuchsEmailService> _logger;
|
||||
private readonly Fuchs_intranet _intranet;
|
||||
private readonly FuchsEmailSettings _emailSettings;
|
||||
private OCORE.email.EmailServerSettings? _cachedSettings;
|
||||
|
||||
private const string ReplyToName = "Sebastian Fuchs - Bad und Heizung";
|
||||
private const string ReplyToAddress = "info@sanitaerfuchs.de";
|
||||
private const string SignatureIntro =
|
||||
"<p> </p><p style=\"margin:24px 0 16px 0;line-height:140%;\">" +
|
||||
"Herzliche Gr\u00fc\u00dfe aus D\u00fcsseldorf-Bilk<br/>" +
|
||||
"Ihr Team der Firma Sebastian Fuchs</p>";
|
||||
|
||||
public FuchsEmailService(
|
||||
ILogger<FuchsEmailService> logger,
|
||||
Fuchs_intranet intranet,
|
||||
IOptions<FuchsEmailSettings> emailSettings)
|
||||
{
|
||||
_logger = logger;
|
||||
_intranet = intranet;
|
||||
_emailSettings = emailSettings.Value;
|
||||
}
|
||||
|
||||
public async Task<bool> SendEmailAsync(string reference, string subject, string html,
|
||||
string email, string name, Dictionary<string, byte[]>? attachments)
|
||||
{
|
||||
var errors = new List<string>();
|
||||
string guid = "";
|
||||
string config = "";
|
||||
DateTime sent = default;
|
||||
|
||||
if (!IsValidEmail(email))
|
||||
errors.Add("Die Email-Adresse ist nicht g\u00fcltig.");
|
||||
if (string.IsNullOrEmpty(html))
|
||||
errors.Add("Bitte geben Sie eine Nachricht ein.");
|
||||
|
||||
if (errors.Count == 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
var settings = GetEmailSettings();
|
||||
string body = html + BuildSignature();
|
||||
|
||||
// Development redirect: replace To with DevRedirectAddress, suppress CC/BCC
|
||||
string devRedirect = _emailSettings.DevRedirectAddress;
|
||||
bool isDevRedirect = !string.IsNullOrWhiteSpace(devRedirect);
|
||||
if (isDevRedirect)
|
||||
{
|
||||
_logger.LogWarning("DEV redirect: email to '{Original}' redirected to '{Redirect}'", email, devRedirect);
|
||||
email = devRedirect;
|
||||
name = "Dev Redirect";
|
||||
}
|
||||
|
||||
if (settings != null)
|
||||
{
|
||||
var msg = new OCORE.email.Email(
|
||||
Mode: OCORE.email.EmailMode.DirectMode,
|
||||
type: settings.type)
|
||||
{
|
||||
EmailSettings = settings,
|
||||
Subject = subject
|
||||
};
|
||||
msg.AddTo(email, name);
|
||||
msg.AddReplyTo(new MailboxAddress(ReplyToName, ReplyToAddress));
|
||||
msg.SetBody(body);
|
||||
if (attachments != null)
|
||||
foreach (var kv in attachments)
|
||||
msg.AttachFile(filecontent: kv.Value, kv.Key);
|
||||
|
||||
var result = await msg.SendAsync(
|
||||
(dref, ex) => _logger.LogError(ex, "Email send error {Reference}", dref));
|
||||
|
||||
guid = msg.MessageId ?? "";
|
||||
config = msg.EmailConfig_serialized;
|
||||
sent = result.Timestamp;
|
||||
errors.AddRange(result.ErrorMessages);
|
||||
}
|
||||
else
|
||||
{
|
||||
var main = _emailSettings.Main;
|
||||
string host = !string.IsNullOrEmpty(main.Host) ? main.Host : _emailSettings.SmtpHost;
|
||||
string user = !string.IsNullOrEmpty(main.Username) ? main.Username : _emailSettings.SmtpUser;
|
||||
string pass = !string.IsNullOrEmpty(main.Password) ? main.Password : _emailSettings.SmtpPass;
|
||||
string from = !string.IsNullOrEmpty(main.From) ? main.From
|
||||
: (!string.IsNullOrEmpty(_emailSettings.SmtpFrom) ? _emailSettings.SmtpFrom : user);
|
||||
string fromName = !string.IsNullOrEmpty(main.Alias) ? main.Alias : _emailSettings.SmtpFromName;
|
||||
int port = main.Port > 0 ? main.Port : _emailSettings.SmtpPort;
|
||||
|
||||
var message = new MimeMessage();
|
||||
message.From.Add(new MailboxAddress(fromName, from));
|
||||
message.To.Add(new MailboxAddress(name, email));
|
||||
message.ReplyTo.Add(new MailboxAddress(ReplyToName, ReplyToAddress));
|
||||
message.Subject = subject;
|
||||
|
||||
var builder = new BodyBuilder { HtmlBody = body };
|
||||
if (attachments != null)
|
||||
foreach (var kv in attachments)
|
||||
builder.Attachments.Add(kv.Key, kv.Value);
|
||||
message.Body = builder.ToMessageBody();
|
||||
|
||||
using var client = new MailKit.Net.Smtp.SmtpClient();
|
||||
await client.ConnectAsync(host, port, MailKit.Security.SecureSocketOptions.Auto);
|
||||
if (!string.IsNullOrEmpty(user))
|
||||
await client.AuthenticateAsync(user, pass);
|
||||
await client.SendAsync(message);
|
||||
await client.DisconnectAsync(true);
|
||||
|
||||
guid = message.MessageId!;
|
||||
sent = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errors.Add("Beim Versenden ist ein Fehler aufgetreten.");
|
||||
_logger.LogError(ex, "SendEmailAsync failed for {Reference}", reference);
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.Count > 0)
|
||||
_logger.LogWarning("SendEmailAsync errors for {Reference}: {Errors}", reference, errors);
|
||||
|
||||
// SQL audit log
|
||||
try
|
||||
{
|
||||
var pl = new List<SqlParameter>
|
||||
{
|
||||
SQL_VarChar("@Ref", reference),
|
||||
SQL_VarChar("@guid", guid),
|
||||
SQL_DateTime("@DateSent", sent == default ? DBNull.Value : (object)sent),
|
||||
SQL_NVarChar("@config", config, dbNull_IfEmpty: true),
|
||||
SQL_Bit("@success", errors.Count == 0),
|
||||
SQL_NVarChar("@log", JsonConvert.SerializeObject(errors))
|
||||
};
|
||||
await setSQLValue_async(
|
||||
"EXECUTE [dbo].[fds__logEmail] @Ref, @guid, @DateSent, @config, @success, @log;",
|
||||
_intranet.Intranet__SQLConnectionString, pl,
|
||||
Security: _intranet.GetDbSecurity());
|
||||
}
|
||||
catch (Exception logEx)
|
||||
{
|
||||
_logger.LogError(logEx, "Failed to log email audit for {Reference}", reference);
|
||||
}
|
||||
|
||||
return errors.Count == 0;
|
||||
}
|
||||
|
||||
private OCORE.email.EmailServerSettings? GetEmailSettings()
|
||||
{
|
||||
if (_cachedSettings != null) return _cachedSettings;
|
||||
|
||||
var main = _emailSettings.Main;
|
||||
if (string.IsNullOrWhiteSpace(main.Host)) return null;
|
||||
|
||||
_cachedSettings = new OCORE.email.EmailServerSettings(main.Type, main.ToOcoreJson());
|
||||
return _cachedSettings;
|
||||
}
|
||||
|
||||
private static string BuildSignature()
|
||||
{
|
||||
try
|
||||
{
|
||||
string sigPath = Path.Combine(AppContext.BaseDirectory,
|
||||
"email_signature", "sanitaerfuchs_email_signature.txt");
|
||||
if (File.Exists(sigPath))
|
||||
return SignatureIntro + File.ReadAllText(sigPath);
|
||||
}
|
||||
catch { /* signature is optional */ }
|
||||
return "";
|
||||
}
|
||||
|
||||
private static bool IsValidEmail(string email)
|
||||
{
|
||||
try
|
||||
{
|
||||
var a = new System.Net.Mail.MailAddress(email);
|
||||
return string.Equals(a.Address, email, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
catch { return false; }
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
namespace Fuchs.Services;
|
||||
|
||||
/// <summary>
|
||||
/// SMTP / email server settings bound from appsettings.json → "Fuchs:Email" section.
|
||||
/// </summary>
|
||||
public class FuchsEmailSettings
|
||||
{
|
||||
/// <summary>Main account for outgoing customer emails (anfrage@sanitaerfuchs.de).</summary>
|
||||
public SmtpAccountSettings Main { get; set; } = new();
|
||||
|
||||
/// <summary>FDS/invoicing account (rechnungen@sanitaerfuchs.de).</summary>
|
||||
public SmtpAccountSettings Fds { get; set; } = new();
|
||||
|
||||
/// <summary>Internal OCORE service account used for system notifications.</summary>
|
||||
public SmtpAccountSettings Service { get; set; } = new();
|
||||
|
||||
/// <summary>Comma-separated list of addresses used as test recipients.</summary>
|
||||
public string TestAddresses { get; set; } = "";
|
||||
|
||||
// ── MailKit fallback settings (used when Main is not configured) ──
|
||||
public string SmtpHost { get; set; } = "";
|
||||
public int SmtpPort { get; set; } = 587;
|
||||
public string SmtpUser { get; set; } = "";
|
||||
public string SmtpPass { get; set; } = "";
|
||||
public string SmtpFrom { get; set; } = "";
|
||||
public string SmtpFromName { get; set; } = "SanitärFuchs";
|
||||
|
||||
/// <summary>
|
||||
/// Development only: when set, all outgoing emails are redirected to this address;
|
||||
/// CC and BCC are cleared. Leave empty in production.
|
||||
/// </summary>
|
||||
public string DevRedirectAddress { get; set; } = "";
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
namespace Fuchs.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Abstraction for sending emails with optional attachments and audit logging.
|
||||
/// Abstraction for outbound communication: email and SMS.
|
||||
/// Backed by the ProcessWeb Mailer API (POST /api/mailer?fn=push_com).
|
||||
/// </summary>
|
||||
public interface IEmailService
|
||||
public interface IComService
|
||||
{
|
||||
/// <summary>
|
||||
/// Sends an email and logs the result to the database.
|
||||
@@ -14,7 +15,15 @@ public interface IEmailService
|
||||
/// <param name="email">Recipient email address.</param>
|
||||
/// <param name="name">Recipient display name.</param>
|
||||
/// <param name="attachments">Optional file attachments (filename → content).</param>
|
||||
/// <returns><c>true</c> when the email was sent successfully.</returns>
|
||||
/// <returns><c>true</c> when the email was accepted successfully.</returns>
|
||||
Task<bool> SendEmailAsync(string reference, string subject, string html,
|
||||
string email, string name, Dictionary<string, byte[]>? attachments);
|
||||
string email, string name, Dictionary<string, byte[]>? attachments = null);
|
||||
|
||||
/// <summary>
|
||||
/// Sends an SMS message.
|
||||
/// </summary>
|
||||
/// <param name="mobile">Recipient mobile number (E.164 or local format).</param>
|
||||
/// <param name="message">Text message body.</param>
|
||||
/// <returns><c>true</c> when the SMS was accepted successfully.</returns>
|
||||
Task<bool> SendSmsAsync(string mobile, string message);
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using Fuchs.intranet;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
using OCORE.SQL;
|
||||
using static OCORE.SQL.sql;
|
||||
|
||||
namespace Fuchs.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Outbound communication service backed by the ProcessWeb Mailer API
|
||||
/// (POST https://api.processweb.de/api/mailer?fn=push_com).
|
||||
/// When <c>ProcessWebComSettings.Enabled</c> is <c>false</c> the service
|
||||
/// only logs the intended communication without calling the API.
|
||||
/// </summary>
|
||||
public class ProcessWebComService : IComService
|
||||
{
|
||||
private readonly ILogger<ProcessWebComService> _logger;
|
||||
private readonly Fuchs_intranet _intranet;
|
||||
private readonly ProcessWebComSettings _settings;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
|
||||
private const string SignatureIntro =
|
||||
"<p> </p><p style=\"margin:24px 0 16px 0;line-height:140%;\">" +
|
||||
"Herzliche Grüße aus Düsseldorf-Bilk<br/>" +
|
||||
"Ihr Team der Firma Sebastian Fuchs</p>";
|
||||
|
||||
public ProcessWebComService(
|
||||
ILogger<ProcessWebComService> logger,
|
||||
Fuchs_intranet intranet,
|
||||
IOptions<ProcessWebComSettings> settings,
|
||||
IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
_logger = logger;
|
||||
_intranet = intranet;
|
||||
_settings = settings.Value;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
}
|
||||
|
||||
public async Task<bool> SendEmailAsync(string reference, string subject, string html,
|
||||
string email, string name, Dictionary<string, byte[]>? attachments = null)
|
||||
{
|
||||
if (!IsValidEmail(email))
|
||||
{
|
||||
_logger.LogWarning("SendEmailAsync: invalid email address '{Email}' for ref {Reference}", email, reference);
|
||||
return false;
|
||||
}
|
||||
|
||||
string body = html + BuildSignature();
|
||||
|
||||
if (!_settings.Enabled)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"[ComService DISABLED] Would send email ref={Reference} subject='{Subject}' to={Email}",
|
||||
reference, subject, email);
|
||||
await WriteAuditLogAsync(reference, "", "", default, false, ["Service disabled – email not sent"]);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
string messageId = "";
|
||||
var errors = new List<string>();
|
||||
|
||||
try
|
||||
{
|
||||
var payload = new
|
||||
{
|
||||
comType = "email",
|
||||
recipient = email,
|
||||
subject,
|
||||
body
|
||||
};
|
||||
|
||||
var (ok, responseBody) = await PostToApiAsync("push_com", payload);
|
||||
if (ok)
|
||||
{
|
||||
success = true;
|
||||
messageId = Guid.NewGuid().ToString("N");
|
||||
}
|
||||
else
|
||||
{
|
||||
errors.Add($"API error: {responseBody}");
|
||||
_logger.LogWarning("SendEmailAsync API error for {Reference}: {Body}", reference, responseBody);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errors.Add("Beim Versenden ist ein Fehler aufgetreten.");
|
||||
_logger.LogError(ex, "SendEmailAsync failed for {Reference}", reference);
|
||||
}
|
||||
|
||||
await WriteAuditLogAsync(reference, messageId, "", success ? DateTime.UtcNow : default, success, errors);
|
||||
return success;
|
||||
}
|
||||
|
||||
public async Task<bool> SendSmsAsync(string mobile, string message)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(mobile))
|
||||
{
|
||||
_logger.LogWarning("SendSmsAsync: empty mobile number");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_settings.Enabled)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"[ComService DISABLED] Would send SMS to={Mobile} message='{Message}'",
|
||||
mobile, message);
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var payload = new
|
||||
{
|
||||
comType = "sms",
|
||||
recipient = mobile,
|
||||
body = message
|
||||
};
|
||||
|
||||
var (ok, responseBody) = await PostToApiAsync("push_com", payload);
|
||||
if (ok) return true;
|
||||
|
||||
_logger.LogWarning("SendSmsAsync API error for {Mobile}: {Body}", mobile, responseBody);
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "SendSmsAsync failed for {Mobile}", mobile);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Private helpers ────────────────────────────────────────────────────────
|
||||
|
||||
private async Task<(bool ok, string body)> PostToApiAsync(string fn, object payload)
|
||||
{
|
||||
var client = _httpClientFactory.CreateClient("ProcessWebMailer");
|
||||
var json = JsonConvert.SerializeObject(payload);
|
||||
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
|
||||
string credentials = Convert.ToBase64String(
|
||||
Encoding.UTF8.GetBytes($"{_settings.AccountId}:{_settings.Token}"));
|
||||
client.DefaultRequestHeaders.Authorization =
|
||||
new AuthenticationHeaderValue("Basic", credentials);
|
||||
|
||||
var response = await client.PostAsync($"{_settings.BaseUrl}/api/mailer?fn={fn}", content);
|
||||
string responseBody = await response.Content.ReadAsStringAsync();
|
||||
return (response.IsSuccessStatusCode, responseBody);
|
||||
}
|
||||
|
||||
private async Task WriteAuditLogAsync(string reference, string guid, string config,
|
||||
DateTime sent, bool success, IEnumerable<string> errors)
|
||||
{
|
||||
try
|
||||
{
|
||||
var pl = new List<SqlParameter>
|
||||
{
|
||||
SQL_VarChar("@Ref", reference),
|
||||
SQL_VarChar("@guid", guid),
|
||||
SQL_DateTime("@DateSent", sent == default ? DBNull.Value : (object)sent),
|
||||
SQL_NVarChar("@config", config, dbNull_IfEmpty: true),
|
||||
SQL_Bit("@success", success),
|
||||
SQL_NVarChar("@log", JsonConvert.SerializeObject(errors.ToList()))
|
||||
};
|
||||
await setSQLValue_async(
|
||||
"EXECUTE [dbo].[fds__logEmail] @Ref, @guid, @DateSent, @config, @success, @log;",
|
||||
_intranet.Intranet__SQLConnectionString, pl,
|
||||
Security: _intranet.GetDbSecurity());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to write audit log for {Reference}", reference);
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildSignature()
|
||||
{
|
||||
try
|
||||
{
|
||||
string sigPath = Path.Combine(AppContext.BaseDirectory,
|
||||
"email_signature", "sanitaerfuchs_email_signature.txt");
|
||||
if (File.Exists(sigPath))
|
||||
return SignatureIntro + File.ReadAllText(sigPath);
|
||||
}
|
||||
catch { /* signature is optional */ }
|
||||
return "";
|
||||
}
|
||||
|
||||
private static bool IsValidEmail(string email)
|
||||
{
|
||||
try
|
||||
{
|
||||
var a = new System.Net.Mail.MailAddress(email);
|
||||
return string.Equals(a.Address, email, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
catch { return false; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace Fuchs.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Settings for the ProcessWeb Mailer API, bound from appsettings.json → "Fuchs:Mailer".
|
||||
/// </summary>
|
||||
public class ProcessWebComSettings
|
||||
{
|
||||
/// <summary>Base URL of the ProcessWeb API (e.g. "https://api.processweb.de").</summary>
|
||||
public string BaseUrl { get; set; } = "https://api.processweb.de";
|
||||
|
||||
/// <summary>Account ID used for HTTP Basic authentication.</summary>
|
||||
public string AccountId { get; set; } = "";
|
||||
|
||||
/// <summary>API token used for HTTP Basic authentication.</summary>
|
||||
public string Token { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// When <c>false</c> (default) the service is disabled and only logs the
|
||||
/// intended communication without actually calling the API.
|
||||
/// Set to <c>true</c> to enable live sending.
|
||||
/// </summary>
|
||||
public bool Enabled { get; set; } = false;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
|
||||
namespace OCORE_web.Secrets;
|
||||
|
||||
/// <summary>
|
||||
/// Placeholder for secret management extension methods.
|
||||
/// Replace with a real Azure Key Vault / DPAPI implementation when ready.
|
||||
/// </summary>
|
||||
public static class SecretManagementExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// No-op stub. Wire up real secret management (e.g. Azure Key Vault) here.
|
||||
/// </summary>
|
||||
public static WebApplicationBuilder AddSecretManagement(this WebApplicationBuilder builder)
|
||||
=> builder;
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Fuchs.Services;
|
||||
|
||||
/// <summary>
|
||||
/// SMTP account configuration for a single email account.
|
||||
/// Property names match the OCORE EmailServerSettings JSON format (lowercase).
|
||||
/// </summary>
|
||||
public class SmtpAccountSettings
|
||||
{
|
||||
[JsonProperty("alias")]
|
||||
public string Alias { get; set; } = "";
|
||||
|
||||
[JsonProperty("to")]
|
||||
public string To { get; set; } = "";
|
||||
|
||||
[JsonProperty("from")]
|
||||
public string From { get; set; } = "";
|
||||
|
||||
[JsonProperty("bcc")]
|
||||
public string Bcc { get; set; } = "";
|
||||
|
||||
[JsonProperty("host")]
|
||||
public string Host { get; set; } = "";
|
||||
|
||||
[JsonProperty("port")]
|
||||
public int Port { get; set; } = 587;
|
||||
|
||||
[JsonProperty("security")]
|
||||
public string Security { get; set; } = "StartTls";
|
||||
|
||||
[JsonProperty("username")]
|
||||
public string Username { get; set; } = "";
|
||||
|
||||
[JsonProperty("password")]
|
||||
public string Password { get; set; } = "";
|
||||
|
||||
[JsonProperty("type")]
|
||||
public string Type { get; set; } = "smtp";
|
||||
|
||||
/// <summary>
|
||||
/// Serializes this instance back to the JSON string format expected by
|
||||
/// <c>OCORE.email.EmailServerSettings(type, raw)</c>.
|
||||
/// </summary>
|
||||
public string ToOcoreJson() => JsonConvert.SerializeObject(this);
|
||||
}
|
||||
Reference in New Issue
Block a user