using GerbilManagerWebAPI.Models;
using MailKit.Security;
namespace GerbilManagerWebAPI.Inbox
{
///
/// Sends a (human-edited) reply to a Request via SMTP, threaded into the original Gmail
/// conversation, and marks the request Answered. Only ever called on the explicit
/// POST /api/requests/{id}/send — never auto-sends.
///
public sealed class SendReplyService
{
private readonly ApplicationContext _db;
private readonly IGmailMailSender _sender;
private readonly MailSettingsService _settings;
public SendReplyService(ApplicationContext db, IGmailMailSender sender, MailSettingsService settings)
{
_db = db;
_sender = sender;
_settings = settings;
}
public async Task SendAsync(Request request, string body, CancellationToken ct = default)
{
var settings = await _settings.GetAsync(ct);
var password = _settings.DecryptPassword(settings);
if (string.IsNullOrWhiteSpace(settings.GmailAddress) || string.IsNullOrWhiteSpace(password))
return SendOutcome.Fail("MailNotConfigured");
var mail = new OutgoingMail(
To: request.FromAddress,
Subject: ReplySubject(request.Subject),
Body: body,
InReplyTo: request.GmailMessageId,
References: BuildReferences(request.ReferencesHeader, request.GmailMessageId));
try
{
await _sender.SendAsync(new MailConnection(settings.GmailAddress!, password!, settings.Folder), mail, ct);
}
catch (AuthenticationException)
{
return SendOutcome.Fail("MailAuthFailed");
}
request.Status = RequestStatus.Answered;
request.AnsweredAt = DateTimeOffset.UtcNow;
request.DraftReply = body; // record what was actually sent
await _db.SaveChangesAsync(ct);
return SendOutcome.Ok;
}
/// "Re: " prefix without double-prefixing an existing reply subject.
internal static string ReplySubject(string? original)
{
var s = (original ?? "").Trim();
if (s.Length == 0) return "Re:";
return s.StartsWith("Re:", StringComparison.OrdinalIgnoreCase) ? s : $"Re: {s}";
}
/// References = existing chain + the original Message-Id (deduped, in order).
internal static string BuildReferences(string? existingChain, string originalMessageId)
{
var ids = (existingChain ?? "")
.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.ToList();
if (!string.IsNullOrWhiteSpace(originalMessageId) && !ids.Contains(originalMessageId))
ids.Add(originalMessageId);
return string.Join(' ', ids);
}
}
}