email service and template using resend

This commit is contained in:
2026-05-18 20:47:05 +07:00
parent f0ce22bbb8
commit bf5c97c442
16 changed files with 1152 additions and 2 deletions
@@ -0,0 +1,44 @@
-- CreateEnum
CREATE TYPE "EmailJobStatus" AS ENUM ('PENDING', 'PROCESSING', 'SUCCESS', 'FAILED');
-- CreateTable: log append-only setiap email yang berhasil terkirim.
-- `idempotencyKey` UNIQUE cegah double-send saat webhook retry / cron rerun.
CREATE TABLE "EmailSent" (
"id" TEXT NOT NULL,
"idempotencyKey" TEXT NOT NULL,
"to" TEXT NOT NULL,
"template" TEXT NOT NULL,
"subject" TEXT NOT NULL,
"providerMessageId" TEXT,
"sentAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "EmailSent_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX "EmailSent_idempotencyKey_key" ON "EmailSent"("idempotencyKey");
CREATE INDEX "EmailSent_to_sentAt_idx" ON "EmailSent"("to", "sentAt" DESC);
CREATE INDEX "EmailSent_template_sentAt_idx" ON "EmailSent"("template", "sentAt" DESC);
-- CreateTable: retry queue untuk email yang gagal saat sync send.
-- Cron `/api/cron/process-email-jobs` pick PENDING/FAILED (attempts<5),
-- exponential backoff (scheduledAt bumped tiap retry).
CREATE TABLE "EmailJob" (
"id" TEXT NOT NULL,
"idempotencyKey" TEXT NOT NULL,
"to" TEXT NOT NULL,
"template" TEXT NOT NULL,
"subject" TEXT NOT NULL,
"html" TEXT NOT NULL,
"status" "EmailJobStatus" NOT NULL DEFAULT 'PENDING',
"attempts" INTEGER NOT NULL DEFAULT 0,
"scheduledAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"lastAttemptAt" TIMESTAMP(3),
"lastError" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "EmailJob_pkey" PRIMARY KEY ("id")
);
CREATE INDEX "EmailJob_status_scheduledAt_idx" ON "EmailJob"("status", "scheduledAt");
CREATE INDEX "EmailJob_idempotencyKey_idx" ON "EmailJob"("idempotencyKey");
+45
View File
@@ -467,6 +467,51 @@ model Refund {
@@index([status, createdAt])
}
/// Log append-only setiap email yang berhasil terkirim. `idempotencyKey`
/// UNIQUE cegah double-send saat webhook retry / cron rerun.
model EmailSent {
id String @id @default(cuid())
idempotencyKey String @unique
to String
template String
subject String
/// ID dari Resend (atau provider lain) untuk troubleshooting di dashboard mereka.
providerMessageId String?
sentAt DateTime @default(now())
@@index([to, sentAt(sort: Desc)])
@@index([template, sentAt(sort: Desc)])
}
/// Retry queue untuk email yang gagal saat sync send. Cron pick PENDING/FAILED
/// (attempts<5) → retry dengan exponential backoff. Idempotent via `idempotencyKey`.
model EmailJob {
id String @id @default(cuid())
idempotencyKey String
to String
template String
subject String
html String
status EmailJobStatus @default(PENDING)
attempts Int @default(0)
scheduledAt DateTime @default(now())
lastAttemptAt DateTime?
lastError String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([status, scheduledAt])
@@index([idempotencyKey])
}
enum EmailJobStatus {
PENDING
PROCESSING
SUCCESS
FAILED
}
/// Log polymorphic untuk admin actions lintas entity. Append-only — kalau
/// admin dihapus, `adminId` di-set NULL tapi `adminEmail` snapshot tetap.
/// Dipakai untuk compliance & investigasi (siapa approve/reject/cancel/