INBOX-3: SMTP send + Gmail threading (POST /api/requests/{id}/send)
- IGmailMailSender (mockable) + MailKit GmailMailSender (smtp.gmail.com:587 STARTTLS, App
Password). SendReplyService builds a threaded reply: In-Reply-To = original Message-Id,
References = stored chain + original (deduped), Subject = 'Re: ' (no double-prefix); sends,
then sets Status=Answered + AnsweredAt + stores sent body in DraftReply. Never auto-sends.
- POST /api/requests/{id}/send {body}: 200 updated RequestDto; 404 unknown; 503 MailNotConfigured;
502 MailAuthFailed (UI hint: re-enter App Password). No entity change (no migration).
- Tests (fake SMTP): threading headers, Answered transition, no-double-Re, not-configured +
auth-failure keep status; BuildMimeMessage header mapping. Live send gated on App Password.
This commit is contained in:
27
GerbilManagerWebAPI/Inbox/MailSendContracts.cs
Normal file
27
GerbilManagerWebAPI/Inbox/MailSendContracts.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace GerbilManagerWebAPI.Inbox
|
||||
{
|
||||
/// <summary>A reply to send, with RFC threading headers already computed.</summary>
|
||||
public sealed record OutgoingMail(
|
||||
string To,
|
||||
string Subject,
|
||||
string Body,
|
||||
string? InReplyTo,
|
||||
string? References);
|
||||
|
||||
/// <summary>Outcome of a send attempt. Error is null on success; otherwise a stable code
|
||||
/// ("MailNotConfigured", "MailAuthFailed") the UI maps to a German hint.</summary>
|
||||
public sealed record SendOutcome(bool Sent, string? Error)
|
||||
{
|
||||
public static readonly SendOutcome Ok = new(true, null);
|
||||
public static SendOutcome Fail(string code) => new(false, code);
|
||||
}
|
||||
|
||||
/// <summary>Sends mail. Abstracted so tests can assert the message/capture it without a live
|
||||
/// SMTP server (the live MailKit/Gmail path is gated on Julian's App Password).</summary>
|
||||
public interface IGmailMailSender
|
||||
{
|
||||
/// <summary>Sends the reply. Throws <see cref="MailKit.Security.AuthenticationException"/>
|
||||
/// on bad/revoked credentials (surfaced as "MailAuthFailed").</summary>
|
||||
Task SendAsync(MailConnection connection, OutgoingMail mail, CancellationToken ct = default);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user