diff --git a/kits/firestore-send-email/CHANGELOG.md b/kits/firestore-send-email/CHANGELOG.md index 27cdd5bb5..3fa1f9c9d 100644 --- a/kits/firestore-send-email/CHANGELOG.md +++ b/kits/firestore-send-email/CHANGELOG.md @@ -1,3 +1,5 @@ +- fix: deploy `processQueue` with the extension's runtime settings: `concurrency: 1`, `cpu: "gcf_gen1"` (0.1666 vCPU at 256MiB) and `ingressSettings: "ALLOW_INTERNAL_ONLY"`. `setGlobalOptions` can't change these. Before, the kit took the 2nd gen defaults of concurrency `80`, 1 vCPU and `ALLOW_ALL`. A burst of mail with attachments landed on one 256MiB instance, which ran out of memory and dropped every event in flight on it: the documents were left in `PENDING` or `PROCESSING`, or with no `delivery` field, and were never retried or marked as failed, so the mail was never sent. Existing kit deployments switch on their next deploy. + - fix: an Eventarc publish failure no longer fails the function. A channel that was deleted or is unreachable answers `PERMISSION_DENIED`, and the publish was awaited before the work the function exists to do, so the mail was never sent. The publish failure is now logged as a warning and the invocation continues. Deploys also declare `eventarcpublishing.googleapis.com`, so the API is enabled on your project whether or not you use events. - docs: the no-region fallback needs an explicit empty `DATABASE_REGION=` line in `.env`. Omitting the key entirely fails a non-interactive deploy, because the CLI resolves parameters before it reads their defaults. Corrects the note on the `DATABASE_REGION` mapping fix below; no behavior change. diff --git a/kits/firestore-send-email/README.md b/kits/firestore-send-email/README.md index 3bc671501..b54c65da9 100644 --- a/kits/firestore-send-email/README.md +++ b/kits/firestore-send-email/README.md @@ -280,6 +280,11 @@ a `TypeError` about reading `attachments` into `delivery.error`. It now writes - SendGrid is still selected by an `smtp.sendgrid.net` connection URI with the API key in `SMTP_PASSWORD`, and Outlook hosts still get their explicit transport configuration. +- The function still runs like the extension's: 0.1666 vCPU + (`cpu: "gcf_gen1"`), one event per instance (`concurrency: 1`) and + internal-only ingress, rather than the 2nd gen defaults of 1 vCPU, + concurrency `80` and `ALLOW_ALL`, so a burst of mail scales out across + instances instead of sharing one. `setGlobalOptions` can't change these. - The function still runs with a 120 second timeout, and `TLS_OPTIONS`, `DATABASE`, `USERS_COLLECTION` and `TEMPLATES_COLLECTION` behave as before. diff --git a/kits/firestore-send-email/src/index.ts b/kits/firestore-send-email/src/index.ts index e16a5f5ca..a0319bab6 100644 --- a/kits/firestore-send-email/src/index.ts +++ b/kits/firestore-send-email/src/index.ts @@ -108,6 +108,15 @@ function ensureInitialized(): Promise { export const processQueue = onDocumentWritten( { ...(deploy.region ? { region: deploy.region } : {}), + // The extension ran this function at 0.1666 vCPU, one event per instance, + // with internal-only ingress. Left unset, the CLI deploys the 2nd gen + // defaults: 1 vCPU, concurrency 80 and `ALLOW_ALL`. At concurrency 80, a + // burst of mail with attachments lands on one 256MiB instance, which runs + // out of memory and drops every event in flight on it. The trigger has no + // retry policy, so that mail is never sent. + concurrency: 1, + cpu: "gcf_gen1", + ingressSettings: "ALLOW_INTERNAL_ONLY", document: deploy.document, database: deploy.database, timeoutSeconds: 120, diff --git a/kits/firestore-send-email/tests/index.test.ts b/kits/firestore-send-email/tests/index.test.ts index ee37425b5..16b597241 100644 --- a/kits/firestore-send-email/tests/index.test.ts +++ b/kits/firestore-send-email/tests/index.test.ts @@ -89,4 +89,13 @@ describe("processQueue options", () => { const document = options.document as { toCEL(): string }; expect(document.toCEL()).toContain("params.MAIL_COLLECTION"); }); + + // Concurrency 80 let a burst of mail with attachments land on one 256MiB + // instance, which ran out of memory and dropped it. + test("the function runs one event per gen1 CPU instance with internal-only ingress", async () => { + const options = await loadTriggerOptions(); + expect(options.concurrency).toBe(1); + expect(options.cpu).toBe("gcf_gen1"); + expect(options.ingressSettings).toBe("ALLOW_INTERNAL_ONLY"); + }); });