mirror of
https://github.com/stack-auth/stack.git
synced 2026-07-20 21:29:36 +08:00
try-catch the email-queue step bulldozer-dependent item checks
This commit is contained in:
parent
f7ac51b51a
commit
fef19c6b0f
@ -708,44 +708,55 @@ async function processSingleEmail(context: TenancyProcessingContext, row: EmailO
|
||||
}
|
||||
|
||||
if (context.billingTeamId != null && row.sendRetries === 0 && arePlanLimitsEnforced()) {
|
||||
const app = getHexclaveServerApp();
|
||||
const emailItem = await app.getItem({ itemId: ITEM_IDS.emailsPerMonth, teamId: context.billingTeamId });
|
||||
const isDebited = await emailItem.tryDecreaseQuantity(1);
|
||||
if (!isDebited) {
|
||||
const errorMessage = "Monthly email sending limit exceeded for your plan. Please upgrade your plan or wait until next month.";
|
||||
// Intentionally do NOT increment sendRetries or append to
|
||||
// sendAttemptErrors. sendRetries tracks SMTP attempts, and a quota
|
||||
// rejection never reaches SMTP; the DB-level
|
||||
// EmailOutbox_sendAttemptErrors_requires_failure CHECK also forbids
|
||||
// non-null sendAttemptErrors while sendRetries is 0. All the
|
||||
// debugging info is captured in sendServerError* below. Keeping
|
||||
// sendRetries at 0 means that if a future admin flow ever
|
||||
// un-finalises this row (clearing finishedSendingAt), the
|
||||
// `sendRetries === 0` quota gate above re-fires instead of being
|
||||
// silently skipped.
|
||||
await globalPrismaClient.emailOutbox.update({
|
||||
where: {
|
||||
tenancyId_id: {
|
||||
tenancyId: row.tenancyId,
|
||||
id: row.id,
|
||||
// The monthly-email quota lives in bulldozer, but email delivery must not
|
||||
// hard-depend on it: if bulldozer is unreachable, report it and send the
|
||||
// email anyway (treat the quota as available) rather than failing
|
||||
// transactional mail such as password resets, sign-in codes, and team
|
||||
// invites. The quota is re-enforced on the next send once bulldozer is
|
||||
// back. A DB error while recording a quota rejection also lands here and
|
||||
// fails open, which is the same safe direction.
|
||||
try {
|
||||
const app = getHexclaveServerApp();
|
||||
const emailItem = await app.getItem({ itemId: ITEM_IDS.emailsPerMonth, teamId: context.billingTeamId });
|
||||
const isDebited = await emailItem.tryDecreaseQuantity(1);
|
||||
if (!isDebited) {
|
||||
const errorMessage = "Monthly email sending limit exceeded for your plan. Please upgrade your plan or wait until next month.";
|
||||
// Intentionally do NOT increment sendRetries or append to
|
||||
// sendAttemptErrors. sendRetries tracks SMTP attempts, and a quota
|
||||
// rejection never reaches SMTP; the DB-level
|
||||
// EmailOutbox_sendAttemptErrors_requires_failure CHECK also forbids
|
||||
// non-null sendAttemptErrors while sendRetries is 0. All the
|
||||
// debugging info is captured in sendServerError* below. Keeping
|
||||
// sendRetries at 0 means that if a future admin flow ever
|
||||
// un-finalises this row (clearing finishedSendingAt), the
|
||||
// `sendRetries === 0` quota gate above re-fires instead of being
|
||||
// silently skipped.
|
||||
await globalPrismaClient.emailOutbox.update({
|
||||
where: {
|
||||
tenancyId_id: {
|
||||
tenancyId: row.tenancyId,
|
||||
id: row.id,
|
||||
},
|
||||
finishedSendingAt: null,
|
||||
},
|
||||
finishedSendingAt: null,
|
||||
},
|
||||
data: {
|
||||
finishedSendingAt: new Date(),
|
||||
canHaveDeliveryInfo: false,
|
||||
sendServerErrorExternalMessage: errorMessage,
|
||||
sendServerErrorExternalDetails: { errorType: "monthly-email-limit-exceeded" },
|
||||
sendServerErrorInternalMessage: errorMessage,
|
||||
sendServerErrorInternalDetails: {
|
||||
errorType: "monthly-email-limit-exceeded",
|
||||
remainingQuota: emailItem.quantity,
|
||||
billingTeamId: context.billingTeamId,
|
||||
data: {
|
||||
finishedSendingAt: new Date(),
|
||||
canHaveDeliveryInfo: false,
|
||||
sendServerErrorExternalMessage: errorMessage,
|
||||
sendServerErrorExternalDetails: { errorType: "monthly-email-limit-exceeded" },
|
||||
sendServerErrorInternalMessage: errorMessage,
|
||||
sendServerErrorInternalDetails: {
|
||||
errorType: "monthly-email-limit-exceeded",
|
||||
remainingQuota: emailItem.quantity,
|
||||
billingTeamId: context.billingTeamId,
|
||||
},
|
||||
shouldUpdateSequenceId: true,
|
||||
},
|
||||
shouldUpdateSequenceId: true,
|
||||
},
|
||||
});
|
||||
return;
|
||||
});
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
captureError("email-queue-step:monthly-email-quota-check", error);
|
||||
}
|
||||
}
|
||||
const baseContent = {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user