email service and template using resend
This commit is contained in:
@@ -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");
|
||||
@@ -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/
|
||||
|
||||
Reference in New Issue
Block a user