feat: complete cmpp gateway delivery recovery workflows

This commit is contained in:
hectorzhao
2026-07-08 16:30:06 +08:00
parent cc628d0214
commit 8144f08652
60 changed files with 8901 additions and 94 deletions
@@ -0,0 +1,56 @@
-- Add customer-side downstream delivery persistence and uplink matching fields.
ALTER TABLE "SmsUplinkMessage"
ADD COLUMN "applicationId" TEXT,
ADD COLUMN "messageRecordId" TEXT,
ADD COLUMN "matchStatus" TEXT NOT NULL DEFAULT 'unmatched',
ADD COLUMN "matchReason" TEXT;
ALTER TABLE "SmsUplinkMessage"
ADD CONSTRAINT "SmsUplinkMessage_applicationId_fkey"
FOREIGN KEY ("applicationId") REFERENCES "SmsApplication"("id") ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE "SmsUplinkMessage"
ADD CONSTRAINT "SmsUplinkMessage_messageRecordId_fkey"
FOREIGN KEY ("messageRecordId") REFERENCES "SmsMessageRecord"("id") ON DELETE SET NULL ON UPDATE CASCADE;
CREATE INDEX "SmsUplinkMessage_applicationId_receivedAt_idx" ON "SmsUplinkMessage"("applicationId", "receivedAt");
CREATE INDEX "SmsUplinkMessage_messageRecordId_idx" ON "SmsUplinkMessage"("messageRecordId");
CREATE INDEX "SmsUplinkMessage_matchStatus_idx" ON "SmsUplinkMessage"("matchStatus");
CREATE TABLE "CmppDownstreamDelivery" (
"id" TEXT NOT NULL,
"tenantId" TEXT NOT NULL,
"applicationId" TEXT NOT NULL,
"messageRecordId" TEXT,
"messageId" TEXT,
"deliveryType" TEXT NOT NULL,
"status" TEXT NOT NULL DEFAULT 'pending',
"payload" JSONB NOT NULL,
"retryCount" INTEGER NOT NULL DEFAULT 0,
"nextRetryAt" TIMESTAMP(3),
"deliveredAt" TIMESTAMP(3),
"lastError" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "CmppDownstreamDelivery_pkey" PRIMARY KEY ("id")
);
ALTER TABLE "CmppDownstreamDelivery"
ADD CONSTRAINT "CmppDownstreamDelivery_tenantId_fkey"
FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
ALTER TABLE "CmppDownstreamDelivery"
ADD CONSTRAINT "CmppDownstreamDelivery_applicationId_fkey"
FOREIGN KEY ("applicationId") REFERENCES "SmsApplication"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
ALTER TABLE "CmppDownstreamDelivery"
ADD CONSTRAINT "CmppDownstreamDelivery_messageRecordId_fkey"
FOREIGN KEY ("messageRecordId") REFERENCES "SmsMessageRecord"("id") ON DELETE SET NULL ON UPDATE CASCADE;
CREATE INDEX "CmppDownstreamDelivery_tenantId_status_createdAt_idx" ON "CmppDownstreamDelivery"("tenantId", "status", "createdAt");
CREATE INDEX "CmppDownstreamDelivery_applicationId_status_createdAt_idx" ON "CmppDownstreamDelivery"("applicationId", "status", "createdAt");
CREATE INDEX "CmppDownstreamDelivery_messageId_idx" ON "CmppDownstreamDelivery"("messageId");
CREATE INDEX "CmppDownstreamDelivery_messageRecordId_idx" ON "CmppDownstreamDelivery"("messageRecordId");
CREATE INDEX "CmppDownstreamDelivery_deliveryType_status_idx" ON "CmppDownstreamDelivery"("deliveryType", "status");
@@ -0,0 +1,3 @@
ALTER TABLE "SmsApplication"
ADD COLUMN "cmppMaxConnections" INTEGER NOT NULL DEFAULT 1,
ADD COLUMN "cmppWindowSize" INTEGER NOT NULL DEFAULT 16;
@@ -0,0 +1,38 @@
CREATE TABLE "GatewaySubmitDeadLetter" (
"id" TEXT NOT NULL,
"streamMessageId" TEXT NOT NULL,
"tenantId" TEXT,
"applicationId" TEXT,
"channelId" TEXT,
"traceId" TEXT,
"messageId" TEXT,
"submitId" TEXT,
"status" TEXT NOT NULL DEFAULT 'pending',
"failureCode" TEXT NOT NULL,
"failureMessage" TEXT NOT NULL,
"attempts" INTEGER NOT NULL DEFAULT 1,
"maxAttempts" INTEGER NOT NULL DEFAULT 3,
"commandPayload" JSONB,
"rawPayload" TEXT,
"manualRetryCount" INTEGER NOT NULL DEFAULT 0,
"lastRetryStreamId" TEXT,
"lastRetriedAt" TIMESTAMP(3),
"resolvedAt" TIMESTAMP(3),
"resolvedStatus" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "GatewaySubmitDeadLetter_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX "GatewaySubmitDeadLetter_streamMessageId_key" ON "GatewaySubmitDeadLetter"("streamMessageId");
CREATE INDEX "GatewaySubmitDeadLetter_status_createdAt_idx" ON "GatewaySubmitDeadLetter"("status", "createdAt");
CREATE INDEX "GatewaySubmitDeadLetter_tenantId_status_createdAt_idx" ON "GatewaySubmitDeadLetter"("tenantId", "status", "createdAt");
CREATE INDEX "GatewaySubmitDeadLetter_applicationId_status_createdAt_idx" ON "GatewaySubmitDeadLetter"("applicationId", "status", "createdAt");
CREATE INDEX "GatewaySubmitDeadLetter_channelId_status_createdAt_idx" ON "GatewaySubmitDeadLetter"("channelId", "status", "createdAt");
CREATE INDEX "GatewaySubmitDeadLetter_messageId_idx" ON "GatewaySubmitDeadLetter"("messageId");
CREATE INDEX "GatewaySubmitDeadLetter_submitId_idx" ON "GatewaySubmitDeadLetter"("submitId");
ALTER TABLE "GatewaySubmitDeadLetter" ADD CONSTRAINT "GatewaySubmitDeadLetter_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE "GatewaySubmitDeadLetter" ADD CONSTRAINT "GatewaySubmitDeadLetter_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "SmsApplication"("id") ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE "GatewaySubmitDeadLetter" ADD CONSTRAINT "GatewaySubmitDeadLetter_channelId_fkey" FOREIGN KEY ("channelId") REFERENCES "SmsChannel"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -0,0 +1,33 @@
CREATE TABLE "GatewayDownstreamRecoveryStatus" (
"id" TEXT NOT NULL,
"account" TEXT NOT NULL,
"tenantId" TEXT,
"applicationId" TEXT,
"gatewayInstanceId" TEXT,
"state" TEXT NOT NULL,
"lastAttemptAt" TIMESTAMP(3),
"lastSuccessAt" TIMESTAMP(3),
"lastFailureAt" TIMESTAMP(3),
"nextRetryAt" TIMESTAMP(3),
"attemptCount" INTEGER NOT NULL DEFAULT 0,
"lastError" TEXT,
"lastSkipReason" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "GatewayDownstreamRecoveryStatus_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX "GatewayDownstreamRecoveryStatus_account_key" ON "GatewayDownstreamRecoveryStatus"("account");
CREATE INDEX "GatewayDownstreamRecoveryStatus_tenantId_state_updatedAt_idx" ON "GatewayDownstreamRecoveryStatus"("tenantId", "state", "updatedAt");
CREATE INDEX "GatewayDownstreamRecoveryStatus_applicationId_state_updatedAt_idx" ON "GatewayDownstreamRecoveryStatus"("applicationId", "state", "updatedAt");
CREATE INDEX "GatewayDownstreamRecoveryStatus_state_updatedAt_idx" ON "GatewayDownstreamRecoveryStatus"("state", "updatedAt");
CREATE INDEX "GatewayDownstreamRecoveryStatus_nextRetryAt_idx" ON "GatewayDownstreamRecoveryStatus"("nextRetryAt");
ALTER TABLE "GatewayDownstreamRecoveryStatus"
ADD CONSTRAINT "GatewayDownstreamRecoveryStatus_tenantId_fkey"
FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE "GatewayDownstreamRecoveryStatus"
ADD CONSTRAINT "GatewayDownstreamRecoveryStatus_applicationId_fkey"
FOREIGN KEY ("applicationId") REFERENCES "SmsApplication"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -0,0 +1,5 @@
ALTER TABLE "GatewayDownstreamRecoveryStatus"
ADD COLUMN "failureCategory" TEXT;
CREATE INDEX "GatewayDownstreamRecoveryStatus_failureCategory_updatedAt_idx"
ON "GatewayDownstreamRecoveryStatus"("failureCategory", "updatedAt");
@@ -0,0 +1,6 @@
ALTER TABLE "GatewayDownstreamRecoveryStatus"
ADD COLUMN "lockOwner" TEXT,
ADD COLUMN "lockExpiresAt" TIMESTAMP(3);
CREATE INDEX "GatewayDownstreamRecoveryStatus_lockOwner_lockExpiresAt_idx"
ON "GatewayDownstreamRecoveryStatus"("lockOwner", "lockExpiresAt");
@@ -0,0 +1,64 @@
CREATE TABLE "SmsMessageSegmentAudit" (
"id" TEXT NOT NULL,
"tenantId" TEXT NOT NULL,
"batchTaskId" TEXT,
"messageRecordId" TEXT NOT NULL,
"submitRecordId" TEXT,
"channelId" TEXT,
"submitId" TEXT NOT NULL,
"attempt" INTEGER NOT NULL DEFAULT 0,
"segmentTotal" INTEGER NOT NULL DEFAULT 1,
"segmentIndex" INTEGER NOT NULL DEFAULT 1,
"sequenceId" INTEGER,
"gatewayMessageId" TEXT,
"submitStatus" TEXT NOT NULL DEFAULT 'queued',
"receiptStatus" TEXT,
"rawStatus" TEXT,
"compensationType" TEXT,
"errorCode" TEXT,
"errorMessage" TEXT,
"submittedAt" TIMESTAMP(3),
"deliveredAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "SmsMessageSegmentAudit_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX "SmsMessageSegmentAudit_messageRecordId_submitId_segmentIndex_key"
ON "SmsMessageSegmentAudit"("messageRecordId", "submitId", "segmentIndex");
CREATE INDEX "SmsMessageSegmentAudit_tenantId_createdAt_idx"
ON "SmsMessageSegmentAudit"("tenantId", "createdAt");
CREATE INDEX "SmsMessageSegmentAudit_messageRecordId_segmentIndex_idx"
ON "SmsMessageSegmentAudit"("messageRecordId", "segmentIndex");
CREATE INDEX "SmsMessageSegmentAudit_submitRecordId_idx"
ON "SmsMessageSegmentAudit"("submitRecordId");
CREATE INDEX "SmsMessageSegmentAudit_gatewayMessageId_idx"
ON "SmsMessageSegmentAudit"("gatewayMessageId");
CREATE INDEX "SmsMessageSegmentAudit_submitStatus_receiptStatus_idx"
ON "SmsMessageSegmentAudit"("submitStatus", "receiptStatus");
ALTER TABLE "SmsMessageSegmentAudit"
ADD CONSTRAINT "SmsMessageSegmentAudit_tenantId_fkey"
FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
ALTER TABLE "SmsMessageSegmentAudit"
ADD CONSTRAINT "SmsMessageSegmentAudit_batchTaskId_fkey"
FOREIGN KEY ("batchTaskId") REFERENCES "SmsBatchTask"("id") ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE "SmsMessageSegmentAudit"
ADD CONSTRAINT "SmsMessageSegmentAudit_messageRecordId_fkey"
FOREIGN KEY ("messageRecordId") REFERENCES "SmsMessageRecord"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "SmsMessageSegmentAudit"
ADD CONSTRAINT "SmsMessageSegmentAudit_submitRecordId_fkey"
FOREIGN KEY ("submitRecordId") REFERENCES "SmsSubmitRecord"("id") ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE "SmsMessageSegmentAudit"
ADD CONSTRAINT "SmsMessageSegmentAudit_channelId_fkey"
FOREIGN KEY ("channelId") REFERENCES "SmsChannel"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -0,0 +1,45 @@
-- CreateTable
CREATE TABLE "SmsUplinkMatchCandidate" (
"id" TEXT NOT NULL,
"uplinkMessageId" TEXT NOT NULL,
"tenantId" TEXT NOT NULL,
"applicationId" TEXT NOT NULL,
"messageRecordId" TEXT,
"matchSource" TEXT NOT NULL,
"confidence" INTEGER NOT NULL DEFAULT 50,
"reason" TEXT,
"status" TEXT NOT NULL DEFAULT 'pending',
"claimedAt" TIMESTAMP(3),
"claimedById" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "SmsUplinkMatchCandidate_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "SmsUplinkMatchCandidate_uplinkMessageId_applicationId_messageRecordId_key" ON "SmsUplinkMatchCandidate"("uplinkMessageId", "applicationId", "messageRecordId");
-- CreateIndex
CREATE INDEX "SmsUplinkMatchCandidate_uplinkMessageId_status_idx" ON "SmsUplinkMatchCandidate"("uplinkMessageId", "status");
-- CreateIndex
CREATE INDEX "SmsUplinkMatchCandidate_tenantId_status_createdAt_idx" ON "SmsUplinkMatchCandidate"("tenantId", "status", "createdAt");
-- CreateIndex
CREATE INDEX "SmsUplinkMatchCandidate_applicationId_status_createdAt_idx" ON "SmsUplinkMatchCandidate"("applicationId", "status", "createdAt");
-- CreateIndex
CREATE INDEX "SmsUplinkMatchCandidate_messageRecordId_idx" ON "SmsUplinkMatchCandidate"("messageRecordId");
-- AddForeignKey
ALTER TABLE "SmsUplinkMatchCandidate" ADD CONSTRAINT "SmsUplinkMatchCandidate_uplinkMessageId_fkey" FOREIGN KEY ("uplinkMessageId") REFERENCES "SmsUplinkMessage"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "SmsUplinkMatchCandidate" ADD CONSTRAINT "SmsUplinkMatchCandidate_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "SmsUplinkMatchCandidate" ADD CONSTRAINT "SmsUplinkMatchCandidate_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "SmsApplication"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "SmsUplinkMatchCandidate" ADD CONSTRAINT "SmsUplinkMatchCandidate_messageRecordId_fkey" FOREIGN KEY ("messageRecordId") REFERENCES "SmsMessageRecord"("id") ON DELETE SET NULL ON UPDATE CASCADE;
+201 -12
View File
@@ -36,8 +36,13 @@ model Tenant {
smsApiRequests SmsApiRequest[]
smsSubmitRecords SmsSubmitRecord[]
smsReceiptRecords SmsReceiptRecord[]
smsMessageSegmentAudits SmsMessageSegmentAudit[]
smsUplinkMessages SmsUplinkMessage[]
smsUplinkMatchCandidates SmsUplinkMatchCandidate[]
cmppDownstreamDeliveries CmppDownstreamDelivery[]
cmppConnectionStates CmppConnectionState[]
gatewayDownstreamRecoveryStatuses GatewayDownstreamRecoveryStatus[]
gatewaySubmitDeadLetters GatewaySubmitDeadLetter[]
}
model EnterpriseCertification {
@@ -338,6 +343,8 @@ model SmsApplication {
callbackUrl String?
cmppAccount String @unique
secretHash String
cmppMaxConnections Int @default(1)
cmppWindowSize Int @default(16)
dailyLimit Int?
customerUnitPrice Int @default(0)
queuePriority String @default("normal")
@@ -354,7 +361,12 @@ model SmsApplication {
sendTasks SmsSendTask[]
batchTasks SmsBatchTask[]
messageRecords SmsMessageRecord[]
uplinkMessages SmsUplinkMessage[]
uplinkMatchCandidates SmsUplinkMatchCandidate[]
downstreamDeliveries CmppDownstreamDelivery[]
connectionStates CmppConnectionState[]
gatewayDownstreamRecoveryStatuses GatewayDownstreamRecoveryStatus[]
gatewaySubmitDeadLetters GatewaySubmitDeadLetter[]
@@index([tenantId, status])
}
@@ -496,8 +508,10 @@ model SmsChannel {
submitSessions CmppSubmitSession[]
submitRecords SmsSubmitRecord[]
receiptRecords SmsReceiptRecord[]
segmentAudits SmsMessageSegmentAudit[]
uplinkMessages SmsUplinkMessage[]
connectionStates CmppConnectionState[]
gatewaySubmitDeadLetters GatewaySubmitDeadLetter[]
@@index([status])
}
@@ -818,6 +832,7 @@ model SmsBatchTask {
messages SmsMessageRecord[]
submitRecords SmsSubmitRecord[]
receiptRecords SmsReceiptRecord[]
segmentAudits SmsMessageSegmentAudit[]
@@index([tenantId, status, createdAt])
@@index([status, scheduledAt])
@@ -876,6 +891,10 @@ model SmsMessageRecord {
channel SmsChannel? @relation(fields: [channelId], references: [id])
submitRecords SmsSubmitRecord[]
receiptRecords SmsReceiptRecord[]
segmentAudits SmsMessageSegmentAudit[]
matchedUplinks SmsUplinkMessage[] @relation("SmsUplinkMatchedMessage")
uplinkMatchCandidates SmsUplinkMatchCandidate[]
downstreamDeliveries CmppDownstreamDelivery[]
@@index([tenantId, status, queuedAt])
@@index([batchTaskId, status])
@@ -922,12 +941,51 @@ model SmsSubmitRecord {
messageRecord SmsMessageRecord @relation(fields: [messageRecordId], references: [id], onDelete: Cascade)
channel SmsChannel @relation(fields: [channelId], references: [id])
session CmppSubmitSession? @relation(fields: [sessionId], references: [id])
segmentAudits SmsMessageSegmentAudit[]
@@index([tenantId, createdAt])
@@index([messageRecordId])
@@index([gatewayMessageId])
}
model SmsMessageSegmentAudit {
id String @id @default(cuid())
tenantId String
batchTaskId String?
messageRecordId String
submitRecordId String?
channelId String?
submitId String
attempt Int @default(0)
segmentTotal Int @default(1)
segmentIndex Int @default(1)
sequenceId Int?
gatewayMessageId String?
submitStatus String @default("queued")
receiptStatus String?
rawStatus String?
compensationType String?
errorCode String?
errorMessage String?
submittedAt DateTime?
deliveredAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id])
batchTask SmsBatchTask? @relation(fields: [batchTaskId], references: [id])
messageRecord SmsMessageRecord @relation(fields: [messageRecordId], references: [id], onDelete: Cascade)
submitRecord SmsSubmitRecord? @relation(fields: [submitRecordId], references: [id])
channel SmsChannel? @relation(fields: [channelId], references: [id])
@@unique([messageRecordId, submitId, segmentIndex])
@@index([tenantId, createdAt])
@@index([messageRecordId, segmentIndex])
@@index([submitRecordId])
@@index([gatewayMessageId])
@@index([submitStatus, receiptStatus])
}
model SmsReceiptRecord {
id String @id @default(cuid())
tenantId String
@@ -954,21 +1012,152 @@ model SmsReceiptRecord {
}
model SmsUplinkMessage {
id String @id @default(cuid())
tenantId String?
channelId String
messageId String?
sequenceId Int?
phoneNumber String
destId String
content String
receivedAt DateTime
createdAt DateTime @default(now())
id String @id @default(cuid())
tenantId String?
applicationId String?
channelId String
messageRecordId String?
messageId String?
sequenceId Int?
phoneNumber String
destId String
content String
matchStatus String @default("unmatched")
matchReason String?
receivedAt DateTime
createdAt DateTime @default(now())
tenant Tenant? @relation(fields: [tenantId], references: [id])
channel SmsChannel @relation(fields: [channelId], references: [id])
tenant Tenant? @relation(fields: [tenantId], references: [id])
application SmsApplication? @relation(fields: [applicationId], references: [id])
messageRecord SmsMessageRecord? @relation("SmsUplinkMatchedMessage", fields: [messageRecordId], references: [id])
channel SmsChannel @relation(fields: [channelId], references: [id])
matchCandidates SmsUplinkMatchCandidate[]
@@index([tenantId, createdAt])
@@index([applicationId, receivedAt])
@@index([channelId, receivedAt])
@@index([messageRecordId])
@@index([matchStatus])
@@index([phoneNumber])
}
model SmsUplinkMatchCandidate {
id String @id @default(cuid())
uplinkMessageId String
tenantId String
applicationId String
messageRecordId String?
matchSource String
confidence Int @default(50)
reason String?
status String @default("pending")
claimedAt DateTime?
claimedById String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
uplinkMessage SmsUplinkMessage @relation(fields: [uplinkMessageId], references: [id], onDelete: Cascade)
tenant Tenant @relation(fields: [tenantId], references: [id])
application SmsApplication @relation(fields: [applicationId], references: [id])
messageRecord SmsMessageRecord? @relation(fields: [messageRecordId], references: [id])
@@unique([uplinkMessageId, applicationId, messageRecordId])
@@index([uplinkMessageId, status])
@@index([tenantId, status, createdAt])
@@index([applicationId, status, createdAt])
@@index([messageRecordId])
}
model CmppDownstreamDelivery {
id String @id @default(cuid())
tenantId String
applicationId String
messageRecordId String?
messageId String?
deliveryType String
status String @default("pending")
payload Json
retryCount Int @default(0)
nextRetryAt DateTime?
deliveredAt DateTime?
lastError String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id])
application SmsApplication @relation(fields: [applicationId], references: [id])
messageRecord SmsMessageRecord? @relation(fields: [messageRecordId], references: [id])
@@index([tenantId, status, createdAt])
@@index([applicationId, status, createdAt])
@@index([messageId])
@@index([messageRecordId])
@@index([deliveryType, status])
}
model GatewaySubmitDeadLetter {
id String @id @default(cuid())
streamMessageId String @unique
tenantId String?
applicationId String?
channelId String?
traceId String?
messageId String?
submitId String?
status String @default("pending")
failureCode String
failureMessage String
attempts Int @default(1)
maxAttempts Int @default(3)
commandPayload Json?
rawPayload String?
manualRetryCount Int @default(0)
lastRetryStreamId String?
lastRetriedAt DateTime?
resolvedAt DateTime?
resolvedStatus String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant? @relation(fields: [tenantId], references: [id])
application SmsApplication? @relation(fields: [applicationId], references: [id])
channel SmsChannel? @relation(fields: [channelId], references: [id])
@@index([status, createdAt])
@@index([tenantId, status, createdAt])
@@index([applicationId, status, createdAt])
@@index([channelId, status, createdAt])
@@index([messageId])
@@index([submitId])
}
model GatewayDownstreamRecoveryStatus {
id String @id @default(cuid())
account String @unique
tenantId String?
applicationId String?
gatewayInstanceId String?
state String
lockOwner String?
lockExpiresAt DateTime?
lastAttemptAt DateTime?
lastSuccessAt DateTime?
lastFailureAt DateTime?
nextRetryAt DateTime?
attemptCount Int @default(0)
failureCategory String?
lastError String?
lastSkipReason String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant? @relation(fields: [tenantId], references: [id])
application SmsApplication? @relation(fields: [applicationId], references: [id])
@@index([tenantId, state, updatedAt])
@@index([applicationId, state, updatedAt])
@@index([failureCategory, updatedAt])
@@index([lockOwner, lockExpiresAt])
@@index([state, updatedAt])
@@index([nextRetryAt])
}