feat(email): add durable send ledger
CI and Deploy / test (pull_request) Failing after 1m21s
CI and Deploy / deploy (pull_request) Has been skipped

Tracks only idempotency and delivery metadata; recipient, subject, and body are excluded. No provider send path is enabled.
This commit is contained in:
cesnimda
2026-08-09 23:32:27 +02:00
parent 3a7e4f1088
commit 653f011be2
8 changed files with 3077 additions and 0 deletions
@@ -1098,6 +1098,69 @@ namespace JobTrackerApi.Migrations
b.ToTable("CvVariantVersions");
});
modelBuilder.Entity("JobTrackerApi.Models.EmailSendAttempt", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("ClientRequestId")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("TEXT");
b.Property<DateTime?>("CompletedAtUtc")
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAtUtc")
.HasColumnType("TEXT");
b.Property<string>("FailureCategory")
.HasMaxLength(64)
.HasColumnType("TEXT");
b.Property<int>("JobApplicationId")
.HasColumnType("INTEGER");
b.Property<string>("OwnerUserId")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<string>("PayloadHash")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("TEXT");
b.Property<string>("Provider")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("TEXT");
b.Property<string>("ProviderMessageId")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<DateTime?>("StartedAtUtc")
.HasColumnType("TEXT");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("JobApplicationId");
b.HasIndex("OwnerUserId", "ClientRequestId")
.IsUnique();
b.HasIndex("OwnerUserId", "CreatedAtUtc");
b.ToTable("EmailSendAttempts");
});
modelBuilder.Entity("JobTrackerApi.Models.GmailConnection", b =>
{
b.Property<int>("Id")
@@ -2383,6 +2446,17 @@ namespace JobTrackerApi.Migrations
b.Navigation("CvVariant");
});
modelBuilder.Entity("JobTrackerApi.Models.EmailSendAttempt", b =>
{
b.HasOne("JobTrackerApi.Models.JobApplication", "JobApplication")
.WithMany()
.HasForeignKey("JobApplicationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("JobApplication");
});
modelBuilder.Entity("JobTrackerApi.Models.InterviewPrepItem", b =>
{
b.HasOne("JobTrackerApi.Models.JobApplication", "JobApplication")