feat(email): add explicit send API
Require tenant-owned jobs, explicit confirmation, canonical request IDs, and rate limiting before provider delivery. Persist sent correspondence with a content-free idempotency ledger, and never retry uncertain outcomes automatically.
This commit is contained in:
@@ -26,7 +26,7 @@ public sealed class EmailSendAttemptStore(JobTrackerContext db, TimeProvider tim
|
||||
if (!await db.JobApplications.AnyAsync(job => job.Id == request.JobApplicationId, cancellationToken))
|
||||
throw new InvalidOperationException("The job application does not exist in the current owner scope.");
|
||||
|
||||
var existing = await db.EmailSendAttempts.FirstOrDefaultAsync(item => item.ClientRequestId == request.ClientRequestId, cancellationToken);
|
||||
var existing = await db.EmailSendAttempts.AsNoTracking().FirstOrDefaultAsync(item => item.ClientRequestId == request.ClientRequestId, cancellationToken);
|
||||
if (existing is not null) return Existing(existing, request.PayloadHash);
|
||||
|
||||
var attempt = new EmailSendAttempt
|
||||
@@ -48,7 +48,7 @@ public sealed class EmailSendAttemptStore(JobTrackerContext db, TimeProvider tim
|
||||
catch (DbUpdateException)
|
||||
{
|
||||
db.Entry(attempt).State = EntityState.Detached;
|
||||
existing = await db.EmailSendAttempts.FirstOrDefaultAsync(item => item.ClientRequestId == request.ClientRequestId, cancellationToken);
|
||||
existing = await db.EmailSendAttempts.AsNoTracking().FirstOrDefaultAsync(item => item.ClientRequestId == request.ClientRequestId, cancellationToken);
|
||||
if (existing is not null) return Existing(existing, request.PayloadHash);
|
||||
throw;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user