Initial commit: WpywMail 自建邮件系统:.NET 8 原生 SMTP/IMAP 服务端(DKIM 签名、SPF/DKIM/DMARC 入站校验、SQLite 存储、完整账号体系)、Node 服务端、WinUI 3 客户端与 Web 前端

This commit is contained in:
WpyQwq
2026-09-19 11:19:40 +08:00
commit b8814a7615
84 changed files with 21197 additions and 0 deletions
+75
View File
@@ -0,0 +1,75 @@
using System.Net;
using System.Net.Mail;
namespace WpywMail.Native;
public sealed class DeliveryQueue
{
private readonly AppConfig config;
private readonly FileStore store;
private readonly DirectSmtpDelivery direct;
public DeliveryQueue(AppConfig config, FileStore store)
{
this.config = config;
this.store = store;
direct = new DirectSmtpDelivery(config, store);
}
public async Task RunAsync(CancellationToken token)
{
AppLog.Info($"[发送] 投递模式:{(config.DeliveryMode.Equals("relay", StringComparison.OrdinalIgnoreCase) ? "SMTP 中继" : "按 MX 直接投递")}");
while (!token.IsCancellationRequested)
{
foreach (var job in store.TakeDueQueue(10))
{
try
{
var message = store.GetById(job.MessageId) ?? throw new InvalidOperationException("发送队列中的邮件不存在。");
await Deliver(message, job.Recipients, token);
store.CompleteQueue(job);
AppLog.Info($"[发送] 投递成功:{string.Join(", ", job.Recipients)}");
}
catch (Exception ex)
{
store.FailQueue(job, ex);
AppLog.Error($"[发送队列] {job.MessageId} → {string.Join(", ", job.Recipients)}:{ex.Message}");
}
}
await Task.Delay(TimeSpan.FromSeconds(5), token).ContinueWith(_ => { });
}
}
private async Task Deliver(MailMessage message, string[] recipients, CancellationToken token)
{
if (config.DeliveryMode.Equals("relay", StringComparison.OrdinalIgnoreCase))
{
await DeliverThroughRelay(message, recipients, token);
return;
}
await direct.DeliverAsync(message, recipients, token);
}
private async Task DeliverThroughRelay(MailMessage message, string[] recipients, CancellationToken token)
{
if (string.IsNullOrWhiteSpace(config.Relay.Host)) throw new InvalidOperationException("DeliveryMode=relay 时必须配置 Relay.Host。");
using var client = new SmtpClient(config.Relay.Host, config.Relay.Port)
{
EnableSsl = config.Relay.EnableSsl,
DeliveryMethod = SmtpDeliveryMethod.Network,
Timeout = 60_000
};
if (!string.IsNullOrWhiteSpace(config.Relay.User)) client.Credentials = new NetworkCredential(config.Relay.User, config.Relay.Password);
using var mail = new System.Net.Mail.MailMessage
{
From = new MailAddress(message.From),
Subject = message.Subject,
Body = message.Text,
BodyEncoding = System.Text.Encoding.UTF8,
SubjectEncoding = System.Text.Encoding.UTF8
};
foreach (var recipient in recipients) mail.To.Add(recipient);
await client.SendMailAsync(mail, token);
}
}