swap to queue for emails#394
Conversation
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
corates | f2a922c | Commit Preview URL Branch Preview URL |
Mar 07 2026, 02:37 AM |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughMigrates email delivery from a Durable Object (EmailQueue) to Cloudflare Queues: removes the EmailQueue DO, its HTTP route and tests; adds a queue producer ( Changes
Sequence DiagramsequenceDiagram
participant App as Application / Route
participant Queue as Cloudflare Queue (Producer)
participant Consumer as Queue Consumer (Worker)
participant Postmark as Postmark API
App->>Queue: queueEmail(env, payload)
Note over Queue: Message enqueued (ack)
Queue-->>App: Enqueue ack
Consumer->>Queue: Receive batch
Queue->>Consumer: EmailPayload batch
Consumer->>Consumer: Validate payload
Consumer->>Postmark: Send email (html/text, replyTo?)
Postmark-->>Consumer: Success / Error
alt Success
Consumer->>Consumer: Log success
else Failure
Consumer->>Queue: Retry attempts (consumer-managed)
Note over Consumer: Dead-letter after max retries
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/workers/src/routes/billing/__tests__/invoiceHandlers.test.ts (1)
232-251:⚠️ Potential issue | 🟡 MinorMissing assertion: test does not verify email was queued.
The test is named "queues dunning email when user found and EMAIL_QUEUE available" but never asserts that
mockEmailQueue.sendwas actually called. This makes the test ineffective at verifying the email queuing behavior.Proposed fix to add assertion
const ctx = createTestContext(db, { env: { EMAIL_QUEUE: mockEmailQueue } }); const result = await handleInvoicePaymentFailed(invoice, ctx); expect(result.handled).toBe(true); expect(result.result).toBe('payment_failed_processed'); + expect(mockEmailQueue.send).toHaveBeenCalledTimes(1); + expect(mockEmailQueue.send).toHaveBeenCalledWith( + expect.objectContaining({ + to: 'billing@example.com', + }), + ); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/workers/src/routes/billing/__tests__/invoiceHandlers.test.ts` around lines 232 - 251, The test for "queues dunning email when user found and EMAIL_QUEUE available" is missing an assertion that the email queue was invoked; update the test in invoiceHandlers.test.ts to assert that mockEmailQueue.send was called with the expected payload after calling handleInvoicePaymentFailed(invoice, ctx). Locate the test that creates mockEmailQueue (mockEmailQueue = { send: vi.fn().mockResolvedValue(undefined) }) and add an assertion like expect(mockEmailQueue.send).toHaveBeenCalled() and optionally verify arguments (e.g., matching invoice.id, customer or a dunning template) to ensure handleInvoicePaymentFailed triggers the queue send.packages/workers/src/routes/contact.ts (1)
179-207:⚠️ Potential issue | 🟡 MinorAdd
replyTofield to enable contact form replies.The HTML email mentions "You can reply directly to this email to respond to ${name}" but the email payload doesn't include a
replyToheader. To enable this, add the field to the email queue infrastructure:
- Update
EmailPayloadinterface inpackages/workers/src/lib/email-queue.ts:export interface EmailPayload { to: string; subject: string; + replyTo?: string; html?: string; text?: string; }
- Update
SendEmailParamsinterface inpackages/workers/src/auth/email.ts:interface SendEmailParams { to: string; subject: string; + replyTo?: string; html: string; text: string; }
- Update Postmark API call in
packages/workers/src/auth/email.ts:const response = await postmark.sendEmail({ From: `CoRATES <${env.EMAIL_FROM || 'noreply@corates.org'}>`, To: to, Subject: subject, + ReplyTo: replyTo, HtmlBody: html, TextBody: text, MessageStream: 'outbound', });
- Update contact.ts to pass the field:
await queueEmail(env, { to: contactEmail, subject: `[Contact Form] ${subject || 'New Inquiry'}`, + replyTo: email, text: `New contact form submission:\n\nName: ${name}\nEmail: ${email}\nSubject: ${subject || 'Not specified'}\n\nMessage:\n${message}`,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/workers/src/routes/contact.ts` around lines 179 - 207, The HTML promises reply-by-email but the outgoing payload never sets a reply address; add a replyTo field through the email stack: extend the EmailPayload type in EmailPayload (packages/workers/src/lib/email-queue.ts) to include replyTo?: string, update SendEmailParams in the auth email module (SendEmailParams in packages/workers/src/auth/email.ts) to accept replyTo?: string, ensure the Postmark/Postmark client call that sends mail (the function that maps SendEmailParams to the Postmark API request in packages/workers/src/auth/email.ts) includes replyTo when constructing the API payload, and finally pass replyTo: email (the submitter address) when calling queueEmail from contact.ts (the queueEmail(...) invocation shown) so replies route back to the form submitter.
🧹 Nitpick comments (6)
packages/workers/src/routes/billing/handlers/invoiceHandlers.ts (1)
202-202: Make the comment intent-focused, not action-described.Line 202 repeats the code action and doesn’t add context. Prefer a brief “why” comment (or remove it if unnecessary), e.g., that dunning is queued to keep webhook handling non-blocking and resilient.
As per coding guidelines, "Comments should explain why something is being done, not what the code is doing" and "Reserve comments for explaining intent, context, workarounds, assumptions, edge cases, or limitations."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/workers/src/routes/billing/handlers/invoiceHandlers.ts` at line 202, Replace the action-focused inline comment "// Queue dunning email" with an intent-focused comment that explains why dunning is queued (for example: "Queue dunning email to keep webhook processing non-blocking and resilient, retrying delivery asynchronously") or remove the comment if the code is self-explanatory; update the comment near the dunning queueing call in invoiceHandlers.ts so it documents intent/context/assumptions rather than restating the action.packages/workers/src/routes/billing/__tests__/invoiceHandlers.test.ts (1)
266-287: Consider assertingsendwas not called when user is missing.To strengthen this test's verification that dunning email is skipped, consider asserting that
mockEmailQueue.sendwas never invoked.Proposed enhancement
expect(result.handled).toBe(true); expect(ctx.logger.stripe).toHaveBeenCalledWith('dunning_email_skipped_no_user', { subscriptionId: 'sub-local-1', stripeCustomerId: 'cus_orphan', }); + expect(mockEmailQueue.send).not.toHaveBeenCalled(); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/workers/src/routes/billing/__tests__/invoiceHandlers.test.ts` around lines 266 - 287, The test should also assert that the email queue's send method was not invoked when there is no user; add an assertion using the existing mockEmailQueue (mockEmailQueue.send) after calling handleInvoicePaymentFailed to verify it was never called, keeping the rest of the test (invoice object, ctx creation, and assertions on ctx.logger.stripe and result.handled) unchanged.packages/workers/src/routes/health.ts (1)
120-138: Consider adding queue health check for operational visibility.Since email delivery is now handled by a Cloudflare Queue, you may want to verify the
EMAIL_QUEUEqueue binding is available in health checks. This provides visibility into queue availability alongside other critical infrastructure.Suggested addition (optional)
+ // Check Queue bindings are available + try { + checks.services.queues = { + status: c.env.EMAIL_QUEUE ? 'healthy' : 'unhealthy', + type: 'Cloudflare Queue', + bindings: { + EMAIL_QUEUE: !!c.env.EMAIL_QUEUE, + }, + }; + } catch (error) { + const err = error as Error; + checks.services.queues = { + status: 'unhealthy', + type: 'Cloudflare Queue', + error: err.message, + }; + checks.status = 'degraded'; + }This would also require updating
ServiceStatusSchema,HealthResponseSchema, andHealthChecksinterface to includequeues.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/workers/src/routes/health.ts` around lines 120 - 138, Add a health check for the Cloudflare Queue binding by verifying c.env.EMAIL_QUEUE and adding a new entry under checks.queues (or checks.services.queues) similar to the Durable Objects check: set status to 'healthy' when c.env.EMAIL_QUEUE is present, include type 'Queue' and bindings.EMAIL_QUEUE flag, and mark overall checks.status 'degraded' on failure; then update the ServiceStatusSchema, HealthResponseSchema, and the HealthChecks interface to include the new queues property so the response type covers queue status. Target symbols: checks (in the health route), c.env.EMAIL_QUEUE, ServiceStatusSchema, HealthResponseSchema, and HealthChecks.packages/workers/src/__tests__/helpers.ts (1)
384-394: Consider usingvi.fn()for the defaultmockQueueto enable assertions.The
mockQueueuses plain async functions, which prevents tests usingcreateTestEnv()from asserting thatEMAIL_QUEUE.sendwas called. Tests likecontact.test.jswork around this by providing their own vi.fn() mocks viaenvOverrides, but tests relying on the default env won't be able to verify email queuing behavior.If email-send assertions are commonly needed, consider:
Proposed change
const mockQueue = { - send: async () => {}, - sendBatch: async () => {}, + send: vi.fn(async () => {}), + sendBatch: vi.fn(async () => {}), };This would require importing
vifrom vitest, which may have implications for non-test usage of this helper.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/workers/src/__tests__/helpers.ts` around lines 384 - 394, The default mockQueue uses plain async functions which prevents call assertions; update the mockQueue in createTestEnv (symbols: mockQueue, EMAIL_QUEUE) to use vi.fn() for send and sendBatch so tests can assert calls (e.g., EMAIL_QUEUE.send). Import vi from 'vitest' at the top of the test helper and replace the async arrow functions with vi.fn(async () => ...) or vi.fn(). Note that this introduces a test-only dependency (vitest) into the helper, so ensure the helper remains test-scoped or conditionally import vi if needed.packages/workers/src/lib/send-invitation-email.ts (1)
49-54: Consider static imports for core dependencies.The dynamic imports for
better-auth,drizzle-orm/d1, and@/db/schema.jsadd latency to each invocation. Since this helper is called during invitation flows which are user-facing, static imports at the module level would improve cold-start performance.Suggested refactor
+import { betterAuth } from 'better-auth'; +import { magicLink } from 'better-auth/plugins'; +import { drizzleAdapter } from 'better-auth/adapters/drizzle'; +import { drizzle } from 'drizzle-orm/d1'; +import * as schema from '@/db/schema.js'; +import { MAGIC_LINK_EXPIRY_MINUTES } from '@/auth/emailTemplates.js'; +import { getProjectInvitationEmailHtml, getProjectInvitationEmailText } from '@/auth/emailTemplates.js'; +import { escapeHtml } from '@/lib/escapeHtml.js'; import { createDomainError, SYSTEM_ERRORS } from '@corates/shared'; import type { Env } from '../types'; import { queueEmail } from './email-queue';Then remove the corresponding
await import(...)calls inside the function.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/workers/src/lib/send-invitation-email.ts` around lines 49 - 54, Replace the dynamic in-function imports with top-level static imports to reduce cold-start latency: remove the await import(...) calls inside send-invitation-email and instead import betterAuth, magicLink, drizzleAdapter, drizzle, schema, and MAGIC_LINK_EXPIRY_MINUTES as normal ES module imports at the module scope (referencing the same symbols betterAuth, magicLink, drizzleAdapter, drizzle, schema, MAGIC_LINK_EXPIRY_MINUTES) and update any usages in the function to use those statically imported bindings.packages/workers/src/routes/billing/handlers/dunning.ts (1)
99-100: Consider using static import forqueueEmail.The dynamic import here adds unnecessary runtime overhead since
queueEmailis a straightforward module with no circular dependency concerns. Static imports are preferred for simpler bundling and better error detection at build time.Suggested static import
Add to the imports at the top of the file:
import { queueEmail } from '../../../lib/email-queue';Then simplify the usage:
try { - const { queueEmail } = await import('../../../lib/email-queue'); await queueEmail(env, emailPayload);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/workers/src/routes/billing/handlers/dunning.ts` around lines 99 - 100, Replace the dynamic runtime import of queueEmail with a static import: add a top-level import for queueEmail (import { queueEmail } from '../../../lib/email-queue') and remove the await import(...) usage so you call queueEmail(env, emailPayload) directly; update any references in the dunning handler (where queueEmail is currently dynamically imported) to use the statically imported symbol.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/web/src/components/project/overview-tab/OverviewTab.jsx`:
- Around line 303-313: The TooltipTrigger usage incorrectly passes a render
function to the asChild prop; change TooltipTrigger (from `@ark-ui/solid`) to use
the boolean pattern by setting asChild (e.g., asChild or asChild={true}) and
move the span element inside TooltipTrigger as its single child, removing the
render-function signature and the {...item} spread; keep the existing span
classes and FiPlus element so the trigger behavior is merged onto that span.
---
Outside diff comments:
In `@packages/workers/src/routes/billing/__tests__/invoiceHandlers.test.ts`:
- Around line 232-251: The test for "queues dunning email when user found and
EMAIL_QUEUE available" is missing an assertion that the email queue was invoked;
update the test in invoiceHandlers.test.ts to assert that mockEmailQueue.send
was called with the expected payload after calling
handleInvoicePaymentFailed(invoice, ctx). Locate the test that creates
mockEmailQueue (mockEmailQueue = { send: vi.fn().mockResolvedValue(undefined) })
and add an assertion like expect(mockEmailQueue.send).toHaveBeenCalled() and
optionally verify arguments (e.g., matching invoice.id, customer or a dunning
template) to ensure handleInvoicePaymentFailed triggers the queue send.
In `@packages/workers/src/routes/contact.ts`:
- Around line 179-207: The HTML promises reply-by-email but the outgoing payload
never sets a reply address; add a replyTo field through the email stack: extend
the EmailPayload type in EmailPayload (packages/workers/src/lib/email-queue.ts)
to include replyTo?: string, update SendEmailParams in the auth email module
(SendEmailParams in packages/workers/src/auth/email.ts) to accept replyTo?:
string, ensure the Postmark/Postmark client call that sends mail (the function
that maps SendEmailParams to the Postmark API request in
packages/workers/src/auth/email.ts) includes replyTo when constructing the API
payload, and finally pass replyTo: email (the submitter address) when calling
queueEmail from contact.ts (the queueEmail(...) invocation shown) so replies
route back to the form submitter.
---
Nitpick comments:
In `@packages/workers/src/__tests__/helpers.ts`:
- Around line 384-394: The default mockQueue uses plain async functions which
prevents call assertions; update the mockQueue in createTestEnv (symbols:
mockQueue, EMAIL_QUEUE) to use vi.fn() for send and sendBatch so tests can
assert calls (e.g., EMAIL_QUEUE.send). Import vi from 'vitest' at the top of the
test helper and replace the async arrow functions with vi.fn(async () => ...) or
vi.fn(). Note that this introduces a test-only dependency (vitest) into the
helper, so ensure the helper remains test-scoped or conditionally import vi if
needed.
In `@packages/workers/src/lib/send-invitation-email.ts`:
- Around line 49-54: Replace the dynamic in-function imports with top-level
static imports to reduce cold-start latency: remove the await import(...) calls
inside send-invitation-email and instead import betterAuth, magicLink,
drizzleAdapter, drizzle, schema, and MAGIC_LINK_EXPIRY_MINUTES as normal ES
module imports at the module scope (referencing the same symbols betterAuth,
magicLink, drizzleAdapter, drizzle, schema, MAGIC_LINK_EXPIRY_MINUTES) and
update any usages in the function to use those statically imported bindings.
In `@packages/workers/src/routes/billing/__tests__/invoiceHandlers.test.ts`:
- Around line 266-287: The test should also assert that the email queue's send
method was not invoked when there is no user; add an assertion using the
existing mockEmailQueue (mockEmailQueue.send) after calling
handleInvoicePaymentFailed to verify it was never called, keeping the rest of
the test (invoice object, ctx creation, and assertions on ctx.logger.stripe and
result.handled) unchanged.
In `@packages/workers/src/routes/billing/handlers/dunning.ts`:
- Around line 99-100: Replace the dynamic runtime import of queueEmail with a
static import: add a top-level import for queueEmail (import { queueEmail } from
'../../../lib/email-queue') and remove the await import(...) usage so you call
queueEmail(env, emailPayload) directly; update any references in the dunning
handler (where queueEmail is currently dynamically imported) to use the
statically imported symbol.
In `@packages/workers/src/routes/billing/handlers/invoiceHandlers.ts`:
- Line 202: Replace the action-focused inline comment "// Queue dunning email"
with an intent-focused comment that explains why dunning is queued (for example:
"Queue dunning email to keep webhook processing non-blocking and resilient,
retrying delivery asynchronously") or remove the comment if the code is
self-explanatory; update the comment near the dunning queueing call in
invoiceHandlers.ts so it documents intent/context/assumptions rather than
restating the action.
In `@packages/workers/src/routes/health.ts`:
- Around line 120-138: Add a health check for the Cloudflare Queue binding by
verifying c.env.EMAIL_QUEUE and adding a new entry under checks.queues (or
checks.services.queues) similar to the Durable Objects check: set status to
'healthy' when c.env.EMAIL_QUEUE is present, include type 'Queue' and
bindings.EMAIL_QUEUE flag, and mark overall checks.status 'degraded' on failure;
then update the ServiceStatusSchema, HealthResponseSchema, and the HealthChecks
interface to include the new queues property so the response type covers queue
status. Target symbols: checks (in the health route), c.env.EMAIL_QUEUE,
ServiceStatusSchema, HealthResponseSchema, and HealthChecks.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
packages/docs/audits/durable-objects-migration-review-2026-02.mdis excluded by!packages/docs/audits/**packages/docs/audits/prod-audit-2026-01-19-full.mdis excluded by!packages/docs/audits/**
📒 Files selected for processing (32)
.cursor/rules/durable-objects.mdc.mcp/memory.db-walpackages/docs/STATUS.mdpackages/docs/architecture/diagrams/02-system-architecture.mdpackages/web/src/components/project/overview-tab/OverviewTab.jsxpackages/web/src/components/project/reconcile-tab/rob2-reconcile/pages/PreliminaryPage.jsxpackages/workers/src/__tests__/health.test.jspackages/workers/src/__tests__/helpers.tspackages/workers/src/auth/config.tspackages/workers/src/auth/email.tspackages/workers/src/config/constants.tspackages/workers/src/durable-objects/EmailQueue.tspackages/workers/src/durable-objects/ProjectDoc.tspackages/workers/src/durable-objects/UserSession.tspackages/workers/src/durable-objects/__tests__/EmailQueue.test.jspackages/workers/src/index.tspackages/workers/src/lib/email-queue.tspackages/workers/src/lib/send-invitation-email.tspackages/workers/src/routes/__tests__/contact.test.jspackages/workers/src/routes/__tests__/email.test.jspackages/workers/src/routes/__tests__/project-invitations.test.jspackages/workers/src/routes/account-merge.tspackages/workers/src/routes/billing/__tests__/invoiceHandlers.test.tspackages/workers/src/routes/billing/handlers/dunning.tspackages/workers/src/routes/billing/handlers/invoiceHandlers.tspackages/workers/src/routes/contact.tspackages/workers/src/routes/email.tspackages/workers/src/routes/health.tspackages/workers/src/routes/members.tspackages/workers/src/routes/orgs/invitations.tspackages/workers/src/routes/orgs/members.tspackages/workers/wrangler.jsonc
💤 Files with no reviewable changes (8)
- packages/workers/src/routes/tests/email.test.js
- .cursor/rules/durable-objects.mdc
- packages/workers/src/routes/email.ts
- packages/workers/src/config/constants.ts
- packages/workers/src/tests/health.test.js
- packages/workers/src/auth/email.ts
- packages/workers/src/durable-objects/tests/EmailQueue.test.js
- packages/workers/src/durable-objects/EmailQueue.ts
| <TooltipTrigger | ||
| asChild={item => ( | ||
| <span | ||
| {...item} | ||
| class='bg-secondary text-muted-foreground/70 inline-flex cursor-not-allowed items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-medium' | ||
| > | ||
| <FiPlus class='h-4 w-4' /> | ||
| Invite | ||
| </span> | ||
| )} | ||
| /> |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
@ark-ui/solid TooltipTrigger asChild render prop API documentation
💡 Result:
In @ark-ui/solid, the tooltip “trigger” part is Tooltip.Trigger (sometimes referred to as TooltipTrigger in other libs). It supports:
asChild (composition)
- Prop:
asChild?: boolean - Meaning: When
true, Ark won’t render its default trigger element; instead it merges the trigger props/behavior onto your single child element (your button/link/component). Use exactly one child. [1][2]
Solid example
import { Tooltip } from '@ark-ui/solid/tooltip'
export function Example() {
return (
<Tooltip.Root>
<Tooltip.Trigger asChild>
<a href="/docs">Docs</a>
</Tooltip.Trigger>
<Tooltip.Positioner>
<Tooltip.Content>Go to docs</Tooltip.Content>
</Tooltip.Positioner>
</Tooltip.Root>
)
}[1][2]
“Render prop” API (Tooltip state via function child)
Ark UI’s tooltip “render function” pattern is exposed via Tooltip.Context (not Tooltip.Trigger). Tooltip.Context takes a function child that receives the tooltip API (e.g. open). [1]
Solid example
<Tooltip.Positioner>
<Tooltip.Context>
{(api) => <Tooltip.Content>Open: {String(api.open)}</Tooltip.Content>}
</Tooltip.Context>
</Tooltip.Positioner>[1]
Sources: Ark UI Tooltip (Solid) docs [1], Ark UI Composition / asChild guide [2].
[1] https://ark-ui.com/solid/docs/components/tooltip
[2] https://ark-ui.com/react/docs/guides/composition
Replace asChild render function with boolean pattern.
The asChild prop in @ark-ui/solid's Tooltip.Trigger must be a boolean, not a render function. According to the library documentation, asChild={true} merges trigger behavior onto a single child element. Replace the render function pattern with:
<TooltipTrigger asChild>
<span
class='bg-secondary text-muted-foreground/70 inline-flex cursor-not-allowed items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-medium'
>
<FiPlus class='h-4 w-4' />
Invite
</span>
</TooltipTrigger>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/web/src/components/project/overview-tab/OverviewTab.jsx` around
lines 303 - 313, The TooltipTrigger usage incorrectly passes a render function
to the asChild prop; change TooltipTrigger (from `@ark-ui/solid`) to use the
boolean pattern by setting asChild (e.g., asChild or asChild={true}) and move
the span element inside TooltipTrigger as its single child, removing the
render-function signature and the {...item} spread; keep the existing span
classes and FiPlus element so the trigger behavior is merged onto that span.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
packages/workers/src/index.ts (2)
399-405: Duplicated retry delay calculation.The exponential backoff formula
Math.min(30 * 2 ** msg.attempts, 1800)is repeated in both error paths. Extract to a helper for maintainability.Proposed fix
+ const getRetryDelay = (attempts: number) => Math.min(30 * 2 ** attempts, 1800); + for (const msg of messages) { try { // ... } else { console.error(`[EmailQueue] Send returned error for ${msg.body.to}:`, result.error); - const delay = Math.min(30 * 2 ** msg.attempts, 1800); - msg.retry({ delaySeconds: delay }); + msg.retry({ delaySeconds: getRetryDelay(msg.attempts) }); } } catch (error) { console.error(`[EmailQueue] Exception sending to ${msg.body.to}:`, error); - const delay = Math.min(30 * 2 ** msg.attempts, 1800); - msg.retry({ delaySeconds: delay }); + msg.retry({ delaySeconds: getRetryDelay(msg.attempts) }); } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/workers/src/index.ts` around lines 399 - 405, Extract the duplicated exponential backoff calculation into a single helper function (e.g., getRetryDelay or computeRetryDelay) and use it wherever the code calls Math.min(30 * 2 ** msg.attempts, 1800); specifically replace the inline expression in the try/catch blocks that call msg.retry({ delaySeconds: ... }) with msg.retry({ delaySeconds: getRetryDelay(msg.attempts) }) and import/define the helper near the email processing logic (references: msg.retry, msg.attempts, and the surrounding send/email handling in packages/workers/src/index.ts).
384-408: Add type annotation tobatchparameter and importMessageBatchtype.The
batchparameter lacks a type annotation. For better type safety and IDE support, explicitly type it asMessageBatch<EmailPayload>from@cloudflare/workers-types.Proposed fix
- async queue(batch, env: Env): Promise<void> { + async queue(batch: MessageBatch<EmailPayload>, env: Env): Promise<void> { const emailService = createEmailService(env); - // eslint-disable-next-line no-undef -- Cloudflare Workers global type - const messages = batch.messages as Message<EmailPayload>[]; + const messages = batch.messages;Add the import at the top of the file:
import type { MessageBatch } from '@cloudflare/workers-types';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/workers/src/index.ts` around lines 384 - 408, The queue function's batch parameter should be explicitly typed to improve type safety: import the MessageBatch generic from '@cloudflare/workers-types' and change the queue signature to accept batch: MessageBatch<EmailPayload> (so references like messages: Message<EmailPayload>[] still type-check); add the import line for MessageBatch at the top of the file and ensure EmailPayload is the same type used elsewhere (e.g., in the messages cast and createEmailService usage).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/workers/src/index.ts`:
- Around line 398-405: The logs currently print plaintext recipient addresses
(msg.body.to) which exposes PII; create a small helper (e.g., maskEmail or
hashEmail) and use it where the code logs errors in the send/exception paths to
replace msg.body.to with a masked or hashed representation, then update both
console.error calls that reference msg.body.to to log the masked value instead
so addresses are never emitted in plaintext (keep masking deterministic if you
need correlation across logs, or truncate/anonymize for simple privacy).
- Around line 391-393: The call site is using a type cast to force EmailPayload
into SendEmailParams; instead either make SendEmailParams match EmailPayload
(make html and text optional) or tighten queueEmail to guarantee both fields and
keep SendEmailParams as-is—pick one and remove the unsafe `as` cast in the
emailService.sendEmail call. Locate types EmailPayload and SendEmailParams and
either relax SendEmailParams to optional html/text or add runtime validation in
queueEmail (and its callers) to ensure both fields are present, update the
function signatures accordingly, and remove the cast so TypeScript enforces
correctness without bypassing checks.
---
Nitpick comments:
In `@packages/workers/src/index.ts`:
- Around line 399-405: Extract the duplicated exponential backoff calculation
into a single helper function (e.g., getRetryDelay or computeRetryDelay) and use
it wherever the code calls Math.min(30 * 2 ** msg.attempts, 1800); specifically
replace the inline expression in the try/catch blocks that call msg.retry({
delaySeconds: ... }) with msg.retry({ delaySeconds: getRetryDelay(msg.attempts)
}) and import/define the helper near the email processing logic (references:
msg.retry, msg.attempts, and the surrounding send/email handling in
packages/workers/src/index.ts).
- Around line 384-408: The queue function's batch parameter should be explicitly
typed to improve type safety: import the MessageBatch generic from
'@cloudflare/workers-types' and change the queue signature to accept batch:
MessageBatch<EmailPayload> (so references like messages: Message<EmailPayload>[]
still type-check); add the import line for MessageBatch at the top of the file
and ensure EmailPayload is the same type used elsewhere (e.g., in the messages
cast and createEmailService usage).
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (8)
package.jsonpackages/landing/package.jsonpackages/mcp-memory/package.jsonpackages/mcp/package.jsonpackages/web/package.jsonpackages/workers/package.jsonpackages/workers/src/index.tspackages/workers/wrangler.jsonc
✅ Files skipped from review due to trivial changes (2)
- packages/landing/package.json
- package.json
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/workers/wrangler.jsonc
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Cursor Bugbot
- GitHub Check: Workers Builds: corates
- GitHub Check: Workers Builds: corates-workers-prod
- GitHub Check: prettier
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/yjs-sync.mdc)
**/*.{js,jsx,ts,tsx}: Use theuseProjecthook for managing Yjs connections with reference counting instead of creating Y.Doc instances directly
Never create Y.Doc instances directly; always use the connection registry managed by useProject
Access Y.Doc connection state via projectStore.getConnectionState(projectId) instead of checking connection state directly
Read Yjs-synced data from projectStore (using getStudies, getChecklist, etc.) rather than accessing Y.Doc maps/arrays directly
Use operation functions from useProject or projectActionsStore for writing data instead of directly modifying Y.Doc
Don't store Y.Doc references in component state; always retrieve the connection through useProject
Handle connection cleanup via onCleanup() in Solid.js components or equivalent cleanup patterns, calling disconnect() when the component unmounts
Files:
packages/workers/src/index.ts
packages/workers/src/**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/workers.mdc)
ALWAYS use
db.batch()for multiple related database operations in Drizzle to ensure atomicity - all operations must succeed or all fail together
Files:
packages/workers/src/index.ts
packages/workers/**/*.{js,ts}
📄 CodeRabbit inference engine (.cursor/rules/error-handling.mdc)
packages/workers/**/*.{js,ts}: Always usecreateDomainErrorfrom@corates/sharedfor backend error handling instead of manually creating error objects
Use predefined error constants from@corates/shared(PROJECT_ERRORS, AUTH_ERRORS, VALIDATION_ERRORS, SYSTEM_ERRORS, USER_ERRORS) instead of arbitrary error strings
Wrap database operations in try-catch blocks and return domain errors using createDomainError with SYSTEM_ERRORS.DB_ERROR
Use validation middleware (validateRequest) for request validation instead of manually validating request bodies in routes
Files:
packages/workers/src/index.ts
packages/{web,workers}/**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/error-handling.mdc)
Never throw string literals; always throw Error objects or return domain errors from API routes
Files:
packages/workers/src/index.ts
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/corates.mdc)
**/*.{js,ts,jsx,tsx}: Prefer modern ES6+ syntax and features
Comments should explain why something is being done, not what the code is doing
Reserve comments for explaining intent, context, workarounds, assumptions, edge cases, or limitations
Use TODO(agent): prefix for incomplete work or flagged items with brief description and relevant doc references
Files:
packages/workers/src/index.ts
packages/workers/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/corates.mdc)
packages/workers/src/**/*.{ts,tsx}: Use Zod for schema and input validation on backend services
Use Drizzle ORM for all database interactions and migrations
Use Better-Auth for authentication and user management
All project routes are org-scoped and use/api/orgs/:orgId/projects/...pattern in backend
UserequireOrgMembershipandrequireProjectAccessmiddleware for authentication in org-scoped routes
Never bypass Drizzle for database accessNever bypass Drizzle for database access - use Drizzle ORM for all database operations
Files:
packages/workers/src/index.ts
**/*
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Never use emojis or unicode symbols anywhere in code, comments, documentation, plan files, commit messages, or examples
Files:
packages/workers/src/index.tspackages/web/package.jsonpackages/workers/package.jsonpackages/mcp/package.jsonpackages/mcp-memory/package.json
packages/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
packages/**/*.{ts,tsx,js,jsx}: Prefer modern ES6+ syntax and features
Prefer config files over hardcoding values
Keep files small, focused, and modular - extract large files into sub-modules or separate utilities
Each file should handle one coherent responsibility
Comments should explain WHY something is being done or provide context, not repeat what the code is saying
Do NOT narrate what code is doing, don't duplicate function/variable names in comments, and don't leave stale comments that contradict the code
Use the Agent TODO convention// TODO(agent): Brief descriptionfor incomplete work, flagging items for future attention, known limitations, and documentation section referencesUse TODO(agent) convention with brief description and relevant doc section references for incomplete work or flagged issues
Files:
packages/workers/src/index.ts
packages/workers/src/**/*.ts
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
packages/workers/src/**/*.ts: Use Zod for schema and input validation in backend code
Use Drizzle ORM for ALL database interactions and migrations
Use Better-Auth for authentication and user management
Never bypass Drizzle for database access
Files:
packages/workers/src/index.ts
🧠 Learnings (29)
📚 Learning: 2025-12-27T03:01:35.601Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/durable-objects.mdc:0-0
Timestamp: 2025-12-27T03:01:35.601Z
Learning: Applies to packages/workers/src/durable-objects/**/ProjectDoc.{js,ts} : Use y-websocket protocol with message type 1 for awareness (presence) messages in ProjectDoc
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-17T00:25:35.716Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/corates.mdc:0-0
Timestamp: 2026-01-17T00:25:35.716Z
Learning: Applies to packages/workers/src/**/*.{ts,tsx} : Use Better-Auth for authentication and user management
Applied to files:
packages/workers/src/index.tspackages/workers/package.json
📚 Learning: 2026-01-05T01:00:58.113Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers-testing.mdc:0-0
Timestamp: 2026-01-05T01:00:58.113Z
Learning: Applies to {packages/workers/src/**/*.test.js,packages/workers/src/**/__tests__/**/*.js} : Only re-mock Postmark and Stripe modules inside a test file if you need behavior different from the global mock setup in packages/workers/src/__tests__/setup.js
Applied to files:
packages/workers/src/index.tspackages/workers/package.json
📚 Learning: 2025-12-27T03:01:35.601Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/durable-objects.mdc:0-0
Timestamp: 2025-12-27T03:01:35.601Z
Learning: Applies to packages/workers/src/durable-objects/**/*.{js,ts} : Remove WebSocket sessions from tracking on disconnect and perform cleanup
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2025-12-27T03:01:35.601Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/durable-objects.mdc:0-0
Timestamp: 2025-12-27T03:01:35.601Z
Learning: Applies to packages/workers/src/durable-objects/**/*.{js,ts} : Do not expose internal sync endpoints (/sync, /sync-member, /sync-pdf, /disconnect-all) publicly without X-Internal-Request verification
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-01T23:31:43.756Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/api-routes.mdc:0-0
Timestamp: 2026-01-01T23:31:43.756Z
Learning: Applies to packages/workers/src/routes/**/*.js : Use error constants from `corates/shared` (`PROJECT_ERRORS.*`, `AUTH_ERRORS.*`, `VALIDATION_ERRORS.*`, `SYSTEM_ERRORS.*`, `USER_ERRORS.*`) instead of creating error objects manually
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-01T23:31:43.756Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/api-routes.mdc:0-0
Timestamp: 2026-01-01T23:31:43.756Z
Learning: Applies to packages/workers/src/routes/**/*.js : ALWAYS use `createDomainError` from `corates/shared` for error handling in API routes
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-17T00:25:03.945Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/instructions/api-routes.instructions.md:0-0
Timestamp: 2026-01-17T00:25:03.945Z
Learning: Applies to packages/workers/**/routes/**/*.{js,ts} : Use error helpers from `corates/shared` (`notFound`, `forbidden`, `badRequest`, etc.) for error handling in API routes
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-17T00:25:35.716Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/corates.mdc:0-0
Timestamp: 2026-01-17T00:25:35.716Z
Learning: Applies to packages/workers/src/**/*.{ts,tsx} : Use `requireOrgMembership` and `requireProjectAccess` middleware for authentication in org-scoped routes
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-01T23:31:43.756Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/api-routes.mdc:0-0
Timestamp: 2026-01-01T23:31:43.756Z
Learning: Applies to packages/workers/src/routes/**/*.js : Apply authentication and authorization middleware in correct order: Authentication → Organization membership → Project access → Authorization → Validation → Route handler
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-05T01:00:58.113Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers-testing.mdc:0-0
Timestamp: 2026-01-05T01:00:58.113Z
Learning: Applies to {packages/workers/src/**/*.test.js,packages/workers/src/**/__tests__/**/*.js} : Mock src/middleware/auth.js requireAuth function in route tests by setting user/session via x-test-user-id and x-test-user-email headers
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-01T23:32:23.488Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers.mdc:0-0
Timestamp: 2026-01-01T23:32:23.488Z
Learning: Applies to packages/workers/src/routes/orgs/**/*.{js,ts,jsx,tsx} : Use `requireOrgMembership` and `requireProjectAccess` middleware instead of manual membership checks for org-scoped routes
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-01T23:31:43.756Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/api-routes.mdc:0-0
Timestamp: 2026-01-01T23:31:43.756Z
Learning: Applies to packages/workers/src/routes/orgs/**/*.js : Use organization-scoped route patterns with path structure `/api/orgs/:orgId/...` and leverage `requireOrgMembership`, `requireProjectAccess`, `getOrgContext`, and `getProjectContext` middleware
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-17T00:25:35.716Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/corates.mdc:0-0
Timestamp: 2026-01-17T00:25:35.716Z
Learning: Applies to packages/workers/src/**/*.{ts,tsx} : All project routes are org-scoped and use `/api/orgs/:orgId/projects/...` pattern in backend
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-01T23:32:23.488Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers.mdc:0-0
Timestamp: 2026-01-01T23:32:23.488Z
Learning: Applies to packages/workers/src/routes/orgs/**/*.{js,ts,jsx,tsx} : Use `getOrgContext(c)` to retrieve the validated orgId from context instead of manual checks - orgId is guaranteed valid by middleware
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-17T00:25:35.716Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/corates.mdc:0-0
Timestamp: 2026-01-17T00:25:35.716Z
Learning: Applies to packages/web/src/**/*.{ts,tsx} : Frontend uses orgSlug in URLs (`/orgs/:orgSlug/...`) for readability
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-05T01:00:58.113Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers-testing.mdc:0-0
Timestamp: 2026-01-05T01:00:58.113Z
Learning: Applies to {packages/workers/src/**/*.test.js,packages/workers/src/**/__tests__/**/*.js} : For integration tests, fetch against real worker env using app.fetch(req, env, ctx) with env from cloudflare:test, or use fetchApp() helper
Applied to files:
packages/workers/src/index.tspackages/workers/package.json
📚 Learning: 2025-12-27T03:02:05.951Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/pdf-handling.mdc:0-0
Timestamp: 2025-12-27T03:02:05.951Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Use PDF operations from `useProject` hook instead of bypassing through direct API calls
Applied to files:
packages/web/package.json
📚 Learning: 2026-01-17T16:09:36.920Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-17T16:09:36.920Z
Learning: Applies to packages/{web,landing}/src/**/*.{ts,tsx} : Use Ark UI components from `ark-ui/solid` for UI components
Applied to files:
packages/web/package.json
📚 Learning: 2026-01-17T00:25:35.716Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/corates.mdc:0-0
Timestamp: 2026-01-17T00:25:35.716Z
Learning: Applies to packages/web/src/**/*.{ts,tsx} : Use Ark UI component library (`ark-ui/solid`) for UI components
Applied to files:
packages/web/package.json
📚 Learning: 2026-01-17T16:09:36.920Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-17T16:09:36.920Z
Learning: Applies to packages/{web,landing}/src/**/*.{ts,tsx} : Use `solid-icons` icon library for all icon needs
Applied to files:
packages/web/package.json
📚 Learning: 2026-01-17T00:25:35.716Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/corates.mdc:0-0
Timestamp: 2026-01-17T00:25:35.716Z
Learning: Applies to packages/web/src/**/*.{ts,tsx} : Use `solid-icons` library for icons (e.g., `solid-icons/bi`, `solid-icons/fi`)
Applied to files:
packages/web/package.json
📚 Learning: 2025-12-27T03:02:05.951Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/pdf-handling.mdc:0-0
Timestamp: 2025-12-27T03:02:05.951Z
Learning: Applies to packages/web/src/**/*.{jsx,tsx} : Use `PdfViewer` component from `@/components/checklist-ui/pdf/PdfViewer.jsx` for displaying PDFs, passing pdfData as ArrayBuffer, fileName, readOnly, and onPageChange
Applied to files:
packages/web/package.json
📚 Learning: 2025-12-27T03:02:05.951Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/pdf-handling.mdc:0-0
Timestamp: 2025-12-27T03:02:05.951Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Use `cachePdf` and `getCachedPdf` from `primitives/pdfCache.js` for caching PDF data in IndexedDB
Applied to files:
packages/web/package.json
📚 Learning: 2026-01-17T16:09:36.920Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-17T16:09:36.920Z
Learning: Applies to packages/workers/src/**/*.ts : Use Better-Auth for authentication and user management
Applied to files:
packages/workers/package.json
📚 Learning: 2026-01-05T01:00:58.113Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers-testing.mdc:0-0
Timestamp: 2026-01-05T01:00:58.113Z
Learning: Applies to {packages/workers/src/**/*.test.js,packages/workers/src/**/__tests__/**/*.js} : Tests in packages/workers must run under cloudflare/vitest-pool-workers with global setup from packages/workers/src/__tests__/setup.js
Applied to files:
packages/workers/package.jsonpackages/mcp/package.jsonpackages/mcp-memory/package.json
📚 Learning: 2026-01-05T01:00:58.113Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers-testing.mdc:0-0
Timestamp: 2026-01-05T01:00:58.113Z
Learning: Applies to {packages/workers/src/**/*.test.js,packages/workers/src/**/__tests__/**/*.js} : Reuse provided helpers from packages/workers/src/__tests__/helpers.js (resetTestDatabase, clearProjectDOs, seedUser, seedOrganization, createTestEnv, fetchApp, json, createAuthHeaders) instead of implementing ad-hoc solutions
Applied to files:
packages/workers/package.jsonpackages/mcp-memory/package.json
📚 Learning: 2026-01-17T00:25:35.716Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/corates.mdc:0-0
Timestamp: 2026-01-17T00:25:35.716Z
Learning: Applies to packages/workers/src/**/*.{ts,tsx} : Use Zod for schema and input validation on backend services
Applied to files:
packages/workers/package.jsonpackages/mcp/package.jsonpackages/mcp-memory/package.json
📚 Learning: 2026-01-17T16:09:36.920Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-17T16:09:36.920Z
Learning: Applies to packages/workers/src/**/*.ts : Use Zod for schema and input validation in backend code
Applied to files:
packages/mcp/package.json
🧬 Code graph analysis (1)
packages/workers/src/index.ts (2)
packages/workers/src/auth/email.ts (1)
createEmailService(49-148)packages/workers/src/lib/email-queue.ts (1)
EmailPayload(10-15)
🔇 Additional comments (7)
packages/workers/package.json (1)
34-56: Dependency version bumps look reasonable.These are all minor/patch version bumps that should be backward compatible. The
@sentry/cloudflareupdate aligns with the commit message about fixing Sentry typing.packages/mcp/package.json (1)
23-23: LGTM — routine dependency bumps.Both version updates are minor/patch increments following semver conventions. The
@types/nodedev dependency change is low-risk. Consider verifying the@modelcontextprotocol/sdkversion to ensure compatibility with existing code.Also applies to: 27-27
packages/web/package.json (1)
22-50: Dependency version alignment looks good.The
@embedpdfpackage set is consistently bumped to ^2.7.0, and the@sentry/solidbump is isolated and clean with no blocking issues in this segment.packages/mcp-memory/package.json (2)
22-22: Dependency bump for@modelcontextprotocol/sdkis clean.This is a scoped, version-only update and looks good in this file context.
29-29:@types/nodepatch update looks safe.Dev dependency update is straightforward and consistent with the rest of the package manifest.
packages/workers/src/index.ts (2)
19-20: LGTM!Imports are appropriate for the new queue-based email processing flow.
38-38: LGTM!Export change correctly reflects the removal of the EmailQueue Durable Object.
| const result = await emailService.sendEmail( | ||
| msg.body as Parameters<typeof emailService.sendEmail>[0], | ||
| ); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's see the context around lines 391-393 in index.ts
sed -n '385,400p' packages/workers/src/index.tsRepository: InfinityBowman/corates
Length of output: 735
🏁 Script executed:
# Find EmailPayload definition
rg -n "type EmailPayload|interface EmailPayload" --type tsRepository: InfinityBowman/corates
Length of output: 140
🏁 Script executed:
# Find sendEmail function definition
rg -n "function sendEmail|const sendEmail|export.*sendEmail" packages/workers --type tsRepository: InfinityBowman/corates
Length of output: 278
🏁 Script executed:
# Check queueEmail calls as suggested in the review
rg -n -A5 "queueEmail\(" --type tsRepository: InfinityBowman/corates
Length of output: 4160
🏁 Script executed:
# Get the full EmailPayload interface definition
sed -n '10,20p' packages/workers/src/lib/email-queue.tsRepository: InfinityBowman/corates
Length of output: 399
🏁 Script executed:
# Check SendEmailParams type as well
rg -n -B2 -A5 "type SendEmailParams|interface SendEmailParams" --type tsRepository: InfinityBowman/corates
Length of output: 502
🏁 Script executed:
# Verify if messages are only queued via queueEmail or could be queued directly
rg -n "batch.publish|\.publish" packages/workers/src/index.ts -A3 -B3Repository: InfinityBowman/corates
Length of output: 48
Type mismatch between EmailPayload and SendEmailParams requires runtime validation fallback.
EmailPayload defines html and text as optional (either one can be provided), but sendEmail requires both fields. The as cast bypasses TypeScript's type checking. However, queueEmail provides runtime protection by validating that at least one field exists before queuing. To eliminate the type cast and improve type safety, align the interfaces: either update SendEmailParams to match EmailPayload's optional fields, or enforce that queueEmail requires both fields.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/workers/src/index.ts` around lines 391 - 393, The call site is using
a type cast to force EmailPayload into SendEmailParams; instead either make
SendEmailParams match EmailPayload (make html and text optional) or tighten
queueEmail to guarantee both fields and keep SendEmailParams as-is—pick one and
remove the unsafe `as` cast in the emailService.sendEmail call. Locate types
EmailPayload and SendEmailParams and either relax SendEmailParams to optional
html/text or add runtime validation in queueEmail (and its callers) to ensure
both fields are present, update the function signatures accordingly, and remove
the cast so TypeScript enforces correctness without bypassing checks.
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
packages/web/src/primitives/useProject/index.js (1)
265-289:⚠️ Potential issue | 🟠 MajorAfter corruption detection, code continues with broken persistence references.
When corrupted state is detected,
deleteProjectData(projectId)is called but execution continues to set up theupdateHandler(line 280) which capturesproject.ydoc. Since the project row was just deleted, future updates viaY.applyUpdate(project.ydoc, update, 'dexie-sync')at line 283 will reference a deleted document, potentially throwing errors or silently failing.For local-only projects this is especially problematic—all data is silently lost, but the persistence handler still tries to write to the deleted document.
Consider returning early after corruption to force re-initialization:
Proposed fix: Return early after corruption to trigger re-initialization
try { const persistedState = Y.encodeStateAsUpdate(project.ydoc); Y.applyUpdate(ydoc, persistedState); } catch (err) { console.error('Corrupted persisted state, clearing local data:', err); - deleteProjectData(projectId).catch(() => {}); + deleteProjectData(projectId).catch(delErr => { + console.error('Failed to delete corrupted project data:', delErr); + }); + // Re-create the project row so persistence can restart cleanly + db.projects.put({ id: projectId, updatedAt: Date.now() }).catch(() => {}); + // Exit early - syncFromYDoc will run with empty state, persistence will work with fresh row + isLoadingPersistedState = false; + connectionEntry.syncManager.syncFromYDoc(); + if (isLocalProject()) { + projectStore.setConnectionState(projectId, { + connecting: false, + connected: true, + synced: true, + }); + } + return; } finally { isLoadingPersistedState = false; }Alternatively, wrap the entire post-load setup in the try block so it only runs on success.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/web/src/primitives/useProject/index.js` around lines 265 - 289, When corrupted persisted state is detected in the whenLoaded handler, stop further setup so we don't attach persistence handlers that reference a deleted project; after calling deleteProjectData(projectId) return early (or rethrow) to avoid running the rest of the post-load setup (the updateHandler closure that calls Y.applyUpdate(project.ydoc, update, 'dexie-sync'), ydoc.on('update', ...), and connectionEntry.syncManager.syncFromYDoc), or alternatively move the post-load setup (creation of updateHandler, ydoc.on('update', ...) and syncFromYDoc call) inside the try block so they only run when Y.encodeStateAsUpdate/project.ydoc application succeeds.packages/workers/src/routes/billing/__tests__/subscriptionHandlers.test.ts (1)
355-387:⚠️ Potential issue | 🟡 MinorAssert the resumed period dates, not just the status.
This fixture was updated to the new
items.data[0].current_period_*shape, but the test only checksupdated.status. Please also verify thatperiodStartandperiodEndwere persisted from those nested fields so the regression this PR is fixing is actually covered.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/workers/src/routes/billing/__tests__/subscriptionHandlers.test.ts` around lines 355 - 387, The test only checks status; also assert that the resumed period dates from stripeSub.items.data[0].current_period_start/current_period_end were persisted via handleSubscriptionResumed: read the two values from the fixture (items.data[0].current_period_start and current_period_end), convert them to the DB-stored format (e.g., JS Date by multiplying by 1000 if DB uses Date), then add expectations against the fetched updated record's periodStart and periodEnd fields to ensure they match the converted expectedStart and expectedEnd.packages/workers/src/routes/billing/handlers/subscriptionHandlers.ts (1)
144-157:⚠️ Potential issue | 🟠 MajorPreserve existing billing dates when item periods are missing.
On Line 149 and Line 152, a missing
firstItem.current_period_*now clearsperiodStartandperiodEndtonull. For an existing subscription row, that silently loses billing state if Stripe sends a partial item payload. Please fall back to the stored values here, and apply the same guard inhandleSubscriptionResumed().Proposed fix
await db .update(subscription) .set({ status: sub.status, periodStart: - firstItem?.current_period_start ? new Date(firstItem.current_period_start * 1000) : null, + firstItem?.current_period_start + ? new Date(firstItem.current_period_start * 1000) + : existing.periodStart, periodEnd: - firstItem?.current_period_end ? new Date(firstItem.current_period_end * 1000) : null, + firstItem?.current_period_end + ? new Date(firstItem.current_period_end * 1000) + : existing.periodEnd, cancelAtPeriodEnd: sub.cancel_at_period_end, cancelAt: sub.cancel_at ? new Date(sub.cancel_at * 1000) : null, canceledAt: sub.canceled_at ? new Date(sub.canceled_at * 1000) : null, - plan: sub.items.data[0]?.price?.lookup_key || existing.plan, + plan: firstItem?.price?.lookup_key || existing.plan, updatedAt: new Date(), })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/workers/src/routes/billing/handlers/subscriptionHandlers.ts` around lines 144 - 157, The update is overwriting periodStart/periodEnd with null when firstItem.current_period_start/current_period_end are missing; change the db.update(...) .set mapping to fall back to the existing subscription row values (e.g., use existing.periodStart and existing.periodEnd) instead of null so missing item periods don’t wipe stored billing dates, and make the same change inside handleSubscriptionResumed() to guard against partial Stripe item payloads.
🧹 Nitpick comments (3)
packages/web/src/primitives/useProject/connection.js (1)
86-106: Good defensive enhancement for awareness resilience.The try/catch wrapper appropriately isolates ephemeral awareness failures from breaking the provider. The array clone before mutation is correct.
Minor hardening: consider accessing the error message defensively in case a non-Error is thrown.
Defensive error message access
} catch (err) { - console.warn('Awareness update skipped:', err.message); + console.warn('Awareness update skipped:', err instanceof Error ? err.message : err); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/web/src/primitives/useProject/connection.js` around lines 86 - 106, The catch block in the AWARENESS_MSG_TYPE wrapper assumes thrown err has a message property; update the catch to defensively derive a string (e.g., const msg = (err && err.message) || String(err)) and use that in the console.warn so non-Error throws are handled; modify the wrapper around originalAwarenessHandler (provider.messageHandlers[AWARENESS_MSG_TYPE]) to compute a safe message and optionally include the full err object in the log for debugging..cursor/mcp.json (1)
19-22: Consider adding explicittypefield for consistency.The sentry entry in
.mcp.jsonincludes"type": "http", but this file omits it. Adding the explicit type would maintain consistency between configuration files.♻️ Suggested fix
}, "sentry": { + "type": "http", "url": "https://mcp.sentry.dev/mcp" }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.cursor/mcp.json around lines 19 - 22, The sentry configuration is missing an explicit "type" field; update the "sentry" object to include "type": "http" alongside the existing "url" property so it matches other config files and keeps schema consistency (i.e., add the "type" key inside the "sentry" object that currently contains "url").packages/workers/src/routes/contact.ts (1)
179-210: Make the returnedmessageIdtraceable through the queue pipeline.The UUID returned here is generated after enqueueing and is not attached to the queued payload, so clients and support cannot correlate it with queue processing or Postmark delivery logs. If you want to expose a receipt ID, generate it before enqueueing and include it in the payload/logging; otherwise the field is misleading.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/workers/src/routes/contact.ts` around lines 179 - 210, Generate the messageId before calling queueEmail and attach it to the queued payload so the same UUID can be correlated end-to-end; e.g., create a messageId via crypto.randomUUID() prior to invoking queueEmail and include that messageId field in the object passed to queueEmail, and return that same messageId from the c.json response (the affected symbols are queueEmail and the return call to c.json in contact.ts).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/web/package.json`:
- Line 55: Update the codebase to be compatible with better-auth 1.5.4: replace
any removed API usages (createAdapter, Adapter) and types (InferUser,
InferSession) with the new equivalents (Adapter replacements and User, Session),
change imports that used the removed barrel `@better-auth/core/utils` to explicit
subpath imports (e.g., `@better-auth/core/utils/id`), replace calls to the removed
/forget-password/email-otp flow with the standard password reset endpoints and
verify magic link/verification handlers, update plugin error code definitions to
use defineErrorCodes(), and audit code that relied on "after" DB hooks running
inside transactions (update logic to account for hooks running after commit);
search for these symbols (createAdapter, Adapter, InferUser, InferSession,
defineErrorCodes, `@better-auth/core/utils`) to locate and update all occurrences.
In
`@packages/web/src/components/project/reconcile-tab/rob2-reconcile/pages/PreliminaryPage.jsx`:
- Line 1: The preliminary NoteEditor edits are updating Y.Text but not
persisting into the reconciled checklist, so restore a bridge that calls
props.onFinalChange (which flows to updateChecklistAnswer) when the editor
content changes: add an explicit onChange (or onFinalChange) prop to
NoteEditor.jsx and invoke it from the editor change handler (in NoteEditor.jsx
around the current Y.Text update logic), then in ROB2Reconciliation.jsx wire
that prop to the existing props.onFinalChange/updateChecklistAnswer path so
props.finalValue is written into reconciledChecklist; ensure the same fix is
applied to both referenced locations (around lines ~323-329 and ~365-377) so
manual edits persist.
In `@packages/workers/src/auth/config.ts`:
- Around line 182-184: The current await queueEmail(env, { to: email, subject,
html, text }).catch(...) swallows enqueue errors; change it to await
queueEmail(env, { to: email, subject, html, text }) without a catch, or catch,
log via console.error('[Auth] Magic link email queue error:', err) and then
rethrow the error (throw err) so the failure propagates to the caller; apply the
same fix to the other two occurrences that use queueEmail in the auth email
hooks (the other blocks that currently log-and-resolve) so auth-critical flows
can surface retryable errors.
In `@packages/workers/src/lib/email-queue.ts`:
- Around line 10-24: Replace the hand-rolled truthiness check in queueEmail with
a reusable Zod schema: define a z.object schema for EmailPayload (fields to,
subject, html?, text?, replyTo?) and add a .refine(...) that enforces at least
one of html or text is present; then validate the incoming payload via
schema.parse or safeParse inside queueEmail and on failure throw
createDomainError(VALIDATION_ERRORS.INVALID_INPUT, ...) with the schema error
details instead of the manual check. Ensure the schema (e.g.,
EmailPayloadSchema) is exported/placed so other callers can reuse it across
email flows.
In `@packages/workers/src/lib/send-invitation-email.ts`:
- Around line 106-108: The code currently logs the full invitation magic link
(capturedMagicLinkUrl) when env.ENVIRONMENT !== 'production', exposing a bearer
token; remove that console.log or replace it with a redacted version that strips
or masks the token/query string (e.g., remove everything after the path or
replace the token value with ****) before logging. Update the block around
capturedMagicLinkUrl (in send-invitation-email.ts) so you never emit the raw URL
to logs, keeping only non-sensitive context such as the host/path or a masked
token.
- Around line 13-35: Add runtime input validation to sendInvitationEmail by
creating a Zod schema that mirrors SendInvitationEmailParams (e.g.,
SendInvitationEmailSchema = z.object({ env: z.any(), email: z.string().email(),
token: z.string(), projectName: z.string(), inviterName: z.string(), role:
z.string() })) and validate the incoming params at the top of the
sendInvitationEmail function using parse or safeParse; use the parsed result for
downstream logic (or throw a clear error/return a predictable failure when
validation fails). Replace direct usage of the TypeScript-only
SendInvitationEmailParams with the validated value (or infer the TS type from
the schema) so malformed request data fails early and predictably. Ensure the
validation is imported from zod and that any error messages include enough
context for callers.
In `@packages/workers/src/routes/billing/handlers/invoiceHandlers.ts`:
- Around line 19-20: Update InvoiceWithSubscription and the three handlers
(handleInvoicePaymentSucceeded, handleInvoicePaymentFailed,
handleInvoiceFinalized) to read the subscription ID from the new invoice
structure at invoice.parent.subscription_details.subscription, falling back to
the legacy invoice.subscription when present for backward compatibility; modify
the type/interface so it reflects optional paths (e.g., subscription?: string
and parent?: { subscription_details?: { subscription?: string } }) and change
the handlers' checks to extract subscriptionId =
invoice.parent?.subscription_details?.subscription || invoice.subscription
before deciding if the invoice is a subscription invoice so both old and new
webhook payloads are handled.
---
Outside diff comments:
In `@packages/web/src/primitives/useProject/index.js`:
- Around line 265-289: When corrupted persisted state is detected in the
whenLoaded handler, stop further setup so we don't attach persistence handlers
that reference a deleted project; after calling deleteProjectData(projectId)
return early (or rethrow) to avoid running the rest of the post-load setup (the
updateHandler closure that calls Y.applyUpdate(project.ydoc, update,
'dexie-sync'), ydoc.on('update', ...), and
connectionEntry.syncManager.syncFromYDoc), or alternatively move the post-load
setup (creation of updateHandler, ydoc.on('update', ...) and syncFromYDoc call)
inside the try block so they only run when Y.encodeStateAsUpdate/project.ydoc
application succeeds.
In `@packages/workers/src/routes/billing/__tests__/subscriptionHandlers.test.ts`:
- Around line 355-387: The test only checks status; also assert that the resumed
period dates from
stripeSub.items.data[0].current_period_start/current_period_end were persisted
via handleSubscriptionResumed: read the two values from the fixture
(items.data[0].current_period_start and current_period_end), convert them to the
DB-stored format (e.g., JS Date by multiplying by 1000 if DB uses Date), then
add expectations against the fetched updated record's periodStart and periodEnd
fields to ensure they match the converted expectedStart and expectedEnd.
In `@packages/workers/src/routes/billing/handlers/subscriptionHandlers.ts`:
- Around line 144-157: The update is overwriting periodStart/periodEnd with null
when firstItem.current_period_start/current_period_end are missing; change the
db.update(...) .set mapping to fall back to the existing subscription row values
(e.g., use existing.periodStart and existing.periodEnd) instead of null so
missing item periods don’t wipe stored billing dates, and make the same change
inside handleSubscriptionResumed() to guard against partial Stripe item
payloads.
---
Nitpick comments:
In @.cursor/mcp.json:
- Around line 19-22: The sentry configuration is missing an explicit "type"
field; update the "sentry" object to include "type": "http" alongside the
existing "url" property so it matches other config files and keeps schema
consistency (i.e., add the "type" key inside the "sentry" object that currently
contains "url").
In `@packages/web/src/primitives/useProject/connection.js`:
- Around line 86-106: The catch block in the AWARENESS_MSG_TYPE wrapper assumes
thrown err has a message property; update the catch to defensively derive a
string (e.g., const msg = (err && err.message) || String(err)) and use that in
the console.warn so non-Error throws are handled; modify the wrapper around
originalAwarenessHandler (provider.messageHandlers[AWARENESS_MSG_TYPE]) to
compute a safe message and optionally include the full err object in the log for
debugging.
In `@packages/workers/src/routes/contact.ts`:
- Around line 179-210: Generate the messageId before calling queueEmail and
attach it to the queued payload so the same UUID can be correlated end-to-end;
e.g., create a messageId via crypto.randomUUID() prior to invoking queueEmail
and include that messageId field in the object passed to queueEmail, and return
that same messageId from the c.json response (the affected symbols are
queueEmail and the return call to c.json in contact.ts).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 7a4d04cf-700e-4f33-b1fc-ea7fa7d64608
⛔ Files ignored due to path filters (2)
.mcp/memory.dbis excluded by!**/*.dbpnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (26)
.cursor/mcp.json.mcp.json.vscode/mcp.jsonpackage.jsonpackages/landing/package.jsonpackages/mcp-memory/package.jsonpackages/mcp/package.jsonpackages/web/package.jsonpackages/web/src/components/project/reconcile-tab/rob2-reconcile/pages/PreliminaryPage.jsxpackages/web/src/primitives/useProject/connection.jspackages/web/src/primitives/useProject/index.jspackages/workers/package.jsonpackages/workers/scripts/setup-stripe-test.mjspackages/workers/src/auth/config.tspackages/workers/src/auth/email.tspackages/workers/src/durable-objects/ProjectDoc.tspackages/workers/src/lib/email-queue.tspackages/workers/src/lib/send-invitation-email.tspackages/workers/src/lib/stripe.tspackages/workers/src/routes/admin/stripe-tools.tspackages/workers/src/routes/billing/__tests__/invoiceHandlers.test.tspackages/workers/src/routes/billing/__tests__/subscriptionHandlers.test.tspackages/workers/src/routes/billing/handlers/invoiceHandlers.tspackages/workers/src/routes/billing/handlers/subscriptionHandlers.tspackages/workers/src/routes/contact.tspnpm-workspace.yaml
✅ Files skipped from review due to trivial changes (1)
- .vscode/mcp.json
🚧 Files skipped from review as they are similar to previous changes (6)
- packages/mcp-memory/package.json
- packages/mcp/package.json
- packages/workers/src/routes/billing/tests/invoiceHandlers.test.ts
- packages/workers/package.json
- package.json
- packages/landing/package.json
| "@tanstack/solid-query": "^5.90.23", | ||
| "@tanstack/solid-table": "^8.21.3", | ||
| "better-auth": "^1.4.19", | ||
| "better-auth": "^1.5.4", |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
better-auth 1.5 release notes breaking changes
💡 Result:
Better Auth 1.5 (released Mar 1, 2026): breaking changes
- Removed endpoint:
/forget-password/email-otpwas removed (use the standard password reset flow instead). [1] - Removed export:
better-auth/adapters/testwas removed (use thetestUtilsplugin instead). [1] - API key plugin extraction:
apiKeymoved out ofbetter-auth/pluginsinto@better-auth/api-key(update your import). [1]- Schema:
ApiKey.userId→referenceId; addedconfigId(default"default"). [1] - Options:
permissions.defaultPermissionsfirst arg changeduserId→referenceId. [1] - Client SDK:
apiKey.userId→apiKey.referenceId; newapiKey.references(owner type) andapiKey.configId. [1]
- Schema:
- Previously
@deprecatedAPIs removed (notable examples):createAdapter→createAdapterFactory,Adapter→DBAdapter,TransactionAdapter→DBTransactionAdapter, plus several client/helper types and option names. [2] - Core utils import change:
@better-auth/core/utilsbarrel export removed; must import from subpaths (e.g.@better-auth/core/utils/id,/json,/error-codes). [2] - Plugin error codes typing: plugin
$ERROR_CODESnow expectsRecord<string, RawError>(usedefineErrorCodes(), which now yields{code, message}objects). [3] - Type removals:
InferUser/InferSessiontypes removed (use genericUser/Sessiontypes instead). [3] - Hook timing: database “after” hooks now run after the transaction commits (not inside the transaction). [4]
- Import path change:
getMigrationsmoved tobetter-auth/db/migration. [4] - Secondary storage session shape:
session.idremoved from sessions in secondary storage. [4] - Plugin init context semantics: plugin
init()context is now mutable and shared across lifecycle;init()can return extra context keys. [4]
Sources: Better Auth 1.5 release post + changelog entry for v1.5.0. [1][2]
Update better-auth to 1.5.4 and verify auth flow migrations.
Better-auth 1.5.4 includes breaking changes that directly impact this PR's password reset flow:
/forget-password/email-otpendpoint was removed; use the standard password reset flow insteadcreateAdapter,Adapter, and other deprecated APIs were removed@better-auth/core/utilsbarrel export removed; imports must use subpaths (e.g.,@better-auth/core/utils/id)- Type removals:
InferUserandInferSessionreplaced withUserandSession - Database "after" hooks now run after transaction commits (not during)
- Plugin error codes typing requires
defineErrorCodes()helper
Verify all email flows (magic links, verification, password resets) still work with these changes and update imports accordingly.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/web/package.json` at line 55, Update the codebase to be compatible
with better-auth 1.5.4: replace any removed API usages (createAdapter, Adapter)
and types (InferUser, InferSession) with the new equivalents (Adapter
replacements and User, Session), change imports that used the removed barrel
`@better-auth/core/utils` to explicit subpath imports (e.g.,
`@better-auth/core/utils/id`), replace calls to the removed
/forget-password/email-otp flow with the standard password reset endpoints and
verify magic link/verification handlers, update plugin error code definitions to
use defineErrorCodes(), and audit code that relied on "after" DB hooks running
inside transactions (update logic to account for hooks running after commit);
search for these symbols (createAdapter, Adapter, InferUser, InferSession,
defineErrorCodes, `@better-auth/core/utils`) to locate and update all occurrences.
| export interface EmailPayload { | ||
| to: string; | ||
| subject: string; | ||
| html?: string; | ||
| text?: string; | ||
| replyTo?: string; | ||
| } | ||
|
|
||
| export async function queueEmail(env: Env, payload: EmailPayload): Promise<void> { | ||
| if (!payload?.to || !payload?.subject || (!payload?.html && !payload?.text)) { | ||
| throw createDomainError( | ||
| VALIDATION_ERRORS.INVALID_INPUT, | ||
| { fields: ['to', 'subject', 'html', 'text'] }, | ||
| 'Email payload requires to, subject, and html or text', | ||
| ); |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Use a shared Zod schema for queued email payloads.
This helper is now the backend validation boundary for multiple email flows, but it still relies on a single truthiness check. Converting this to a z.object(...).refine(...) schema keeps the contract reusable across callers and avoids hand-rolled validation drift.
As per coding guidelines, packages/workers/src/**/*.{ts,tsx}: Use Zod for schema and input validation on backend services.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/workers/src/lib/email-queue.ts` around lines 10 - 24, Replace the
hand-rolled truthiness check in queueEmail with a reusable Zod schema: define a
z.object schema for EmailPayload (fields to, subject, html?, text?, replyTo?)
and add a .refine(...) that enforces at least one of html or text is present;
then validate the incoming payload via schema.parse or safeParse inside
queueEmail and on failure throw
createDomainError(VALIDATION_ERRORS.INVALID_INPUT, ...) with the schema error
details instead of the manual check. Ensure the schema (e.g.,
EmailPayloadSchema) is exported/placed so other callers can reuse it across
email flows.
| interface SendInvitationEmailParams { | ||
| env: Env; | ||
| email: string; | ||
| token: string; | ||
| projectName: string; | ||
| inviterName: string; | ||
| role: string; | ||
| } | ||
|
|
||
| interface SendInvitationEmailResult { | ||
| emailQueued: boolean; | ||
| } | ||
|
|
||
| /** | ||
| * Generates a magic link and queues an invitation email. | ||
| * | ||
| * Throws on critical errors (missing AUTH_SECRET, failed magic link generation). | ||
| * Returns { emailQueued: false } if only the queue send fails, since the | ||
| * invitation record already exists in the database and can be resent. | ||
| */ | ||
| export async function sendInvitationEmail( | ||
| params: SendInvitationEmailParams, | ||
| ): Promise<SendInvitationEmailResult> { |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Add runtime validation for this shared backend entry point.
SendInvitationEmailParams is only a TypeScript type right now. Since this helper builds auth URLs and email payloads from request-derived strings, please parse params with a Zod schema before using it so bad input fails early and predictably.
As per coding guidelines, "packages/workers/src/**/*.{ts,tsx}: Use Zod for schema and input validation on backend services".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/workers/src/lib/send-invitation-email.ts` around lines 13 - 35, Add
runtime input validation to sendInvitationEmail by creating a Zod schema that
mirrors SendInvitationEmailParams (e.g., SendInvitationEmailSchema = z.object({
env: z.any(), email: z.string().email(), token: z.string(), projectName:
z.string(), inviterName: z.string(), role: z.string() })) and validate the
incoming params at the top of the sendInvitationEmail function using parse or
safeParse; use the parsed result for downstream logic (or throw a clear
error/return a predictable failure when validation fails). Replace direct usage
of the TypeScript-only SendInvitationEmailParams with the validated value (or
infer the TS type from the schema) so malformed request data fails early and
predictably. Ensure the validation is imported from zod and that any error
messages include enough context for callers.
| // The subscription field moved to invoice.parent.subscription_details.subscription in newer | ||
| // API versions, but webhook payloads may still include it at the top level |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n packages/workers/src/routes/billing/handlers/invoiceHandlers.ts | head -100Repository: InfinityBowman/corates
Length of output: 3761
🏁 Script executed:
# Search for all occurrences of invoice.subscription in the file
rg "invoice\.subscription" packages/workers/src/routes/billing/handlers/invoiceHandlers.ts -A 2 -B 2Repository: InfinityBowman/corates
Length of output: 48
🏁 Script executed:
# Search for references to invoice.parent.subscription_details
rg "invoice\.parent|subscription_details" packages/workers/src/routes/billing/handlers/invoiceHandlers.ts -A 2 -B 2Repository: InfinityBowman/corates
Length of output: 296
🏁 Script executed:
# Get more lines to see the full handlers
cat -n packages/workers/src/routes/billing/handlers/invoiceHandlers.ts | head -200Repository: InfinityBowman/corates
Length of output: 7309
🏁 Script executed:
# Search for all handlers in this file
rg "export async function handle" packages/workers/src/routes/billing/handlers/invoiceHandlers.tsRepository: InfinityBowman/corates
Length of output: 214
🏁 Script executed:
# Check if there are any references to invoice.parent or subscription_details anywhere in the codebase
rg "invoice\.parent|subscription_details" packages/workers/src/routes/billing/ -rRepository: InfinityBowman/corates
Length of output: 479
🏁 Script executed:
# Check for references to invoice.parent or subscription_details anywhere
rg "invoice\.parent|subscription_details" packages/workers/src/routes/billing/Repository: InfinityBowman/corates
Length of output: 222
🏁 Script executed:
# Get the rest of the invoiceHandlers file to see all handlers
tail -n +200 packages/workers/src/routes/billing/handlers/invoiceHandlers.ts | head -100Repository: InfinityBowman/corates
Length of output: 1984
🏁 Script executed:
# Check the git history or diff to see what changed in this file recently
git log --oneline -20 packages/workers/src/routes/billing/handlers/invoiceHandlers.tsRepository: InfinityBowman/corates
Length of output: 85
🏁 Script executed:
# Check if Stripe API version was recently updated
git log --all --oneline -30 | head -30Repository: InfinityBowman/corates
Length of output: 1509
🏁 Script executed:
# Search for Stripe API version or stripe client initialization
rg "stripe|Stripe" packages/workers/src/routes/billing/ -i | grep -i "version\|api\|2025"Repository: InfinityBowman/corates
Length of output: 1031
🏁 Script executed:
# Check what the actual Stripe type looks like to understand the new structure
rg "invoice\.parent" packages/ -rRepository: InfinityBowman/corates
Length of output: 479
🏁 Script executed:
# Search for Stripe Invoice type definitions or usage in the codebase
fd "stripe" packages/workers/src -type f | head -20Repository: InfinityBowman/corates
Length of output: 236
🏁 Script executed:
# Check if there are any type definitions or imports from stripe package
rg "from 'stripe'" packages/workers/src -A 2 -B 2Repository: InfinityBowman/corates
Length of output: 9340
🏁 Script executed:
# Look for any parent field references specifically
rg "\.parent" packages/workers/src/routes/billing/ -B 2 -A 2Repository: InfinityBowman/corates
Length of output: 616
🏁 Script executed:
# Check the package.json to see the Stripe version
cat packages/workers/package.json | grep -A 2 -B 2 "stripe"Repository: InfinityBowman/corates
Length of output: 607
🏁 Script executed:
# Check the lib/stripe.ts file for API version configuration
cat -n packages/workers/src/lib/stripe.ts | head -50Repository: InfinityBowman/corates
Length of output: 1922
🏁 Script executed:
# Check if there's a STRIPE_API_VERSION constant defined
rg "STRIPE_API_VERSION" packages/workers/srcRepository: InfinityBowman/corates
Length of output: 470
Update handlers to read subscription ID from the new invoice structure.
The comment acknowledges that Stripe's API moved subscription from invoice.subscription to invoice.parent.subscription_details.subscription in newer API versions, but the handlers still only check the deprecated top-level field. All three handlers (handleInvoicePaymentSucceeded, handleInvoicePaymentFailed, handleInvoiceFinalized) will fail to recognize subscription invoices after the Stripe API version upgrade to 2026-02-25.clover, incorrectly classifying them as non-subscription invoices and breaking the dunning flow and subscription status updates.
The InvoiceWithSubscription interface and all handler checks must be updated to read from invoice.parent.subscription_details.subscription instead of (or in addition to, if backward compatibility is needed) the deprecated invoice.subscription field.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/workers/src/routes/billing/handlers/invoiceHandlers.ts` around lines
19 - 20, Update InvoiceWithSubscription and the three handlers
(handleInvoicePaymentSucceeded, handleInvoicePaymentFailed,
handleInvoiceFinalized) to read the subscription ID from the new invoice
structure at invoice.parent.subscription_details.subscription, falling back to
the legacy invoice.subscription when present for backward compatibility; modify
the type/interface so it reflects optional paths (e.g., subscription?: string
and parent?: { subscription_details?: { subscription?: string } }) and change
the handlers' checks to extract subscriptionId =
invoice.parent?.subscription_details?.subscription || invoice.subscription
before deciding if the invoice is a subscription invoice so both old and new
webhook payloads are handled.
There was a problem hiding this comment.
♻️ Duplicate comments (2)
packages/workers/src/index.ts (2)
384-390:⚠️ Potential issue | 🟠 MajorMask recipient addresses in queue failure logs.
Both failure paths log
msg.body.toverbatim. That leaks recipient PII into worker logs; use a masked or hashed form instead.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/workers/src/index.ts` around lines 384 - 390, The logs currently print recipient PII via msg.body.to; create and use a small utility (e.g., maskEmail or hashRecipient) and call it when logging failures in the EmailQueue send/exception handlers so the console.error calls use the masked/hashed value instead of msg.body.to; update both occurrences that build messages for `[EmailQueue] Send returned error for ${msg.body.to}:` and `[EmailQueue] Exception sending to ${msg.body.to}:` to use the new helper and leave retry logic (msg.retry) unchanged.
371-379:⚠️ Potential issue | 🟠 MajorStill validate the queue payload before casting it into
sendEmail().
batchis declared asMessageBatch<any>andmsg.bodyis force-cast into the email service input. That makes the consumer trust whatever is already in the queue, so a stale or malformed message will fail only at delivery time instead of being rejected up front. Cloudflare's queue API keeps message bodies generic unless you type them explicitly. Parse the body with a shared schema before calling Postmark, and ack invalid payloads explicitly. (developers.cloudflare.com)As per coding guidelines, "Use Zod for schema and input validation on backend services".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/workers/src/index.ts` around lines 371 - 379, The queue handler is force-casting msg.body into the email service input (EmailPayload) which trusts arbitrary queued data; update the async queue(batch: MessageBatch<any>, env: Env) implementation to validate msg.body with a shared Zod schema (e.g., EmailPayloadSchema) before calling createEmailService(...).sendEmail: parse/strict-validate each msg.body, ack and log/handle messages that fail validation (do not call sendEmail), and only pass the validated/typed object to emailService.sendEmail; reference the queue method, MessageBatch, msg.body, EmailPayload/EmailPayloadSchema, createEmailService, and sendEmail when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@packages/workers/src/index.ts`:
- Around line 384-390: The logs currently print recipient PII via msg.body.to;
create and use a small utility (e.g., maskEmail or hashRecipient) and call it
when logging failures in the EmailQueue send/exception handlers so the
console.error calls use the masked/hashed value instead of msg.body.to; update
both occurrences that build messages for `[EmailQueue] Send returned error for
${msg.body.to}:` and `[EmailQueue] Exception sending to ${msg.body.to}:` to use
the new helper and leave retry logic (msg.retry) unchanged.
- Around line 371-379: The queue handler is force-casting msg.body into the
email service input (EmailPayload) which trusts arbitrary queued data; update
the async queue(batch: MessageBatch<any>, env: Env) implementation to validate
msg.body with a shared Zod schema (e.g., EmailPayloadSchema) before calling
createEmailService(...).sendEmail: parse/strict-validate each msg.body, ack and
log/handle messages that fail validation (do not call sendEmail), and only pass
the validated/typed object to emailService.sendEmail; reference the queue
method, MessageBatch, msg.body, EmailPayload/EmailPayloadSchema,
createEmailService, and sendEmail when making the change.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: da005ad2-f255-4ed9-a5cc-489b215ab12c
📒 Files selected for processing (3)
eslint.config.jspackages/web/src/components/project/reconcile-tab/rob2-reconcile/pages/PreliminaryPage.jsxpackages/workers/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/web/src/components/project/reconcile-tab/rob2-reconcile/pages/PreliminaryPage.jsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Workers Builds: corates-workers-prod
- GitHub Check: Workers Builds: corates
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/yjs-sync.mdc)
**/*.{js,jsx,ts,tsx}: Use theuseProjecthook for managing Yjs connections with reference counting instead of creating Y.Doc instances directly
Never create Y.Doc instances directly; always use the connection registry managed by useProject
Access Y.Doc connection state via projectStore.getConnectionState(projectId) instead of checking connection state directly
Read Yjs-synced data from projectStore (using getStudies, getChecklist, etc.) rather than accessing Y.Doc maps/arrays directly
Use operation functions from useProject or projectActionsStore for writing data instead of directly modifying Y.Doc
Don't store Y.Doc references in component state; always retrieve the connection through useProject
Handle connection cleanup via onCleanup() in Solid.js components or equivalent cleanup patterns, calling disconnect() when the component unmounts
Files:
packages/workers/src/index.tseslint.config.js
packages/workers/src/**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/workers.mdc)
ALWAYS use
db.batch()for multiple related database operations in Drizzle to ensure atomicity - all operations must succeed or all fail together
Files:
packages/workers/src/index.ts
packages/workers/**/*.{js,ts}
📄 CodeRabbit inference engine (.cursor/rules/error-handling.mdc)
packages/workers/**/*.{js,ts}: Always usecreateDomainErrorfrom@corates/sharedfor backend error handling instead of manually creating error objects
Use predefined error constants from@corates/shared(PROJECT_ERRORS, AUTH_ERRORS, VALIDATION_ERRORS, SYSTEM_ERRORS, USER_ERRORS) instead of arbitrary error strings
Wrap database operations in try-catch blocks and return domain errors using createDomainError with SYSTEM_ERRORS.DB_ERROR
Use validation middleware (validateRequest) for request validation instead of manually validating request bodies in routes
Files:
packages/workers/src/index.ts
packages/{web,workers}/**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/error-handling.mdc)
Never throw string literals; always throw Error objects or return domain errors from API routes
Files:
packages/workers/src/index.ts
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/corates.mdc)
**/*.{js,ts,jsx,tsx}: Prefer modern ES6+ syntax and features
Comments should explain why something is being done, not what the code is doing
Reserve comments for explaining intent, context, workarounds, assumptions, edge cases, or limitations
Use TODO(agent): prefix for incomplete work or flagged items with brief description and relevant doc references
Files:
packages/workers/src/index.tseslint.config.js
packages/workers/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/corates.mdc)
packages/workers/src/**/*.{ts,tsx}: Use Zod for schema and input validation on backend services
Use Drizzle ORM for all database interactions and migrations
Use Better-Auth for authentication and user management
All project routes are org-scoped and use/api/orgs/:orgId/projects/...pattern in backend
UserequireOrgMembershipandrequireProjectAccessmiddleware for authentication in org-scoped routes
Never bypass Drizzle for database accessNever bypass Drizzle for database access - use Drizzle ORM for all database operations
Files:
packages/workers/src/index.ts
**/*
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Never use emojis or unicode symbols anywhere in code, comments, documentation, plan files, commit messages, or examples
Files:
packages/workers/src/index.tseslint.config.js
packages/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
packages/**/*.{ts,tsx,js,jsx}: Prefer modern ES6+ syntax and features
Prefer config files over hardcoding values
Keep files small, focused, and modular - extract large files into sub-modules or separate utilities
Each file should handle one coherent responsibility
Comments should explain WHY something is being done or provide context, not repeat what the code is saying
Do NOT narrate what code is doing, don't duplicate function/variable names in comments, and don't leave stale comments that contradict the code
Use the Agent TODO convention// TODO(agent): Brief descriptionfor incomplete work, flagging items for future attention, known limitations, and documentation section referencesUse TODO(agent) convention with brief description and relevant doc section references for incomplete work or flagged issues
Files:
packages/workers/src/index.ts
packages/workers/src/**/*.ts
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
packages/workers/src/**/*.ts: Use Zod for schema and input validation in backend code
Use Drizzle ORM for ALL database interactions and migrations
Use Better-Auth for authentication and user management
Never bypass Drizzle for database access
Files:
packages/workers/src/index.ts
🧠 Learnings (21)
📚 Learning: 2026-01-05T01:00:58.113Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers-testing.mdc:0-0
Timestamp: 2026-01-05T01:00:58.113Z
Learning: Applies to {packages/workers/src/**/*.test.js,packages/workers/src/**/__tests__/**/*.js} : Only re-mock Postmark and Stripe modules inside a test file if you need behavior different from the global mock setup in packages/workers/src/__tests__/setup.js
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2025-12-27T03:01:35.601Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/durable-objects.mdc:0-0
Timestamp: 2025-12-27T03:01:35.601Z
Learning: Applies to packages/workers/src/durable-objects/**/ProjectDoc.{js,ts} : Use y-websocket protocol with message type 1 for awareness (presence) messages in ProjectDoc
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-17T00:25:35.716Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/corates.mdc:0-0
Timestamp: 2026-01-17T00:25:35.716Z
Learning: Applies to packages/workers/src/**/*.{ts,tsx} : Use Better-Auth for authentication and user management
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2025-12-27T03:01:35.601Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/durable-objects.mdc:0-0
Timestamp: 2025-12-27T03:01:35.601Z
Learning: Applies to packages/workers/src/durable-objects/**/*.{js,ts} : Do not expose internal sync endpoints (/sync, /sync-member, /sync-pdf, /disconnect-all) publicly without X-Internal-Request verification
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-17T16:09:36.920Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-17T16:09:36.920Z
Learning: Applies to packages/**/*.{ts,tsx,js,jsx} : Use the Agent TODO convention `// TODO(agent): Brief description` for incomplete work, flagging items for future attention, known limitations, and documentation section references
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-01T23:31:43.756Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/api-routes.mdc:0-0
Timestamp: 2026-01-01T23:31:43.756Z
Learning: Applies to packages/workers/src/routes/**/*.js : Use error constants from `corates/shared` (`PROJECT_ERRORS.*`, `AUTH_ERRORS.*`, `VALIDATION_ERRORS.*`, `SYSTEM_ERRORS.*`, `USER_ERRORS.*`) instead of creating error objects manually
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-01T23:31:43.756Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/api-routes.mdc:0-0
Timestamp: 2026-01-01T23:31:43.756Z
Learning: Applies to packages/workers/src/routes/**/*.js : ALWAYS use `createDomainError` from `corates/shared` for error handling in API routes
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-17T00:25:03.945Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/instructions/api-routes.instructions.md:0-0
Timestamp: 2026-01-17T00:25:03.945Z
Learning: Applies to packages/workers/**/routes/**/*.{js,ts} : Use error helpers from `corates/shared` (`notFound`, `forbidden`, `badRequest`, etc.) for error handling in API routes
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-17T00:25:35.716Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/corates.mdc:0-0
Timestamp: 2026-01-17T00:25:35.716Z
Learning: Applies to packages/workers/src/**/*.{ts,tsx} : Use `requireOrgMembership` and `requireProjectAccess` middleware for authentication in org-scoped routes
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-01T23:31:43.756Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/api-routes.mdc:0-0
Timestamp: 2026-01-01T23:31:43.756Z
Learning: Applies to packages/workers/src/routes/**/*.js : Apply authentication and authorization middleware in correct order: Authentication → Organization membership → Project access → Authorization → Validation → Route handler
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-05T01:00:58.113Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers-testing.mdc:0-0
Timestamp: 2026-01-05T01:00:58.113Z
Learning: Applies to {packages/workers/src/**/*.test.js,packages/workers/src/**/__tests__/**/*.js} : Mock src/middleware/auth.js requireAuth function in route tests by setting user/session via x-test-user-id and x-test-user-email headers
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-01T23:32:23.488Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers.mdc:0-0
Timestamp: 2026-01-01T23:32:23.488Z
Learning: Applies to packages/workers/src/routes/orgs/**/*.{js,ts,jsx,tsx} : Use `requireOrgMembership` and `requireProjectAccess` middleware instead of manual membership checks for org-scoped routes
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-01T23:31:43.756Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/api-routes.mdc:0-0
Timestamp: 2026-01-01T23:31:43.756Z
Learning: Applies to packages/workers/src/routes/orgs/**/*.js : Use organization-scoped route patterns with path structure `/api/orgs/:orgId/...` and leverage `requireOrgMembership`, `requireProjectAccess`, `getOrgContext`, and `getProjectContext` middleware
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-17T00:25:35.716Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/corates.mdc:0-0
Timestamp: 2026-01-17T00:25:35.716Z
Learning: Applies to packages/workers/src/**/*.{ts,tsx} : All project routes are org-scoped and use `/api/orgs/:orgId/projects/...` pattern in backend
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-01T23:32:23.488Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers.mdc:0-0
Timestamp: 2026-01-01T23:32:23.488Z
Learning: Applies to packages/workers/src/routes/orgs/**/*.{js,ts,jsx,tsx} : Use `getOrgContext(c)` to retrieve the validated orgId from context instead of manual checks - orgId is guaranteed valid by middleware
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-17T00:25:35.716Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/corates.mdc:0-0
Timestamp: 2026-01-17T00:25:35.716Z
Learning: Applies to packages/web/src/**/*.{ts,tsx} : Frontend uses orgSlug in URLs (`/orgs/:orgSlug/...`) for readability
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-05T01:00:58.113Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers-testing.mdc:0-0
Timestamp: 2026-01-05T01:00:58.113Z
Learning: Applies to {packages/workers/src/**/*.test.js,packages/workers/src/**/__tests__/**/*.js} : For integration tests, fetch against real worker env using app.fetch(req, env, ctx) with env from cloudflare:test, or use fetchApp() helper
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-06T23:56:57.354Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/error-handling.mdc:0-0
Timestamp: 2026-01-06T23:56:57.354Z
Learning: Applies to packages/workers/**/*.{js,ts} : Always use `createDomainError` from `corates/shared` for backend error handling instead of manually creating error objects
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2025-12-27T03:01:35.601Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/durable-objects.mdc:0-0
Timestamp: 2025-12-27T03:01:35.601Z
Learning: Applies to packages/workers/src/durable-objects/**/*.{js,ts} : Handle WebSocket connection errors gracefully by closing connections and cleaning up resources
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-06T23:56:57.354Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/error-handling.mdc:0-0
Timestamp: 2026-01-06T23:56:57.354Z
Learning: Applies to packages/workers/**/*.{js,ts} : Wrap database operations in try-catch blocks and return domain errors using createDomainError with SYSTEM_ERRORS.DB_ERROR
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-06T23:56:57.354Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/error-handling.mdc:0-0
Timestamp: 2026-01-06T23:56:57.354Z
Learning: Applies to packages/{web,workers}/**/*.{js,ts,jsx,tsx} : Never throw string literals; always throw Error objects or return domain errors from API routes
Applied to files:
packages/workers/src/index.ts
🔇 Additional comments (1)
eslint.config.js (1)
331-332: LGTM!The addition of
MessageandMessageBatchglobals is correct for Cloudflare Queues support. These types are used in queue consumer handlers (queue(batch: MessageBatch<T>, env, ctx)) and are appropriately scoped to the backend workers files where the queue consumer is implemented.
…wman/corates into switch-to-email-queue
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| */ | ||
| async function sendProjectInvitation( | ||
| to: string, | ||
| projectName: string, |
There was a problem hiding this comment.
Unused email service methods after queue migration
Low Severity
The sendEmailVerification, sendPasswordReset, and sendMagicLink methods in createEmailService are now dead code. After this PR, auth/config.ts generates email HTML/text directly and calls queueEmail(), and the queue consumer only calls emailService.sendEmail(). These three methods (and the template imports they rely on like getVerificationEmailHtml, getPasswordResetEmailHtml, getMagicLinkEmailHtml) are never invoked anywhere in the codebase. They remain exported on the EmailService interface, which is misleading.
Additional Locations (1)
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
.mcp.md (1)
1-26: Clarify the purpose of embedding MCP configuration in a Markdown HTML comment.This file embeds MCP server configuration as JSON within an HTML comment block in a Markdown file. While this format might be intentional for documentation or tooling purposes, it's unconventional compared to standard JSON configuration files (like the related
.mcp.jsonand.vscode/mcp.jsonfiles mentioned in the summary).If this is a private/development configuration, consider adding a brief explanation at the top of the file documenting its purpose and relationship to the other MCP configuration files in the repository.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.mcp.md around lines 1 - 26, The embedded MCP JSON (the HTML comment containing "mcpServers" with entries "corates", "corates-memory", "ark-ui", and "sentry") is unclear; add a short top-of-file note explaining why the MCP configuration is stored inside a Markdown HTML comment, whether it is for documentation/tooling, and how it relates to the canonical .mcp.json and .vscode/mcp.json files (e.g., source of truth vs. examples or dev-only overrides), so readers and tools understand its intent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.mcp.md:
- Around line 16-20: The current MCP entry uses npx with the auto-accept flag
(-y) for the "ark-ui" tool which risks silently installing a
malicious/typosquatted package; replace this with a safer approach: either
pre-install `@ark-ui/mcp` as a devDependency and point the config to the local
binary, remove the -y flag to require user confirmation when invoking npx, or
pin a specific safe version using npx `@ark-ui/mcp`@<semver> to avoid implicit
upgrades—also confirm whether the "ark-ui" configuration in this file is
intended to be private/local-only and, if so, mark or move it to a local-only
config to avoid exposing it in public repos.
---
Nitpick comments:
In @.mcp.md:
- Around line 1-26: The embedded MCP JSON (the HTML comment containing
"mcpServers" with entries "corates", "corates-memory", "ark-ui", and "sentry")
is unclear; add a short top-of-file note explaining why the MCP configuration is
stored inside a Markdown HTML comment, whether it is for documentation/tooling,
and how it relates to the canonical .mcp.json and .vscode/mcp.json files (e.g.,
source of truth vs. examples or dev-only overrides), so readers and tools
understand its intent.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 05c34c75-5b3c-4678-9998-425a506d2599
📒 Files selected for processing (7)
.mcp.json.mcp.mdpackages/web/src/components/project/overview-tab/OverviewTab.jsxpackages/workers/src/auth/config.tspackages/workers/src/index.tspackages/workers/src/lib/send-invitation-email.tspackages/workers/src/routes/billing/__tests__/invoiceHandlers.test.ts
💤 Files with no reviewable changes (1)
- .mcp.json
🚧 Files skipped from review as they are similar to previous changes (3)
- packages/workers/src/routes/billing/tests/invoiceHandlers.test.ts
- packages/workers/src/index.ts
- packages/web/src/components/project/overview-tab/OverviewTab.jsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Cursor Bugbot
- GitHub Check: Workers Builds: corates
- GitHub Check: Workers Builds: corates-workers-prod
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/yjs-sync.mdc)
**/*.{js,jsx,ts,tsx}: Use theuseProjecthook for managing Yjs connections with reference counting instead of creating Y.Doc instances directly
Never create Y.Doc instances directly; always use the connection registry managed by useProject
Access Y.Doc connection state via projectStore.getConnectionState(projectId) instead of checking connection state directly
Read Yjs-synced data from projectStore (using getStudies, getChecklist, etc.) rather than accessing Y.Doc maps/arrays directly
Use operation functions from useProject or projectActionsStore for writing data instead of directly modifying Y.Doc
Don't store Y.Doc references in component state; always retrieve the connection through useProject
Handle connection cleanup via onCleanup() in Solid.js components or equivalent cleanup patterns, calling disconnect() when the component unmounts
Files:
packages/workers/src/auth/config.tspackages/workers/src/lib/send-invitation-email.ts
packages/workers/src/**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/workers.mdc)
ALWAYS use
db.batch()for multiple related database operations in Drizzle to ensure atomicity - all operations must succeed or all fail together
Files:
packages/workers/src/auth/config.tspackages/workers/src/lib/send-invitation-email.ts
packages/workers/**/*.{js,ts}
📄 CodeRabbit inference engine (.cursor/rules/error-handling.mdc)
packages/workers/**/*.{js,ts}: Always usecreateDomainErrorfrom@corates/sharedfor backend error handling instead of manually creating error objects
Use predefined error constants from@corates/shared(PROJECT_ERRORS, AUTH_ERRORS, VALIDATION_ERRORS, SYSTEM_ERRORS, USER_ERRORS) instead of arbitrary error strings
Wrap database operations in try-catch blocks and return domain errors using createDomainError with SYSTEM_ERRORS.DB_ERROR
Use validation middleware (validateRequest) for request validation instead of manually validating request bodies in routes
Files:
packages/workers/src/auth/config.tspackages/workers/src/lib/send-invitation-email.ts
packages/{web,workers}/**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/error-handling.mdc)
Never throw string literals; always throw Error objects or return domain errors from API routes
Files:
packages/workers/src/auth/config.tspackages/workers/src/lib/send-invitation-email.ts
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/corates.mdc)
**/*.{js,ts,jsx,tsx}: Prefer modern ES6+ syntax and features
Comments should explain why something is being done, not what the code is doing
Reserve comments for explaining intent, context, workarounds, assumptions, edge cases, or limitations
Use TODO(agent): prefix for incomplete work or flagged items with brief description and relevant doc references
Files:
packages/workers/src/auth/config.tspackages/workers/src/lib/send-invitation-email.ts
packages/workers/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/corates.mdc)
packages/workers/src/**/*.{ts,tsx}: Use Zod for schema and input validation on backend services
Use Drizzle ORM for all database interactions and migrations
Use Better-Auth for authentication and user management
All project routes are org-scoped and use/api/orgs/:orgId/projects/...pattern in backend
UserequireOrgMembershipandrequireProjectAccessmiddleware for authentication in org-scoped routes
Never bypass Drizzle for database accessNever bypass Drizzle for database access - use Drizzle ORM for all database operations
Files:
packages/workers/src/auth/config.tspackages/workers/src/lib/send-invitation-email.ts
**/*
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Never use emojis or unicode symbols anywhere in code, comments, documentation, plan files, commit messages, or examples
Files:
packages/workers/src/auth/config.tspackages/workers/src/lib/send-invitation-email.ts
packages/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
packages/**/*.{ts,tsx,js,jsx}: Prefer modern ES6+ syntax and features
Prefer config files over hardcoding values
Keep files small, focused, and modular - extract large files into sub-modules or separate utilities
Each file should handle one coherent responsibility
Comments should explain WHY something is being done or provide context, not repeat what the code is saying
Do NOT narrate what code is doing, don't duplicate function/variable names in comments, and don't leave stale comments that contradict the code
Use the Agent TODO convention// TODO(agent): Brief descriptionfor incomplete work, flagging items for future attention, known limitations, and documentation section referencesUse TODO(agent) convention with brief description and relevant doc section references for incomplete work or flagged issues
Files:
packages/workers/src/auth/config.tspackages/workers/src/lib/send-invitation-email.ts
packages/workers/src/**/*.ts
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
packages/workers/src/**/*.ts: Use Zod for schema and input validation in backend code
Use Drizzle ORM for ALL database interactions and migrations
Use Better-Auth for authentication and user management
Never bypass Drizzle for database access
Files:
packages/workers/src/auth/config.tspackages/workers/src/lib/send-invitation-email.ts
🧠 Learnings (27)
📚 Learning: 2026-01-17T00:25:35.716Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/corates.mdc:0-0
Timestamp: 2026-01-17T00:25:35.716Z
Learning: Applies to packages/workers/src/**/*.{ts,tsx} : Use Better-Auth for authentication and user management
Applied to files:
packages/workers/src/auth/config.tspackages/workers/src/lib/send-invitation-email.ts
📚 Learning: 2026-01-17T16:09:36.920Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-17T16:09:36.920Z
Learning: Applies to packages/workers/src/**/*.ts : Use Better-Auth for authentication and user management
Applied to files:
packages/workers/src/auth/config.tspackages/workers/src/lib/send-invitation-email.ts
📚 Learning: 2026-01-05T01:00:58.113Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers-testing.mdc:0-0
Timestamp: 2026-01-05T01:00:58.113Z
Learning: Applies to {packages/workers/src/**/*.test.js,packages/workers/src/**/__tests__/**/*.js} : Only re-mock Postmark and Stripe modules inside a test file if you need behavior different from the global mock setup in packages/workers/src/__tests__/setup.js
Applied to files:
packages/workers/src/auth/config.ts
📚 Learning: 2026-01-17T16:09:36.920Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-17T16:09:36.920Z
Learning: Applies to packages/workers/src/**/*.ts : Use Drizzle ORM for ALL database interactions and migrations
Applied to files:
packages/workers/src/auth/config.ts
📚 Learning: 2026-01-17T00:25:35.716Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/corates.mdc:0-0
Timestamp: 2026-01-17T00:25:35.716Z
Learning: Applies to packages/workers/src/**/*.{ts,tsx} : Use `requireOrgMembership` and `requireProjectAccess` middleware for authentication in org-scoped routes
Applied to files:
packages/workers/src/auth/config.ts
📚 Learning: 2026-01-17T00:25:35.716Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/corates.mdc:0-0
Timestamp: 2026-01-17T00:25:35.716Z
Learning: Applies to packages/workers/src/**/*.{ts,tsx} : Use Drizzle ORM for all database interactions and migrations
Applied to files:
packages/workers/src/auth/config.ts
📚 Learning: 2026-01-05T01:00:58.113Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers-testing.mdc:0-0
Timestamp: 2026-01-05T01:00:58.113Z
Learning: Applies to {packages/workers/src/**/*.test.js,packages/workers/src/**/__tests__/**/*.js} : Reuse provided helpers from packages/workers/src/__tests__/helpers.js (resetTestDatabase, clearProjectDOs, seedUser, seedOrganization, createTestEnv, fetchApp, json, createAuthHeaders) instead of implementing ad-hoc solutions
Applied to files:
packages/workers/src/auth/config.tspackages/workers/src/lib/send-invitation-email.ts
📚 Learning: 2026-01-06T23:56:57.354Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/error-handling.mdc:0-0
Timestamp: 2026-01-06T23:56:57.354Z
Learning: Applies to packages/{web,workers}/**/*.{js,ts,jsx,tsx} : Never throw string literals; always throw Error objects or return domain errors from API routes
Applied to files:
packages/workers/src/auth/config.ts
📚 Learning: 2026-01-01T23:31:43.756Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/api-routes.mdc:0-0
Timestamp: 2026-01-01T23:31:43.756Z
Learning: Applies to packages/workers/src/routes/**/*.js : Use error constants from `corates/shared` (`PROJECT_ERRORS.*`, `AUTH_ERRORS.*`, `VALIDATION_ERRORS.*`, `SYSTEM_ERRORS.*`, `USER_ERRORS.*`) instead of creating error objects manually
Applied to files:
packages/workers/src/auth/config.ts
📚 Learning: 2026-01-17T16:10:07.531Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-17T16:10:07.531Z
Learning: Use required libraries for specific functionality: Zod for validation, Drizzle for database, Better-Auth for authentication, Ark UI for components
Applied to files:
packages/workers/src/auth/config.ts
📚 Learning: 2026-01-17T00:25:03.945Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/instructions/api-routes.instructions.md:0-0
Timestamp: 2026-01-17T00:25:03.945Z
Learning: Applies to packages/workers/**/routes/**/*.{js,ts} : Create DB client from environment using `createDb(c.env.DB)` instead of creating DB connections directly
Applied to files:
packages/workers/src/auth/config.ts
📚 Learning: 2026-01-01T23:31:43.756Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/api-routes.mdc:0-0
Timestamp: 2026-01-01T23:31:43.756Z
Learning: Applies to packages/workers/src/routes/**/*.js : Always create DB client from environment using `createDb` function in route handlers
Applied to files:
packages/workers/src/auth/config.ts
📚 Learning: 2026-01-17T16:09:36.920Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-17T16:09:36.920Z
Learning: Applies to packages/workers/src/**/*.ts : Never bypass Drizzle for database access
Applied to files:
packages/workers/src/auth/config.ts
📚 Learning: 2026-01-17T00:25:35.716Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/corates.mdc:0-0
Timestamp: 2026-01-17T00:25:35.716Z
Learning: Applies to packages/workers/src/**/*.{ts,tsx} : Never bypass Drizzle for database access
Applied to files:
packages/workers/src/auth/config.ts
📚 Learning: 2026-01-17T16:10:07.531Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-17T16:10:07.531Z
Learning: Applies to packages/workers/src/**/*.{ts,tsx} : Never bypass Drizzle for database access - use Drizzle ORM for all database operations
Applied to files:
packages/workers/src/auth/config.ts
📚 Learning: 2026-01-01T23:32:17.698Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/organizations.mdc:0-0
Timestamp: 2026-01-01T23:32:17.698Z
Learning: Applies to packages/workers/src/routes/orgs/** : Ensure project invitation flow creates org membership before project membership, including orgRole and projectRole in invitation data
Applied to files:
packages/workers/src/lib/send-invitation-email.ts
📚 Learning: 2026-01-17T16:09:36.920Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-17T16:09:36.920Z
Learning: Applies to packages/workers/src/**/*.ts : Use Zod for schema and input validation in backend code
Applied to files:
packages/workers/src/lib/send-invitation-email.ts
📚 Learning: 2026-01-17T00:25:35.716Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/corates.mdc:0-0
Timestamp: 2026-01-17T00:25:35.716Z
Learning: Applies to packages/workers/src/**/*.{ts,tsx} : Use Zod for schema and input validation on backend services
Applied to files:
packages/workers/src/lib/send-invitation-email.ts
📚 Learning: 2026-01-01T23:32:23.488Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers.mdc:0-0
Timestamp: 2026-01-01T23:32:23.488Z
Learning: Applies to packages/workers/src/config/validation.{js,ts} : Add new Zod validation schemas to `config/validation.js` and reuse `commonFields` when possible to avoid duplication
Applied to files:
packages/workers/src/lib/send-invitation-email.ts
📚 Learning: 2026-01-01T23:31:43.756Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/api-routes.mdc:0-0
Timestamp: 2026-01-01T23:31:43.756Z
Learning: Applies to packages/workers/src/config/validation.js : Add new validation schemas to `src/config/validation.js` and reuse `commonFields` when possible
Applied to files:
packages/workers/src/lib/send-invitation-email.ts
📚 Learning: 2026-01-01T23:32:23.488Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers.mdc:0-0
Timestamp: 2026-01-01T23:32:23.488Z
Learning: Applies to packages/workers/src/routes/**/*.{js,ts,jsx,tsx} : ALWAYS validate request bodies using `validateRequest` middleware from the validation config rather than manual validation
Applied to files:
packages/workers/src/lib/send-invitation-email.ts
📚 Learning: 2026-01-06T23:56:57.354Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/error-handling.mdc:0-0
Timestamp: 2026-01-06T23:56:57.354Z
Learning: Applies to packages/workers/**/*.{js,ts} : Use validation middleware (validateRequest) for request validation instead of manually validating request bodies in routes
Applied to files:
packages/workers/src/lib/send-invitation-email.ts
📚 Learning: 2026-01-05T01:00:58.113Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers-testing.mdc:0-0
Timestamp: 2026-01-05T01:00:58.113Z
Learning: Applies to {packages/workers/src/**/*.test.js,packages/workers/src/**/__tests__/**/*.js} : Seed test data using provided seed* helpers (seedUser, seedOrganization, seedOrgMember, seedProject, etc.) from packages/workers/src/__tests__/helpers.js with Zod schema validation
Applied to files:
packages/workers/src/lib/send-invitation-email.ts
📚 Learning: 2026-01-01T23:32:23.488Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers.mdc:0-0
Timestamp: 2026-01-01T23:32:23.488Z
Learning: Applies to packages/workers/src/routes/**/*.{js,ts,jsx,tsx} : Use `validateQueryParams` middleware for validating query parameters in routes
Applied to files:
packages/workers/src/lib/send-invitation-email.ts
📚 Learning: 2026-01-01T23:31:43.756Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/api-routes.mdc:0-0
Timestamp: 2026-01-01T23:31:43.756Z
Learning: Applies to packages/workers/src/routes/**/*.js : Use `validateQueryParams` middleware for query string validation in API routes
Applied to files:
packages/workers/src/lib/send-invitation-email.ts
📚 Learning: 2026-01-17T00:25:03.945Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/instructions/api-routes.instructions.md:0-0
Timestamp: 2026-01-17T00:25:03.945Z
Learning: Applies to packages/workers/**/routes/**/*.{js,ts} : Always use `validateRequest` middleware for request body validation in API routes, imported from '../config/validation.js'
Applied to files:
packages/workers/src/lib/send-invitation-email.ts
📚 Learning: 2026-01-17T16:09:36.920Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-17T16:09:36.920Z
Learning: Applies to packages/**/*.{ts,tsx,js,jsx} : Use the Agent TODO convention `// TODO(agent): Brief description` for incomplete work, flagging items for future attention, known limitations, and documentation section references
Applied to files:
packages/workers/src/lib/send-invitation-email.ts
🔇 Additional comments (9)
packages/workers/src/lib/send-invitation-email.ts (5)
13-24: Add Zod runtime validation for input parameters.This helper is called with request-derived strings (email, token, projectName, etc.). A Zod schema should validate these inputs at runtime to fail fast on malformed data.
+import { z } from 'zod'; + +const SendInvitationEmailSchema = z.object({ + email: z.string().email(), + token: z.string().min(1), + projectName: z.string().min(1), + inviterName: z.string().min(1), + role: z.string().min(1), +}); + interface SendInvitationEmailParams { env: Env; email: string; token: string; projectName: string; inviterName: string; role: string; }Then parse at function entry:
const validated = SendInvitationEmailSchema.parse({ email, token, projectName, inviterName, role });As per coding guidelines: "Use Zod for schema and input validation on backend services".
49-87: LGTM!The temporary auth instance setup correctly mirrors the production configuration. The
expiresInvalue (60 * MAGIC_LINK_EXPIRY_MINUTES) matches the production magicLink plugin configuration, ensuring consistent token validity.
89-104: LGTM!The magic link generation correctly normalizes the email to lowercase and properly validates that the URL was captured, throwing a domain error with appropriate context if generation fails.
106-109: LGTM!The security concern from the previous review has been addressed. The magic link URL is now properly redacted (replacing the token value with "REDACTED") before logging in non-production environments.
128-141: LGTM!The error handling strategy is well-designed. Since
queueEmailthrows errors rather than swallowing them, the try-catch correctly distinguishes success from failure. Returning{ emailQueued: false }on queue failure is appropriate since the invitation record already exists in the database and can be resent later..mcp.md (2)
21-24: LGTM!The Sentry MCP server configuration correctly uses HTTPS for secure communication with the external endpoint.
3-7: The pathpackages/mcp/dist/server.jsis correctly configured as a build artifact. TypeScript is set to compile source files frompackages/mcp/src/topackages/mcp/dist/via thetscbuild command. The package.json bin entry and start script both reference this path, confirming it is the intended configuration. No action required.packages/workers/src/auth/config.ts (2)
8-8: Centralizing the Stripe API version here is a good cleanup.Using the shared constant keeps auth on the same Stripe contract as the rest of the worker and avoids version drift.
Also applies to: 225-226
14-23: The queue-based auth email hooks look solid.Moving these flows onto shared templates plus
queueEmailkeeps the hooks thin, and awaiting the enqueue means auth does not report success until the message has actually been accepted by the queue.Also applies to: 177-181, 443-449, 464-470
| "ark-ui": { | ||
| "type": "stdio", | ||
| "command": "npx", | ||
| "args": ["-y", "@ark-ui/mcp"] | ||
| }, |
There was a problem hiding this comment.
Security concern: npx -y auto-accepts package installation.
The -y flag causes npx to automatically install @ark-ui/mcp without user confirmation. This poses a security risk if:
- The package is compromised or replaced with malicious code
- A typosquatting attack targets the package name
- The package is unexpectedly updated to a malicious version
Consider one of the following approaches:
- Pre-install the package as a dev dependency to lock the version
- Remove the
-yflag to require explicit user confirmation - Use a specific version constraint:
npx@ark-ui/mcp@^1.0.0
Additionally, the AI summary mentions that "ark-ui has been removed from the public configuration," but it appears in this file. Clarify whether this configuration file is intended to be private/local-only.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.mcp.md around lines 16 - 20, The current MCP entry uses npx with the
auto-accept flag (-y) for the "ark-ui" tool which risks silently installing a
malicious/typosquatted package; replace this with a safer approach: either
pre-install `@ark-ui/mcp` as a devDependency and point the config to the local
binary, remove the -y flag to require user confirmation when invoking npx, or
pin a specific safe version using npx `@ark-ui/mcp`@<semver> to avoid implicit
upgrades—also confirm whether the "ark-ui" configuration in this file is
intended to be private/local-only and, if so, mark or move it to a local-only
config to avoid exposing it in public repos.


Switch from Durable Object to Queue for email handling for all emails.
Note
Medium Risk
Switches the app’s outbound email path to Cloudflare Queues (new producer/consumer bindings and retry behavior), touching auth/contact/invitations/billing flows where delivery failures can impact user-facing workflows. Also tweaks Yjs sync/persistence and Stripe period/version handling, which could surface as collaboration or billing regressions if edge cases are missed.
Overview
Email delivery is migrated from a Durable Object to Cloudflare Queues. The
EmailQueueDO implementation/tests and/api/emailroute are removed; a newqueueEmail()producer helper is added and the worker now consumes queue batches inindex.ts, handling retries with backoff and acking on success.All email-producing flows are rewired to enqueue payloads (auth verification/magic links/password resets, contact form with
replyTo, account-merge verification, billing dunning, and project invitations). Invitation emails are deduplicated behind a newsendInvitationEmail()helper, and invitation endpoints now return a “created but delivery may be delayed” message when queuing fails.Infra and correctness updates.
wrangler.jsoncadds queue bindings/consumer config and a migration deleting theEmailQueueclass; health checks/tests are updated accordingly. Also includes small Yjs/Durable Object robustness fixes (ProjectDoc awareness clientId capture +webSocketErrorinit), frontend Yjs persistence guards (Dexie corruption handling, awareness handler try/catch, re-entrancy guard), and Stripe API/version + subscription-period field adjustments.Written by Cursor Bugbot for commit f2a922c. This will update automatically on new commits. Configure here.
Summary by CodeRabbit
Refactor
Documentation