Skip to content

Comments

Scheduling#1914

Merged
MrgSub merged 24 commits intostagingfrom
scheduling
Aug 5, 2025
Merged

Scheduling#1914
MrgSub merged 24 commits intostagingfrom
scheduling

Conversation

@MrgSub
Copy link
Collaborator

@MrgSub MrgSub commented Aug 4, 2025

Summary by CodeRabbit

  • New Features

    • Added support for scheduling emails to be sent at a future date and time.
    • Introduced an "Undo Send" feature, allowing users to cancel sent emails within 30 seconds.
    • Added a toggle for "Undo Send" in general settings.
    • Added a date/time picker for scheduling emails in the email composer and reply composer.
  • Enhancements

    • Improved attachment handling for outgoing emails.
    • Updated toast notifications with unified button styles and improved layout.
    • Refactored general settings form for better modularity and added "Undo Send" toggle.
  • Bug Fixes

    • Fixed validation and error handling for scheduled send times.
  • Localization

    • Added English translations for new "Undo Send" settings.
  • Chores

    • Updated backend infrastructure to support scheduled and undoable email sending, including new storage and queue resources.
    • Updated configuration files to enable scheduled email processing.

@jazzberry-ai
Copy link

jazzberry-ai bot commented Aug 4, 2025

Bug Report

Name: Unhandled JSON parsing error in processScheduledEmails
Severity: Medium
Example test case: Introduce an invalid JSON string into the scheduled_emails KV store. Observe that the scheduled task fails.
Description: The processScheduledEmails function in apps/server/src/main.ts does not handle potential JSON parsing errors when retrieving data from the scheduled_emails KV store. If the data is corrupted or in an unexpected format, the JSON.parse operation will throw an error, potentially halting the processing of scheduled emails. Add a try-catch block to handle JSON parsing errors gracefully.

Comments? Email us.

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 4, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Warning

Rate limit exceeded

@MrgSub has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 19 minutes and 41 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 2b8f798 and c935a26.

📒 Files selected for processing (6)
  • apps/mail/hooks/use-undo-send.ts (1 hunks)
  • apps/mail/messages/en.json (1 hunks)
  • apps/server/src/lib/schemas.ts (2 hunks)
  • apps/server/src/main.ts (4 hunks)
  • apps/server/src/trpc/routes/mail.ts (5 hunks)
  • apps/server/wrangler.jsonc (12 hunks)

Walkthrough

This update introduces scheduled email sending and an undo-send feature to the mail application. It adds new UI components, backend logic, queue and KV storage integration, and environment/configuration changes to support email scheduling, cancellation, and user settings. Type definitions, schemas, and localization are updated accordingly.

Changes

Cohort / File(s) Change Summary
Frontend: Undo Send Setting & Localization
apps/mail/app/(routes)/settings/general/page.tsx, apps/mail/messages/en.json
Added "Undo Send" toggle to general settings UI and corresponding English localization entries.
Frontend: Email Compose & Scheduling
apps/mail/components/create/create-email.tsx, apps/mail/components/create/email-composer.tsx, apps/mail/components/create/schedule-send-picker.tsx, apps/mail/components/mail/reply-composer.tsx
Enabled scheduling emails and undo-send in compose and reply workflows. Introduced a ScheduleSendPicker component and updated related handlers and props.
Frontend: Undo Send Hook & Utilities
apps/mail/hooks/use-undo-send.ts, apps/mail/lib/email-utils.ts
Added useUndoSend hook for handling undo-send logic and type guard for queued send results.
Frontend: Toast UI
apps/mail/components/ui/toast.tsx
Refactored button styling and layout for toast notifications.
Backend: User Settings & Schemas
apps/server/src/lib/schemas.ts, apps/server/src/db/schema.ts
Added undoSendEnabled to user settings schema and type annotations for settings in DB schema.
Backend: Attachments Utility
apps/server/src/lib/attachments.ts
Introduced serialization and conversion utility for email attachments.
Backend: Google Mail Driver
apps/server/src/lib/driver/google.ts
Enhanced attachment handling for both base64 and arrayBuffer sources.
Backend: Email Scheduling & Undo Logic
apps/server/src/main.ts, apps/server/src/trpc/routes/mail.ts, apps/server/src/types.ts
Implemented scheduled sending, undo-send, and cancellation logic. Added queue and scheduled processing, new mutations, and supporting types.
Backend: Environment & Configuration
apps/server/src/env.ts, apps/server/wrangler.jsonc
Added new KV namespaces, queue bindings, and updated cron schedules for all environments.

Sequence Diagram(s)

Scheduled Email Send & Undo Flow

sequenceDiagram
    participant User
    participant UI (Compose)
    participant Server (TRPC)
    participant KV Store
    participant Queue
    participant Email Agent

    User->>UI (Compose): Schedule email & click Send
    UI (Compose)->>Server (TRPC): sendEmail({scheduleAt, ...})
    alt scheduleAt present or undoSend enabled
        Server (TRPC)->>KV Store: Store status & payload
        Server (TRPC)->>Queue: Enqueue send (delayed)
        Server (TRPC)-->>UI (Compose): Return queued result
        UI (Compose)->>User: Show 'Undo' toast
        User->>UI (Compose): Click 'Undo'
        UI (Compose)->>Server (TRPC): unsend({messageId})
        Server (TRPC)->>KV Store: Mark as cancelled, delete payload
        Server (TRPC)-->>UI (Compose): Return success/failure
    else immediate send
        Server (TRPC)->>Email Agent: Send email now
        Email Agent-->>Server (TRPC): Result
        Server (TRPC)-->>UI (Compose): Return result
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • MrgSub
  • ahmetskilinc

Poem

🚀
Emails can now wait, or be swiftly undone,
With queues and KV, the backend’s begun.
A toggle for “Undo” in settings you’ll find,
And scheduled sends for peace of mind.
Toasts pop up, attachments flow—
The future of mail, in one PR—let’s go!
✉️⏳

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch scheduling

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai coderabbitai bot requested a review from ahmetskilinc August 4, 2025 17:52
@coderabbitai coderabbitai bot added content design Improvements & changes to design & UX High Priority High Priority Work labels Aug 4, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

🔭 Outside diff range comments (4)
apps/mail/app/(routes)/settings/general/page.tsx (1)

129-139: Add default for undoSendEnabled to keep React quiet

The form’s defaultValues omits the new flag, so first render passes undefined into <Switch checked>, which React doesn’t love. Drop in undoSendEnabled: false and you’re golden.

       defaultValues: {
         language: locale,
         timezone: getBrowserTimezone(),
         dynamicContent: false,
         customPrompt: '',
         zeroSignature: true,
+        undoSendEnabled: false,
         defaultEmailAlias: '',
         animations: false,
       },
apps/server/src/lib/driver/google.ts (2)

723-741: Attachment handling logic looks solid, but we need better type safety here.

The defensive programming approach is excellent - checking for both base64 string and arrayBuffer function properties. However, we're doing some sketchy (attachment as any) casting that would make TypeScript cry tears of binary code.

Consider creating a proper union type for attachments:

+interface BaseAttachment {
+  name: string;
+  type?: string;
+}
+
+interface Base64Attachment extends BaseAttachment {
+  base64: string;
+}
+
+interface ArrayBufferAttachment extends BaseAttachment {
+  arrayBuffer: () => Promise<ArrayBuffer>;
+}
+
+type AttachmentInput = Base64Attachment | ArrayBufferAttachment;

-        if (data.attachments && data.attachments?.length > 0) {
-          for (const attachment of data.attachments) {
+        if (data.attachments && data.attachments?.length > 0) {
+          for (const attachment of data.attachments as AttachmentInput[]) {
             let base64Data: string | undefined;

-            if (typeof (attachment as any)?.base64 === 'string') {
-              base64Data = (attachment as any).base64;
-            } else if (typeof (attachment as any)?.arrayBuffer === 'function') {
+            if ('base64' in attachment) {
+              base64Data = attachment.base64;
+            } else if ('arrayBuffer' in attachment) {
               const buffer = Buffer.from(await (attachment as any).arrayBuffer());
               base64Data = buffer.toString('base64');
             }

1244-1261: Houston, we have code duplication - let's extract this attachment processing logic.

This is essentially identical to the attachment handling in createDraft. When I see duplicate code like this, my engineering brain starts twitching like a Raptor engine with a control issue.

Extract this into a private method:

+  private async processAttachmentData(attachment: any): Promise<string | undefined> {
+    if (typeof attachment?.base64 === 'string') {
+      return attachment.base64;
+    } else if (typeof attachment?.arrayBuffer === 'function') {
+      const buffer = Buffer.from(await attachment.arrayBuffer());
+      return buffer.toString('base64');
+    }
+    return undefined;
+  }

   if (attachments?.length > 0) {
     for (const file of attachments) {
-      let base64Content: string | undefined;
-
-      if (typeof (file as any)?.base64 === 'string') {
-        base64Content = (file as any).base64;
-      } else if (typeof (file as any)?.arrayBuffer === 'function') {
-        const buffer = Buffer.from(await (file as any).arrayBuffer());
-        base64Content = buffer.toString('base64');
-      }
-
-      if (!base64Content) continue;
+      const base64Content = await this.processAttachmentData(file);
+      if (!base64Content) continue;
apps/mail/components/create/email-composer.tsx (1)

1322-1337: Great UX lift – one nit

Love the just-in-time picker, but the send button is disabled only on !isScheduleValid; if the user removes the date (scheduleAt === undefined) while the picker still holds an old invalid state, they’re grounded from launching. Consider auto-resetting isScheduleValid to true when scheduleAt becomes undefined.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 01e2adf and 2563c7d.

📒 Files selected for processing (18)
  • apps/mail/app/(routes)/settings/general/page.tsx (1 hunks)
  • apps/mail/components/create/create-email.tsx (4 hunks)
  • apps/mail/components/create/email-composer.tsx (8 hunks)
  • apps/mail/components/create/schedule-send-picker.tsx (1 hunks)
  • apps/mail/components/mail/reply-composer.tsx (7 hunks)
  • apps/mail/components/ui/toast.tsx (2 hunks)
  • apps/mail/hooks/use-undo-send.ts (1 hunks)
  • apps/mail/lib/email-utils.ts (1 hunks)
  • apps/mail/messages/en.json (1 hunks)
  • apps/server/src/db/schema.ts (1 hunks)
  • apps/server/src/env.ts (1 hunks)
  • apps/server/src/lib/attachments.ts (1 hunks)
  • apps/server/src/lib/driver/google.ts (2 hunks)
  • apps/server/src/lib/schemas.ts (2 hunks)
  • apps/server/src/main.ts (4 hunks)
  • apps/server/src/trpc/routes/mail.ts (5 hunks)
  • apps/server/src/types.ts (1 hunks)
  • apps/server/wrangler.jsonc (10 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (AGENT.md)

**/*.{js,jsx,ts,tsx}: Use 2-space indentation
Use single quotes for strings
Limit lines to 100 characters in length
Semicolons are required at the end of statements

Files:

  • apps/mail/components/ui/toast.tsx
  • apps/mail/hooks/use-undo-send.ts
  • apps/server/src/lib/schemas.ts
  • apps/server/src/main.ts
  • apps/mail/components/create/schedule-send-picker.tsx
  • apps/server/src/lib/driver/google.ts
  • apps/server/src/trpc/routes/mail.ts
  • apps/mail/app/(routes)/settings/general/page.tsx
  • apps/mail/components/create/create-email.tsx
  • apps/mail/components/create/email-composer.tsx
  • apps/mail/lib/email-utils.ts
  • apps/server/src/env.ts
  • apps/mail/components/mail/reply-composer.tsx
  • apps/server/src/lib/attachments.ts
  • apps/server/src/types.ts
  • apps/server/src/db/schema.ts
**/*.{js,jsx,ts,tsx,css,scss}

📄 CodeRabbit Inference Engine (AGENT.md)

Use Prettier with sort-imports and Tailwind plugins for code formatting

Files:

  • apps/mail/components/ui/toast.tsx
  • apps/mail/hooks/use-undo-send.ts
  • apps/server/src/lib/schemas.ts
  • apps/server/src/main.ts
  • apps/mail/components/create/schedule-send-picker.tsx
  • apps/server/src/lib/driver/google.ts
  • apps/server/src/trpc/routes/mail.ts
  • apps/mail/app/(routes)/settings/general/page.tsx
  • apps/mail/components/create/create-email.tsx
  • apps/mail/components/create/email-composer.tsx
  • apps/mail/lib/email-utils.ts
  • apps/server/src/env.ts
  • apps/mail/components/mail/reply-composer.tsx
  • apps/server/src/lib/attachments.ts
  • apps/server/src/types.ts
  • apps/server/src/db/schema.ts
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (AGENT.md)

Enable TypeScript strict mode

Files:

  • apps/mail/components/ui/toast.tsx
  • apps/mail/hooks/use-undo-send.ts
  • apps/server/src/lib/schemas.ts
  • apps/server/src/main.ts
  • apps/mail/components/create/schedule-send-picker.tsx
  • apps/server/src/lib/driver/google.ts
  • apps/server/src/trpc/routes/mail.ts
  • apps/mail/app/(routes)/settings/general/page.tsx
  • apps/mail/components/create/create-email.tsx
  • apps/mail/components/create/email-composer.tsx
  • apps/mail/lib/email-utils.ts
  • apps/server/src/env.ts
  • apps/mail/components/mail/reply-composer.tsx
  • apps/server/src/lib/attachments.ts
  • apps/server/src/types.ts
  • apps/server/src/db/schema.ts
**/*.{css,js,ts,jsx,tsx,mdx}

📄 CodeRabbit Inference Engine (.cursor/rules/tailwind-css-v4.mdc)

**/*.{css,js,ts,jsx,tsx,mdx}: Chain variants together for composable variants (e.g., group-has-data-potato:opacity-100).
Use new variants such as starting, not-*, inert, nth-*, in-*, open (for :popover-open), and ** for all descendants.
Do not use deprecated utilities like bg-opacity-*, text-opacity-*, border-opacity-*, and divide-opacity-*; use the new syntax (e.g., bg-black/50).
Use renamed utilities: shadow-sm is now shadow-xs, shadow is now shadow-sm, drop-shadow-sm is now drop-shadow-xs, drop-shadow is now drop-shadow-sm, blur-sm is now blur-xs, blur is now blur-sm, rounded-sm is now rounded-xs, rounded is now rounded-sm, outline-none is now outline-hidden.
Use bg-(--brand-color) syntax for CSS variables in arbitrary values instead of bg-[--brand-color].
Stacked variants now apply left-to-right instead of right-to-left.

Files:

  • apps/mail/components/ui/toast.tsx
  • apps/mail/hooks/use-undo-send.ts
  • apps/server/src/lib/schemas.ts
  • apps/server/src/main.ts
  • apps/mail/components/create/schedule-send-picker.tsx
  • apps/server/src/lib/driver/google.ts
  • apps/server/src/trpc/routes/mail.ts
  • apps/mail/app/(routes)/settings/general/page.tsx
  • apps/mail/components/create/create-email.tsx
  • apps/mail/components/create/email-composer.tsx
  • apps/mail/lib/email-utils.ts
  • apps/server/src/env.ts
  • apps/mail/components/mail/reply-composer.tsx
  • apps/server/src/lib/attachments.ts
  • apps/server/src/types.ts
  • apps/server/src/db/schema.ts
🧠 Learnings (13)
📓 Common learnings
Learnt from: retrogtx
PR: Mail-0/Zero#1468
File: apps/server/src/trpc/routes/mail.ts:331-331
Timestamp: 2025-06-28T03:56:09.376Z
Learning: In apps/server/src/trpc/routes/mail.ts, the user indicated they are not using ISO format for the scheduleAt parameter, despite the frontend code showing toISOString() usage in the ScheduleSendPicker component.
📚 Learning: applies to **/*.{css,js,ts,jsx,tsx,mdx} : use renamed utilities: `shadow-sm` is now `shadow-xs`, `sh...
Learnt from: CR
PR: Mail-0/Zero#0
File: .cursor/rules/tailwind-css-v4.mdc:0-0
Timestamp: 2025-08-03T20:42:04.207Z
Learning: Applies to **/*.{css,js,ts,jsx,tsx,mdx} : Use renamed utilities: `shadow-sm` is now `shadow-xs`, `shadow` is now `shadow-sm`, `drop-shadow-sm` is now `drop-shadow-xs`, `drop-shadow` is now `drop-shadow-sm`, `blur-sm` is now `blur-xs`, `blur` is now `blur-sm`, `rounded-sm` is now `rounded-xs`, `rounded` is now `rounded-sm`, `outline-none` is now `outline-hidden`.

Applied to files:

  • apps/mail/components/ui/toast.tsx
  • apps/mail/components/create/email-composer.tsx
📚 Learning: in draft deletion operations, using settimeout with a delay (like 500ms) before showing success toas...
Learnt from: AnjanyKumarJaiswal
PR: Mail-0/Zero#1732
File: apps/mail/components/create/email-composer.tsx:634-657
Timestamp: 2025-07-15T03:31:14.991Z
Learning: In draft deletion operations, using setTimeout with a delay (like 500ms) before showing success toast notifications improves UX by allowing UI state changes (like closing composers and clearing IDs) to complete before displaying the toast, preventing jarring immediate toast appearances that could disappear quickly during interface transitions.

Applied to files:

  • apps/mail/components/ui/toast.tsx
  • apps/mail/components/create/create-email.tsx
  • apps/mail/components/create/email-composer.tsx
  • apps/mail/components/mail/reply-composer.tsx
📚 Learning: applies to **/*.{css,js,ts,jsx,tsx,mdx} : do not use deprecated utilities like `bg-opacity-*`, `text...
Learnt from: CR
PR: Mail-0/Zero#0
File: .cursor/rules/tailwind-css-v4.mdc:0-0
Timestamp: 2025-08-03T20:42:04.207Z
Learning: Applies to **/*.{css,js,ts,jsx,tsx,mdx} : Do not use deprecated utilities like `bg-opacity-*`, `text-opacity-*`, `border-opacity-*`, and `divide-opacity-*`; use the new syntax (e.g., `bg-black/50`).

Applied to files:

  • apps/mail/components/ui/toast.tsx
  • apps/mail/components/create/email-composer.tsx
📚 Learning: for the "upgrade" link in addconnectiondialog, using a proper element instead of a w...
Learnt from: danteissaias
PR: Mail-0/Zero#902
File: apps/mail/components/connection/add.tsx:77-77
Timestamp: 2025-05-07T16:55:46.513Z
Learning: For the "Upgrade" link in AddConnectionDialog, using a proper <button> element instead of a <span> with onClick is recognized as an accessibility improvement but was deferred as out of scope in PR #902 (CSS variables PR).

Applied to files:

  • apps/mail/components/ui/toast.tsx
  • apps/mail/components/create/email-composer.tsx
📚 Learning: applies to **/*.{css,js,ts,jsx,tsx,mdx} : use new variants such as `starting`, `not-*`, `inert`, `nt...
Learnt from: CR
PR: Mail-0/Zero#0
File: .cursor/rules/tailwind-css-v4.mdc:0-0
Timestamp: 2025-08-03T20:42:04.207Z
Learning: Applies to **/*.{css,js,ts,jsx,tsx,mdx} : Use new variants such as `starting`, `not-*`, `inert`, `nth-*`, `in-*`, `open` (for `:popover-open`), and `**` for all descendants.

Applied to files:

  • apps/mail/components/ui/toast.tsx
📚 Learning: in apps/server/src/trpc/routes/mail.ts, the user indicated they are not using iso format for the sch...
Learnt from: retrogtx
PR: Mail-0/Zero#1468
File: apps/server/src/trpc/routes/mail.ts:331-331
Timestamp: 2025-06-28T03:56:09.376Z
Learning: In apps/server/src/trpc/routes/mail.ts, the user indicated they are not using ISO format for the scheduleAt parameter, despite the frontend code showing toISOString() usage in the ScheduleSendPicker component.

Applied to files:

  • apps/mail/hooks/use-undo-send.ts
  • apps/server/src/main.ts
  • apps/mail/components/create/schedule-send-picker.tsx
  • apps/server/src/trpc/routes/mail.ts
  • apps/mail/app/(routes)/settings/general/page.tsx
  • apps/mail/components/create/create-email.tsx
  • apps/mail/components/create/email-composer.tsx
  • apps/mail/lib/email-utils.ts
  • apps/mail/components/mail/reply-composer.tsx
  • apps/server/src/types.ts
📚 Learning: in apps/server/src/trpc/routes/mail.ts, the attachment processing logic conditionally handles mixed ...
Learnt from: retrogtx
PR: Mail-0/Zero#1468
File: apps/server/src/trpc/routes/mail.ts:386-391
Timestamp: 2025-06-27T04:59:29.731Z
Learning: In apps/server/src/trpc/routes/mail.ts, the attachment processing logic conditionally handles mixed attachment types - it preserves existing File-like objects with arrayBuffer methods while only converting serialized attachments that need processing through toAttachmentFiles.

Applied to files:

  • apps/server/src/main.ts
  • apps/server/src/lib/driver/google.ts
  • apps/server/src/trpc/routes/mail.ts
  • apps/mail/lib/email-utils.ts
  • apps/server/src/lib/attachments.ts
  • apps/server/src/types.ts
📚 Learning: during testing phases, debug logging should be kept active in apps/server/src/lib/email-verification...
Learnt from: retrogtx
PR: Mail-0/Zero#1622
File: apps/server/src/lib/email-verification.ts:189-189
Timestamp: 2025-07-05T05:27:24.623Z
Learning: During testing phases, debug logging should be kept active in apps/server/src/lib/email-verification.ts for BIMI validation and email verification debugging, even if it's verbose.

Applied to files:

  • apps/server/src/main.ts
📚 Learning: in apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() met...
Learnt from: retrogtx
PR: Mail-0/Zero#1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.

Applied to files:

  • apps/server/src/main.ts
  • apps/server/src/lib/driver/google.ts
📚 Learning: in apps/mail/lib/hotkeys/mail-list-hotkeys.tsx, the switchcategorybyindex function using hardcoded i...
Learnt from: retrogtx
PR: Mail-0/Zero#1328
File: apps/mail/lib/hotkeys/mail-list-hotkeys.tsx:202-209
Timestamp: 2025-06-18T17:26:50.918Z
Learning: In apps/mail/lib/hotkeys/mail-list-hotkeys.tsx, the switchCategoryByIndex function using hardcoded indices for category hotkeys does not break when users reorder categories, contrary to the theoretical index-shifting issue. The actual implementation has constraints or mechanisms that prevent hotkey targeting issues.

Applied to files:

  • apps/mail/app/(routes)/settings/general/page.tsx
📚 Learning: in react hook form, avoid using useeffect for form state synchronization when the values prop can ha...
Learnt from: retrogtx
PR: Mail-0/Zero#1354
File: apps/mail/components/ui/prompts-dialog.tsx:85-88
Timestamp: 2025-06-20T05:03:16.944Z
Learning: In React Hook Form, avoid using useEffect for form state synchronization when the values prop can handle reactive updates automatically. The values prop is specifically designed for this purpose and is more optimal than manual useEffect-based synchronization.

Applied to files:

  • apps/mail/components/mail/reply-composer.tsx
📚 Learning: in the mail-0/zero application, sender emails are guaranteed to be non-empty when passed to componen...
Learnt from: danteissaias
PR: Mail-0/Zero#618
File: apps/mail/components/mail/mail-iframe.tsx:12-12
Timestamp: 2025-04-07T20:46:11.697Z
Learning: In the Mail-0/Zero application, sender emails are guaranteed to be non-empty when passed to components that handle them, making additional empty string validation unnecessary.

Applied to files:

  • apps/mail/components/mail/reply-composer.tsx
🪛 Biome (2.1.2)
apps/mail/components/create/schedule-send-picker.tsx

[error] 96-96: A form label must be associated with an input.

Consider adding a for or htmlFor attribute to the label element or moving the input element to inside the label element.

(lint/a11y/noLabelWithoutControl)

⏰ 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: cubic · AI code reviewer
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (26)
apps/server/src/lib/schemas.ts (1)

119-135: Smooth rollout of undoSendEnabled needs data back-fill

Great to see the toggle land. Just double-check that any existing user-settings documents persisted before this deploy get re-saved at least once, otherwise old records will surface undefined and the client may treat that as “true”. A tiny migration or a lazy write-back on fetch keeps things fail-safe.

Also applies to: 139-156

apps/server/src/env.ts (1)

14-17: Verify infra bindings before launch

KV namespaces pending_emails_*, scheduled_emails and queue send_email_queue must be provisioned in Wrangler config for every environment (dev, staging, prod). A missing binding = instant 500.

apps/mail/app/(routes)/settings/general/page.tsx (1)

328-342: UI wiring looks solid

The toggle is wired to i18n and form control correctly. Once the default value lands, this component is mission-ready.

apps/mail/components/create/create-email.tsx (5)

7-7: Excellent integration of the undo-send hook!

The import looks clean and follows the established pattern. This is exactly the kind of modular approach we need for Mars mission software - compartmentalized functionality that can be easily tested and maintained.


66-66: Proper hook destructuring pattern.

The destructuring follows React best practices. This kind of clean API design is what makes software maintainable at scale.


89-89: Good addition of the scheduleAt parameter.

The optional parameter is properly typed and integrated into the function signature. This maintains backward compatibility while enabling the new scheduling feature.


97-107: Smart improvement on the email processing logic.

Removing the optional chaining on email.split('@')[0] is actually correct here since email is guaranteed to be a string from the data.to array. The code is cleaner and more efficient. The scheduleAt parameter is properly passed through to the mutation.


124-124: Perfect integration of the undo-send functionality!

Capturing the result and passing it to handleUndoSend with settings is exactly right. This enables the undo feature when emails are scheduled. The flow is logical and maintains the existing success toast while adding the undo capability.

apps/mail/components/ui/toast.tsx (4)

10-11: Brilliant DRY principle implementation!

Consolidating the common button classes is exactly the kind of optimization we need. Less code duplication means fewer bugs and easier maintenance. This is the kind of efficiency thinking that got us to reusable rockets.


30-32: Clean refactoring of button class references.

Using the shared commonButtonClasses constant across all button types maintains consistency while keeping the actionButton's additional pointer and cursor styles. This is solid engineering.


36-36: Good layout improvement with flex-1.

Adding flex-1 to the content class ensures proper flexible sizing within the toast layout. This will help with the new undo-send button layout.


38-38: Smart layout adjustment for better responsiveness.

Changing from w-96 inline-flex to w-full flex makes the toast more responsive and better accommodates the undo action buttons. This kind of adaptive UI design is crucial for user experience.

apps/server/src/db/schema.ts (1)

197-200: Excellent type safety enhancement!

Adding the explicit type annotation with $type<typeof defaultUserSettings>() ensures compile-time type consistency between the database schema and the settings structure. This is the kind of defensive programming that prevents runtime errors. When we're building for Mars, we can't afford type mismatches causing mission-critical failures.

apps/server/src/types.ts (1)

252-268: Superb interface design with excellent documentation!

This IEmailSendBatch interface is exactly what we need for the queue processing system. The documentation is comprehensive - explaining the messageId as both unique identifier and KV key, the connectionId purpose, and the rationale for optional mail property to avoid queue limits. The sendAt timestamp being informational while actual scheduling is controlled elsewhere is a smart architectural decision. This is the kind of clear, well-documented interface that makes systems maintainable at SpaceX scale.

apps/mail/hooks/use-undo-send.ts (4)

6-8: Clean hook structure following React patterns.

The hook setup with TRPC and mutation is textbook React. This modular approach makes testing and maintenance much easier.


11-16: Smart validation logic with good UX considerations.

The checks for queued result and enabled settings are correct. The time remaining calculation with 30-second fallback is solid. The 5-second threshold prevents confusing UX where users click undo but it's too late. This is the kind of thoughtful UX engineering we need.


17-31: Excellent toast implementation with proper error handling!

The toast success message with undo action is perfectly implemented. The async error handling in the onClick is good practice - catching errors and showing appropriate feedback. The 30-second duration matches the undo window perfectly. This is exactly how user feedback should work - clear, actionable, and reliable.


35-35: Perfect hook return pattern.

Returning the handleUndoSend function in an object maintains consistency with other custom hooks and allows for future extensibility.

apps/mail/components/mail/reply-composer.tsx (1)

8-8: Scheduling integration looks clean - this is some next-level email automation.

The threading of the scheduleAt parameter through the function signature and the sendEmail call is architecturally sound. The undo-send integration follows the established patterns we're seeing across the codebase.

Also applies to: 40-40, 108-108, 183-206, 214-214

apps/server/src/lib/attachments.ts (1)

1-7: Interface definition is solid - clean and purposeful like a Falcon Heavy payload bay.

The SerializedAttachment interface covers all the essential properties needed for attachment handling. The optional fields are appropriately marked.

apps/server/src/trpc/routes/mail.ts (2)

518-524: 60-second TTL feels stingy

After an undo we only keep the “cancelled” marker for 1 min. Any retry job starting later might resurrect the email. Suggest holding the tombstone for the same 24 h window as “pending”.

⛔ Skipped due to learnings
Learnt from: AnjanyKumarJaiswal
PR: Mail-0/Zero#1732
File: apps/mail/components/create/email-composer.tsx:634-657
Timestamp: 2025-07-15T03:31:14.991Z
Learning: In draft deletion operations, using setTimeout with a delay (like 500ms) before showing success toast notifications improves UX by allowing UI state changes (like closing composers and clearing IDs) to complete before displaying the toast, preventing jarring immediate toast appearances that could disappear quickly during interface transitions.

488-494: Micro-optimization: batch the attachment lift-off

toAttachmentFiles is called per item; that’s O(n) conversions spawning new arrays each loop. Convert once:

-attachments?.map((att: any) =>
-  typeof att?.arrayBuffer === 'function' ? att : toAttachmentFiles([att])[0],
-)
+attachments
+  ? toAttachmentFiles(attachments as any[])
+  : []

Cleaner and cheaper.

⛔ Skipped due to learnings
Learnt from: retrogtx
PR: Mail-0/Zero#1468
File: apps/server/src/trpc/routes/mail.ts:386-391
Timestamp: 2025-06-27T04:59:29.731Z
Learning: In apps/server/src/trpc/routes/mail.ts, the attachment processing logic conditionally handles mixed attachment types - it preserves existing File-like objects with arrayBuffer methods while only converting serialized attachments that need processing through toAttachmentFiles.
apps/mail/components/create/schedule-send-picker.tsx (3)

1-13: Solid imports and interface definition

The imports are well-organized and the interface is crystal clear. Props are properly typed with optional fields marked appropriately.


15-19: Clean timezone handling

Nice implementation for converting to local input value. The timezone offset calculation is spot on.


63-63: ScheduleAt ISO format compatibility confirmed

The backend’s new Date(scheduleAt).getTime() call fully supports ISO-formatted strings, so there’s no mismatch between the frontend’s toISOString() and the server. You can safely ignore this review comment—no changes required.

Likely an incorrect or invalid review comment.

apps/server/src/main.ts (1)

883-889: Good addition to scheduled tasks

Processing scheduled emails before expired subscriptions makes sense. The order is logical.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cubic analysis

11 issues found across 18 files • Review in cubic

React with 👍 or 👎 to teach cubic. You can also tag @cubic-dev-ai to give feedback, ask questions, or re-run the review.

@jazzberry-ai
Copy link

jazzberry-ai bot commented Aug 4, 2025

Bug Report

Name Severity Example test case Description
Unbounded ScheduleAt values can cause DoS Medium Schedule a very large number of emails far into the future A malicious user could schedule a large number of emails to be sent in the distant future, potentially filling up the KV store and/or queue, leading to a denial of service. There's no server-side limit on how far in the future an email can be scheduled.
Unsanitized delaySeconds can cause queue issues Medium Directly enqueue a message with a very large delaySeconds A malicious user could bypass the UI and directly enqueue a message with a very large delaySeconds, potentially exceeding the queue's maximum delay or causing other unexpected behavior.
Potential timezone issues with scheduled emails Low Schedule email to be sent at a specific time in a different timezone Timezone offset is only accounted for when the user sets the time. The queue delay is then calculated using the server's current time, leading to a potential mismatch.

Comments? Email us.

@jazzberry-ai
Copy link

jazzberry-ai bot commented Aug 4, 2025

Bug Report

Name Severity Example test case Description
Unauthorized Email Cancellation High 1. User A schedules an email. 2. User B guesses the messageId. 3. User B calls the mail.unsend mutation with the messageId. User B can cancel User A's scheduled email because there is no authorization check in the unsend mutation.

Comments? Email us.

… settings page, improving code readability and maintainability.

- Added validation for scheduled email times to ensure they are in the future and correctly formatted.
- Updated the email scheduling logic to handle both queued and scheduled emails more effectively.

- Add undo button for >12 hours schedules
@jazzberry-ai
Copy link

jazzberry-ai bot commented Aug 5, 2025

Bug Report

Name Severity Example test case Description
Inconsistent attachment handling Medium Schedule an email with attachments for a time more than 12 hours in the future. Observe if the email is sent correctly after 12 hours. Then, schedule an email with attachments for less than 12 hours. Verify that both work as expected to be sure there's no difference between the two attachment paths. When scheduling emails with attachments, the attachment processing logic differs based on whether the scheduled time is less than or greater than 12 hours in the future, as attachments may become unavailable or have been changed by the time it is supposed to send.

Comments? Email us.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (13)
apps/server/src/lib/attachments.ts (1)

15-27: Outstanding rocket-grade engineering on the type safety front!

I see you've evolved from the previous any[] return type to proper AttachmentFile[] typing - this is exactly the kind of precision engineering we need when we're launching emails into the digital stratosphere. The buffer slicing technique with buffer.byteOffset is chef's kiss level optimization, ensuring we get clean ArrayBuffer boundaries without any memory leakage.

apps/mail/lib/email-utils.ts (1)

225-250: Brilliant pivot to Zod schemas - this is like upgrading from a horse-drawn carriage to a Tesla!

The migration from manual type checking to Zod schemas is absolutely genius. The safeParse approach in your type guards automatically handles all the edge cases that were previously flagged in reviews - no more worrying about sendAt being accidentally a string when it should be a number. This is the kind of bulletproof type validation that would make even the most paranoid rocket engineer sleep soundly.

apps/mail/components/mail/reply-composer.tsx (1)

214-216: Excellent UX engineering with that 500ms setTimeout buffer!

You've solved the jarring toast collision problem with the precision of a SpaceX landing sequence. The 500ms delay ensures the "Email Sent" confirmation lands cleanly before the undo option appears - no more competing toasts fighting for attention like rockets on adjacent launch pads.

apps/mail/hooks/use-undo-send.ts (1)

12-35: Phenomenal type safety upgrade and logic precision!

The settings parameter now uses proper UserSettings typing instead of any - that's the kind of type safety that prevents mission failures! The time calculation logic is rock solid, and using timeRemaining for toast duration ensures the undo button doesn't overstay its welcome like a guest at a Mars colony planning meeting.

apps/server/src/lib/driver/google.ts (2)

721-740: Mars-level attachment processing, but we need better error visibility! 🚀

The dual-format attachment handling is brilliant - supporting both base64 strings and arrayBuffer functions gives us the flexibility we need for different data sources. The async buffer conversion is clean.

However, silently skipping failed attachments at line 732 could hide critical issues from users. When someone's important contract attachment disappears into the void, that's not exactly "rapid unscheduled disassembly" - it's just bad UX.

Consider at minimum logging the failure or surfacing an error to help debug attachment processing issues.

The default contentType fallback is solid engineering though - much better than leaving it undefined.


1241-1260: Consistent attachment rocket fuel across the codebase! 🚀

Excellent consistency - the parseOutgoing method mirrors the createDraft attachment processing logic perfectly. This kind of standardization is what makes software reliable at scale.

Same concern applies here though - the silent continue at line 1252 when attachment conversion fails could leave users wondering why their attachments vanished into the digital equivalent of a black hole.

Consider implementing a centralized attachment processing utility that both methods can share, with proper error handling and logging. Something like:

+const processAttachment = async (attachment: any): Promise<string | null> => {
+  try {
+    if (typeof attachment?.base64 === 'string') {
+      return attachment.base64;
+    }
+    if (typeof attachment?.arrayBuffer === 'function') {
+      const buffer = Buffer.from(await attachment.arrayBuffer());
+      return buffer.toString('base64');
+    }
+    console.warn('Attachment processing failed: unsupported format', { attachment });
+    return null;
+  } catch (error) {
+    console.error('Attachment processing error:', error);
+    return null;
+  }
+};

That's the kind of defensive programming that prevents customer support headaches.

apps/mail/components/create/email-composer.tsx (1)

471-475: Houston, we still have a problem - no server validation

Frontend validation is like a paper rocket - looks good but won't get you to orbit. Anyone with DevTools can bypass this and send garbage timestamps to your server. Add proper validation on the backend or watch your scheduled emails launch at random times.

apps/mail/components/create/schedule-send-picker.tsx (3)

107-113: Fix the accessibility - this is Space 101

Your label is floating in space, not connected to anything. Screen readers can't figure out what this input is for. Basic accessibility:

-          <label className="text-sm font-semibold">Choose date & time</label>
+          <label htmlFor="schedule-datetime" className="text-sm font-semibold">Choose date & time</label>
           <input
+            id="schedule-datetime"
             type="datetime-local"
             value={displayValue}
             onChange={handleChange}
             className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:opacity-0"
           />

98-98: Get that console.error out of the render path

Every render that hits an error will spam the console. This is like launching rockets with debug mode on - not production ready. Either remove it or move the logging elsewhere.

-                console.error('Error formatting date', error);
+                // Error is handled by returning fallback

91-101: Overengineered date formatting

Why are we using an IIFE here? This isn't some complex initialization logic. Extract this to a simple function like a normal person:

+  const formatScheduledDate = (localValue: string) => {
+    if (!localValue) return 'Send later';
+    const parsed = new Date(localValue);
+    if (!isValid(parsed)) return 'Send later';
+    try {
+      return format(parsed, 'dd MMM yyyy hh:mm aaa');
+    } catch (error) {
+      console.error('Error formatting date', error);
+      return 'Send later';
+    }
+  };

   <span>
-    {(() => {
-      if (!localValue) return 'Send later';
-      const parsed = new Date(localValue);
-      if (!isValid(parsed)) return 'Send later';
-      try {
-        return format(parsed, 'dd MMM yyyy hh:mm aaa');
-      } catch (error) {
-        console.error('Error formatting date', error);
-        return 'Send later';
-      }
-    })()}
+    {formatScheduledDate(localValue)}
   </span>
apps/server/src/main.ts (2)

789-789: Don't throw away type safety like it's yesterday's rocket

Going from MessageBatch<any> to any is like removing sensors from your spacecraft. Keep the types explicit:

-  async queue(batch: any) {
+  async queue(batch: MessageBatch<any> | { queue: string; messages: Array<{ body: IEmailSendBatch }> }) {

916-965: This won't scale to Mars

Iterating through all scheduled emails works for now, but when you have millions of users, this becomes a bottleneck. Also, that 12-hour window is as arbitrary as saying "Mars is 12 minutes away":

+      const SCHEDULE_WINDOW_HOURS = parseInt(this.env.SCHEDULE_WINDOW_HOURS || '12');
       const now = Date.now();
-      const twelveHoursFromNow = now + (12 * 60 * 60 * 1000);
+      const scheduleWindowMs = SCHEDULE_WINDOW_HOURS * 60 * 60 * 1000;
+      const scheduleWindowEnd = now + scheduleWindowMs;

For true scale, consider:

  1. Sharding by time buckets
  2. Using a proper job scheduling system
  3. Processing in parallel workers
apps/server/src/trpc/routes/mail.ts (1)

423-440: Add maximum scheduling window - we need guardrails! 🛡️

While you've added validation for invalid dates and past times (good work!), you're missing the maximum scheduling window. Without an upper bound, someone could schedule an email for 2077 when we'll all be on Mars.

Add a max scheduling window (e.g., 30 days):

 if (parsedTime <= now) {
   return { success: false, error: 'Schedule time must be in the future' } as const;
 }
+
+const maxScheduleWindow = 30 * 24 * 60 * 60 * 1000; // 30 days
+if (parsedTime > now + maxScheduleWindow) {
+  return { success: false, error: 'Schedule time cannot be more than 30 days in the future' } as const;
+}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 47012f6 and 2b8f798.

📒 Files selected for processing (12)
  • apps/mail/app/(routes)/settings/general/page.tsx (2 hunks)
  • apps/mail/components/create/email-composer.tsx (9 hunks)
  • apps/mail/components/create/schedule-send-picker.tsx (1 hunks)
  • apps/mail/components/mail/reply-composer.tsx (6 hunks)
  • apps/mail/hooks/use-undo-send.ts (1 hunks)
  • apps/mail/lib/email-utils.ts (2 hunks)
  • apps/server/src/lib/attachments.ts (1 hunks)
  • apps/server/src/lib/driver/google.ts (2 hunks)
  • apps/server/src/main.ts (5 hunks)
  • apps/server/src/trpc/routes/mail.ts (6 hunks)
  • apps/server/src/types.ts (1 hunks)
  • apps/server/wrangler.jsonc (12 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (AGENT.md)

**/*.{js,jsx,ts,tsx}: Use 2-space indentation
Use single quotes for strings
Limit lines to 100 characters in length
Semicolons are required at the end of statements

Files:

  • apps/server/src/lib/driver/google.ts
  • apps/mail/components/mail/reply-composer.tsx
  • apps/mail/app/(routes)/settings/general/page.tsx
  • apps/mail/hooks/use-undo-send.ts
  • apps/server/src/types.ts
  • apps/server/src/lib/attachments.ts
  • apps/mail/lib/email-utils.ts
  • apps/mail/components/create/schedule-send-picker.tsx
  • apps/server/src/trpc/routes/mail.ts
  • apps/mail/components/create/email-composer.tsx
  • apps/server/src/main.ts
**/*.{js,jsx,ts,tsx,css,scss}

📄 CodeRabbit Inference Engine (AGENT.md)

Use Prettier with sort-imports and Tailwind plugins for code formatting

Files:

  • apps/server/src/lib/driver/google.ts
  • apps/mail/components/mail/reply-composer.tsx
  • apps/mail/app/(routes)/settings/general/page.tsx
  • apps/mail/hooks/use-undo-send.ts
  • apps/server/src/types.ts
  • apps/server/src/lib/attachments.ts
  • apps/mail/lib/email-utils.ts
  • apps/mail/components/create/schedule-send-picker.tsx
  • apps/server/src/trpc/routes/mail.ts
  • apps/mail/components/create/email-composer.tsx
  • apps/server/src/main.ts
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (AGENT.md)

Enable TypeScript strict mode

Files:

  • apps/server/src/lib/driver/google.ts
  • apps/mail/components/mail/reply-composer.tsx
  • apps/mail/app/(routes)/settings/general/page.tsx
  • apps/mail/hooks/use-undo-send.ts
  • apps/server/src/types.ts
  • apps/server/src/lib/attachments.ts
  • apps/mail/lib/email-utils.ts
  • apps/mail/components/create/schedule-send-picker.tsx
  • apps/server/src/trpc/routes/mail.ts
  • apps/mail/components/create/email-composer.tsx
  • apps/server/src/main.ts
**/*.{css,js,ts,jsx,tsx,mdx}

📄 CodeRabbit Inference Engine (.cursor/rules/tailwind-css-v4.mdc)

**/*.{css,js,ts,jsx,tsx,mdx}: Chain variants together for composable variants (e.g., group-has-data-potato:opacity-100).
Use new variants such as starting, not-*, inert, nth-*, in-*, open (for :popover-open), and ** for all descendants.
Do not use deprecated utilities like bg-opacity-*, text-opacity-*, border-opacity-*, and divide-opacity-*; use the new syntax (e.g., bg-black/50).
Use renamed utilities: shadow-sm is now shadow-xs, shadow is now shadow-sm, drop-shadow-sm is now drop-shadow-xs, drop-shadow is now drop-shadow-sm, blur-sm is now blur-xs, blur is now blur-sm, rounded-sm is now rounded-xs, rounded is now rounded-sm, outline-none is now outline-hidden.
Use bg-(--brand-color) syntax for CSS variables in arbitrary values instead of bg-[--brand-color].
Stacked variants now apply left-to-right instead of right-to-left.

Files:

  • apps/server/src/lib/driver/google.ts
  • apps/mail/components/mail/reply-composer.tsx
  • apps/mail/app/(routes)/settings/general/page.tsx
  • apps/mail/hooks/use-undo-send.ts
  • apps/server/src/types.ts
  • apps/server/src/lib/attachments.ts
  • apps/mail/lib/email-utils.ts
  • apps/mail/components/create/schedule-send-picker.tsx
  • apps/server/src/trpc/routes/mail.ts
  • apps/mail/components/create/email-composer.tsx
  • apps/server/src/main.ts
🧠 Learnings (16)
📓 Common learnings
Learnt from: retrogtx
PR: Mail-0/Zero#1468
File: apps/server/src/trpc/routes/mail.ts:331-331
Timestamp: 2025-06-28T03:56:09.376Z
Learning: In apps/server/src/trpc/routes/mail.ts, the user indicated they are not using ISO format for the scheduleAt parameter, despite the frontend code showing toISOString() usage in the ScheduleSendPicker component.
📚 Learning: in apps/server/src/trpc/routes/mail.ts, the attachment processing logic conditionally handles mixed ...
Learnt from: retrogtx
PR: Mail-0/Zero#1468
File: apps/server/src/trpc/routes/mail.ts:386-391
Timestamp: 2025-06-27T04:59:29.731Z
Learning: In apps/server/src/trpc/routes/mail.ts, the attachment processing logic conditionally handles mixed attachment types - it preserves existing File-like objects with arrayBuffer methods while only converting serialized attachments that need processing through toAttachmentFiles.

Applied to files:

  • apps/server/src/lib/driver/google.ts
  • apps/server/src/types.ts
  • apps/server/src/lib/attachments.ts
  • apps/mail/lib/email-utils.ts
  • apps/server/src/trpc/routes/mail.ts
  • apps/server/src/main.ts
📚 Learning: in apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() met...
Learnt from: retrogtx
PR: Mail-0/Zero#1734
File: apps/server/src/lib/driver/google.ts:211-221
Timestamp: 2025-07-15T06:46:33.349Z
Learning: In apps/server/src/lib/driver/google.ts, the normalization of "draft" to "drafts" in the count() method is necessary because the navigation item in apps/mail/config/navigation.ts has id: 'drafts' (plural) while the Google API returns "draft" (singular). The nav-main.tsx component matches stats by comparing stat.label with item.id, so the backend must return "drafts" for the draft counter badge to appear in the sidebar.

Applied to files:

  • apps/server/src/lib/driver/google.ts
  • apps/server/src/types.ts
  • apps/server/src/main.ts
📚 Learning: in apps/server/src/trpc/routes/mail.ts, the user indicated they are not using iso format for the sch...
Learnt from: retrogtx
PR: Mail-0/Zero#1468
File: apps/server/src/trpc/routes/mail.ts:331-331
Timestamp: 2025-06-28T03:56:09.376Z
Learning: In apps/server/src/trpc/routes/mail.ts, the user indicated they are not using ISO format for the scheduleAt parameter, despite the frontend code showing toISOString() usage in the ScheduleSendPicker component.

Applied to files:

  • apps/mail/components/mail/reply-composer.tsx
  • apps/mail/app/(routes)/settings/general/page.tsx
  • apps/mail/hooks/use-undo-send.ts
  • apps/server/src/types.ts
  • apps/server/wrangler.jsonc
  • apps/mail/lib/email-utils.ts
  • apps/mail/components/create/schedule-send-picker.tsx
  • apps/server/src/trpc/routes/mail.ts
  • apps/mail/components/create/email-composer.tsx
  • apps/server/src/main.ts
📚 Learning: in apps/mail/lib/hotkeys/mail-list-hotkeys.tsx, the switchcategorybyindex function using hardcoded i...
Learnt from: retrogtx
PR: Mail-0/Zero#1328
File: apps/mail/lib/hotkeys/mail-list-hotkeys.tsx:202-209
Timestamp: 2025-06-18T17:26:50.918Z
Learning: In apps/mail/lib/hotkeys/mail-list-hotkeys.tsx, the switchCategoryByIndex function using hardcoded indices for category hotkeys does not break when users reorder categories, contrary to the theoretical index-shifting issue. The actual implementation has constraints or mechanisms that prevent hotkey targeting issues.

Applied to files:

  • apps/mail/components/mail/reply-composer.tsx
  • apps/mail/app/(routes)/settings/general/page.tsx
  • apps/mail/hooks/use-undo-send.ts
  • apps/mail/lib/email-utils.ts
  • apps/mail/components/create/email-composer.tsx
  • apps/server/src/main.ts
📚 Learning: in draft deletion operations, using settimeout with a delay (like 500ms) before showing success toas...
Learnt from: AnjanyKumarJaiswal
PR: Mail-0/Zero#1732
File: apps/mail/components/create/email-composer.tsx:634-657
Timestamp: 2025-07-15T03:31:14.991Z
Learning: In draft deletion operations, using setTimeout with a delay (like 500ms) before showing success toast notifications improves UX by allowing UI state changes (like closing composers and clearing IDs) to complete before displaying the toast, preventing jarring immediate toast appearances that could disappear quickly during interface transitions.

Applied to files:

  • apps/mail/components/mail/reply-composer.tsx
  • apps/mail/hooks/use-undo-send.ts
  • apps/mail/components/create/email-composer.tsx
📚 Learning: in the zero mail application, when implementing the "trust sender" feature, the banner should disapp...
Learnt from: danteissaias
PR: Mail-0/Zero#618
File: apps/mail/components/mail/mail-iframe.tsx:102-102
Timestamp: 2025-04-07T20:48:48.213Z
Learning: In the Zero mail application, when implementing the "Trust Sender" feature, the banner should disappear immediately when a user clicks the "Trust Sender" button without showing a loading state. This is achieved by setting `setImagesEnabled(true)` at the beginning of the `onTrustSender` function, providing immediate visual feedback while the backend operation continues asynchronously.

Applied to files:

  • apps/mail/components/mail/reply-composer.tsx
📚 Learning: in react hook form, avoid using useeffect for form state synchronization when the values prop can ha...
Learnt from: retrogtx
PR: Mail-0/Zero#1354
File: apps/mail/components/ui/prompts-dialog.tsx:85-88
Timestamp: 2025-06-20T05:03:16.944Z
Learning: In React Hook Form, avoid using useEffect for form state synchronization when the values prop can handle reactive updates automatically. The values prop is specifically designed for this purpose and is more optimal than manual useEffect-based synchronization.

Applied to files:

  • apps/mail/app/(routes)/settings/general/page.tsx
  • apps/mail/components/create/schedule-send-picker.tsx
📚 Learning: applies to **/*.{ts,tsx} : enable typescript strict mode...
Learnt from: CR
PR: Mail-0/Zero#0
File: AGENT.md:0-0
Timestamp: 2025-08-03T20:41:43.899Z
Learning: Applies to **/*.{ts,tsx} : Enable TypeScript strict mode

Applied to files:

  • apps/mail/hooks/use-undo-send.ts
📚 Learning: during testing phases, debug logging should be kept active in apps/server/src/lib/email-verification...
Learnt from: retrogtx
PR: Mail-0/Zero#1622
File: apps/server/src/lib/email-verification.ts:189-189
Timestamp: 2025-07-05T05:27:24.623Z
Learning: During testing phases, debug logging should be kept active in apps/server/src/lib/email-verification.ts for BIMI validation and email verification debugging, even if it's verbose.

Applied to files:

  • apps/mail/lib/email-utils.ts
  • apps/server/src/trpc/routes/mail.ts
  • apps/mail/components/create/email-composer.tsx
  • apps/server/src/main.ts
📚 Learning: in the mail-0/zero application, sender emails are guaranteed to be non-empty when passed to componen...
Learnt from: danteissaias
PR: Mail-0/Zero#618
File: apps/mail/components/mail/mail-iframe.tsx:12-12
Timestamp: 2025-04-07T20:46:11.697Z
Learning: In the Mail-0/Zero application, sender emails are guaranteed to be non-empty when passed to components that handle them, making additional empty string validation unnecessary.

Applied to files:

  • apps/mail/lib/email-utils.ts
  • apps/server/src/trpc/routes/mail.ts
📚 Learning: in the mail-0/zero theme system (apps/mail/lib/themes/theme-utils.ts), when color themes are being a...
Learnt from: snehendu098
PR: Mail-0/Zero#1323
File: apps/mail/lib/themes/theme-utils.ts:318-318
Timestamp: 2025-06-24T06:22:58.753Z
Learning: In the Mail-0/Zero theme system (apps/mail/lib/themes/theme-utils.ts), when color themes are being applied, all color values come in HSL format, so there's no need for additional format validation when converting colors with hslToHex().

Applied to files:

  • apps/mail/lib/email-utils.ts
📚 Learning: applies to **/*.{css,js,ts,jsx,tsx,mdx} : do not use deprecated utilities like `bg-opacity-*`, `text...
Learnt from: CR
PR: Mail-0/Zero#0
File: .cursor/rules/tailwind-css-v4.mdc:0-0
Timestamp: 2025-08-03T20:42:04.207Z
Learning: Applies to **/*.{css,js,ts,jsx,tsx,mdx} : Do not use deprecated utilities like `bg-opacity-*`, `text-opacity-*`, `border-opacity-*`, and `divide-opacity-*`; use the new syntax (e.g., `bg-black/50`).

Applied to files:

  • apps/mail/lib/email-utils.ts
  • apps/mail/components/create/email-composer.tsx
📚 Learning: applies to **/*.{css,js,ts,jsx,tsx,mdx} : use `bg-(--brand-color)` syntax for css variables in arbit...
Learnt from: CR
PR: Mail-0/Zero#0
File: .cursor/rules/tailwind-css-v4.mdc:0-0
Timestamp: 2025-08-03T20:42:04.207Z
Learning: Applies to **/*.{css,js,ts,jsx,tsx,mdx} : Use `bg-(--brand-color)` syntax for CSS variables in arbitrary values instead of `bg-[--brand-color]`.

Applied to files:

  • apps/mail/lib/email-utils.ts
📚 Learning: for the "upgrade" link in addconnectiondialog, using a proper element instead of a w...
Learnt from: danteissaias
PR: Mail-0/Zero#902
File: apps/mail/components/connection/add.tsx:77-77
Timestamp: 2025-05-07T16:55:46.513Z
Learning: For the "Upgrade" link in AddConnectionDialog, using a proper <button> element instead of a <span> with onClick is recognized as an accessibility improvement but was deferred as out of scope in PR #902 (CSS variables PR).

Applied to files:

  • apps/mail/components/create/schedule-send-picker.tsx
  • apps/mail/components/create/email-composer.tsx
📚 Learning: applies to **/*.{css,js,ts,jsx,tsx,mdx} : use renamed utilities: `shadow-sm` is now `shadow-xs`, `sh...
Learnt from: CR
PR: Mail-0/Zero#0
File: .cursor/rules/tailwind-css-v4.mdc:0-0
Timestamp: 2025-08-03T20:42:04.207Z
Learning: Applies to **/*.{css,js,ts,jsx,tsx,mdx} : Use renamed utilities: `shadow-sm` is now `shadow-xs`, `shadow` is now `shadow-sm`, `drop-shadow-sm` is now `drop-shadow-xs`, `drop-shadow` is now `drop-shadow-sm`, `blur-sm` is now `blur-xs`, `blur` is now `blur-sm`, `rounded-sm` is now `rounded-xs`, `rounded` is now `rounded-sm`, `outline-none` is now `outline-hidden`.

Applied to files:

  • apps/mail/components/create/email-composer.tsx
🪛 GitHub Actions: autofix.ci
apps/mail/hooks/use-undo-send.ts

[warning] 5-5: ESLint (no-unused-vars): Identifier 'isQueuedSendResult' is imported but never used. Consider removing this import.

apps/server/src/trpc/routes/mail.ts

[warning] 570-570: ESLint (no-unused-vars): Variable 'payload' is declared but never used. Unused variables should start with a '_'. Consider removing this declaration.

🪛 Biome (2.1.2)
apps/mail/components/create/schedule-send-picker.tsx

[error] 107-107: A form label must be associated with an input.

Consider adding a for or htmlFor attribute to the label element or moving the input element to inside the label element.

(lint/a11y/noLabelWithoutControl)

⏰ 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). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (17)
apps/server/src/types.ts (1)

252-257: Clean interface design - this is the kind of elegant simplicity that makes rockets fly straight!

The IEmailSendBatch interface is beautifully architected. Optional sendAt for scheduling, clean extension of IOutgoingMessage, and the optional draftId - it's like you've designed the perfect payload structure for launching emails through the queue system.

apps/mail/app/(routes)/settings/general/page.tsx (2)

183-320: Performance optimization that would make a Tesla Model S Plaid jealous! ⚡

Outstanding refactoring - extracting those inline render functions into memoized callbacks is exactly the kind of performance optimization that separates good code from great code. No more unnecessary re-renders causing UI lag when users are trying to configure their email settings.

The dependency arrays are spot-on too - empty arrays for static renders, [aliases] for the email alias field that actually needs to react to data changes. This is the kind of attention to detail that makes software feel snappy and responsive.

Your users will never notice this optimization, but their browsers' CPU cycles will thank you. That's the mark of quality engineering.


305-320: Undo-send feature integration smoother than a Falcon Heavy landing! 🚀

The undo-send toggle integration is textbook perfect - follows the exact same patterns as the other switch fields, proper localization, consistent styling. This is how you add features without creating technical debt.

The positioning between auto-read and animations makes logical sense from a UX perspective too. Users expect email-related features to be grouped together.

Clean implementation that maintains the codebase's established conventions. This is the kind of consistent engineering that makes features feel like they were always part of the product.

Also applies to: 367-369

apps/server/wrangler.jsonc (4)

66-81: Email scheduling infrastructure deployed like a Starship stack! 🚀

Perfect queue configuration - the send-email-queue is properly set up as both producer and consumer, following the exact same patterns as the existing thread-queue and subscribe-queue. This kind of consistency makes infrastructure predictable and maintainable.

The binding name "send_email_queue" follows the established snake_case convention too. Clean infrastructure engineering.


171-182: KV namespace architecture more organized than a SpaceX mission control! 🚀

Excellent separation of concerns with the three KV namespaces - pending_emails_status, pending_emails_payload, and scheduled_emails. This granular approach gives you better control over data lifecycle and performance optimization.

The ID naming scheme with base + suffix is clean and predictable. Much better than throwing everything into one massive namespace.

This kind of thoughtful data architecture prevents headaches down the road when you need to scale or debug scheduling issues.


118-118: Cron scheduling more precise than a Raptor engine burn sequence! ⏰

The cron trigger updates look solid - hourly processing (0 * * * *) added across all environments to handle scheduled email processing. The local environment keeps the every-minute schedule for development testing, while staging and production use more reasonable intervals.

No duplicate triggers issue visible in the current configuration - looks like that got resolved.

This scheduling setup gives you the flexibility to test rapidly in local while being resource-conscious in production. Smart engineering.

Also applies to: 191-191, 370-370


253-267: Multi-environment consistency better than synchronized Falcon Heavy boosters! 🚀

Outstanding consistency across local, staging, and production environments. The send-email-queue configuration is identical in structure, just with proper environment-specific naming (send-email-queue-staging, send-email-queue-prod).

Same pattern applied to all the KV namespaces too. This kind of environmental parity prevents those nasty "works in staging but breaks in prod" scenarios that keep engineers up at night.

This is exactly how infrastructure should be managed - consistent, predictable, and scalable across all deployment targets.

Also applies to: 439-453

apps/mail/components/create/email-composer.tsx (6)

20-23: Clean imports, but you're not using that useCallback yet

Good to see you imported useCallback, but then you went and created inline arrow functions anyway. Classic move. Let's actually use it for those schedule handlers.


78-79: Interface looks solid

Optional parameter for scheduling - clean and backwards compatible. Ship it.


151-152: State management on point

Clean state setup for scheduling. Default validity to true is the right call - no schedule means send now.


707-714: Finally, proper use of useCallback!

Now we're talking. Memoized handlers with correct dependencies. This is how you prevent unnecessary re-renders and keep your React app running at ludicrous speed.


1330-1330: Smart button disabling

Good call disabling the send button when schedule is invalid. Users can't click what they can't click.


1341-1345: Perfect execution on the picker integration

Using those memoized callbacks instead of inline functions. This is the way. No more recreating functions on every render like some kind of memory-wasting amateur hour.

apps/mail/components/create/schedule-send-picker.tsx (1)

29-46: State sync done right

Good work keeping the local state in sync with the parent value. The useEffect properly handles updates when the parent changes the schedule. No more out-of-sync picker issues.

apps/server/src/main.ts (2)

838-863: This attachment processing is more complex than a Mars landing sequence

The logic works but it's hard to follow. Let's make it cleaner:

-              if (Array.isArray((payload as any).attachments)) {
-                const attachments = (payload as any).attachments;
-                const needsProcessing = [];
-                const processedAttachments = [];
-                
-                for (let i = 0; i < attachments.length; i++) {
-                  const att = attachments[i];
-                  if (typeof att?.arrayBuffer === 'function') {
-                    processedAttachments[i] = att;
-                  } else {
-                    needsProcessing.push({ attachment: att, index: i });
-                  }
-                }
-                
-                if (needsProcessing.length > 0) {
-                  const attachmentsToProcess = needsProcessing.map(item => item.attachment);
-                  const processed = toAttachmentFiles(attachmentsToProcess);
-                  
-                  for (let i = 0; i < needsProcessing.length; i++) {
-                    const { index } = needsProcessing[i];
-                    processedAttachments[index] = processed[i];
-                  }
-                }
-                
-                (payload as any).attachments = processedAttachments;
-              }
+              if (Array.isArray(payload.attachments)) {
+                payload.attachments = payload.attachments.map((att) => {
+                  // Keep existing File-like objects, convert serialized ones
+                  if (typeof att?.arrayBuffer === 'function') {
+                    return att;
+                  }
+                  return toAttachmentFiles([att])[0];
+                });
+              }

Actually, wait... looking closer, your approach maintains the exact array indices which is important for attachment ordering. The complex logic is justified here. Carry on.


908-914: Clean separation of scheduled tasks

Good call splitting this into separate methods. Each does one thing well - like having separate stages on a rocket.

apps/server/src/trpc/routes/mail.ts (1)

238-238: Explicit typing - this is the way! 🚀

Adding explicit string types to the map functions improves type safety. Small changes like this compound into robust systems.

Also applies to: 283-283

@jazzberry-ai
Copy link

jazzberry-ai bot commented Aug 5, 2025

Bug Report

Name: Insecure Date Parsing in Email Scheduling
Severity: Medium

Example test case:

  1. Set your timezone to UTC-12.
  2. Attempt to schedule an email for "2025-08-06 00:00:00" (midnight UTC).
  3. The email might be scheduled, but sent in the past.

Description:
The Date.parse function in apps/server/src/trpc/routes/mail.ts is used to validate the scheduled time. This function is lenient and may parse invalid date formats. Additionally, the parsed time is not timezone-aware, which can cause emails to be scheduled at incorrect times, potentially in the past, depending on the user's timezone. A more robust date parsing mechanism using luxon or date-fns with explicit timezone handling is recommended. Also, the validation should happen in the UI to give immediate feedback.

Name: Email Unsend Authorization Bypass via Payload Manipulation
Severity: High

Example test case:

  1. User A schedules an email.
  2. An attacker (User B) obtains the messageId of User A's scheduled email.
  3. The attacker crafts a request to the unsend mutation with the messageId, but ensures that the payload associated with that messageId in payloadKV does not contain a connectionId field.
  4. The authorization check in the unsend mutation (payload.connectionId && payload.connectionId !== activeConnection.id) will be bypassed because payload.connectionId will be undefined.
  5. The attacker will be able to successfully cancel User A's scheduled email.

Description:
The authorization check in the unsend mutation in apps/server/src/trpc/routes/mail.ts can be bypassed if the payload associated with a scheduled email in payloadKV does not contain a connectionId field. This allows an attacker to cancel emails scheduled by other users if they can obtain the messageId.

Comments? Email us.

@jazzberry-ai
Copy link

jazzberry-ai bot commented Aug 5, 2025

Bug Report

Name Severity Example test case Description
Timezone Discrepancy in Email Scheduling High 1. Set the browser's timezone to a non-UTC timezone (e.g., America/Los_Angeles). 2. Schedule an email for a time in the future relative to the browser's timezone, but in the past if interpreted as UTC. 3. Trigger the send email mutation. 4. Observe that the server rejects the request due to the schedule time being in the past. The client-side ScheduleSendPicker adjusts the date to the user's local timezone before sending it to the server. However, the server-side validation in mail.ts doesn't account for this timezone conversion, leading to incorrect validation and potential rejection of valid schedule times.
Race Condition in Email Cancellation Low 1. Rapidly schedule an email and then immediately cancel it. 2. Observe (if possible) that the email is still sent despite being cancelled. There is a small window between setting the 'cancelled' status and deleting the payload where the queue worker might pick up the message and attempt to send it.
Potential Performance Issues with Large Attachments Medium 1. Attach a very large file (e.g., 50MB or larger) to an email. 2. Schedule the email for sending or attempt to send it immediately. 3. Observe the server's performance and resource consumption. Check for worker crashes or timeouts. The base64 encoding and decoding of large attachments could lead to performance issues and high memory consumption on the server, potentially causing worker crashes or timeouts. There are no explicit size limits enforced on attachments.
Potential Errors During Attachment Processing in Queue Worker Medium 1. Schedule an email with a corrupted or invalid attachment. 2. Observe the queue worker logs for errors during attachment processing. 3. Verify whether the email is sent correctly or if the worker crashes. If there's an error during attachment processing within the queue worker (e.g., base64 decoding fails or arrayBuffer cannot be read), the worker might crash or the email might be sent without attachments. The error handling in this section could be improved to catch these specific exceptions and handle them gracefully.
Potential Non-Idempotency Low 1. Schedule an email. 2. Simulate the queue worker crashing after sending the email, but before deleting the payload and status from KV. 3. Observe whether the email is sent twice. The email might still be sent twice in rare cases: If the worker crashes after sending the email but before deleting the payload and status from KV. The code should be idempotent.

Comments? Email us.

@jazzberry-ai
Copy link

jazzberry-ai bot commented Aug 5, 2025

Bug Report

Name Severity Example test case Description
Race Condition in unsend Mutation High Schedule an email for 30 seconds in the future. Immediately after scheduling, call the unsend mutation. If the email is dequeued from the send_email_queue and is being processed by a worker at the same time the unsend mutation is called, the worker might still proceed to send the email even after the unsend mutation has removed the data from KV storage.
Incomplete Error Handling in unsend Mutation Medium Schedule an email. As a different user, attempt to call the unsend mutation The server checks ownership for the scheduled email, but doesn't prevent deleting data from pending email namespaces if the email hasn't yet reached the scheduled namespace.
Scheduling of Emails in the Past Medium Schedule an email in the past using a modified request. The lack of backend validation for scheduleAt allows emails to be scheduled in the past, leading to unpredictable behavior. A user could potentially modify the request to bypass the frontend validation and schedule an email in the past.

Comments? Email us.

@jazzberry-ai
Copy link

jazzberry-ai bot commented Aug 5, 2025

Bug Report

Name: Race condition in unsend mutation leads to unauthorized cancellation
Severity: High
Example test case: User A schedules an email, transfers their connection to User B, then cancels the email. The check passes because the scheduled task uses User B's connection, but the email is cancelled anyway.
Description: A race condition exists in the unsend mutation. The ownership check is performed against the current active connection. If the connection is transferred after scheduling but before the unsend request, the check will pass, but the email associated to the original connection will be cancelled. Mitigation: Store the connectionId inside statusKV and verify against this value as well.

Comments? Email us.

@jazzberry-ai
Copy link

jazzberry-ai bot commented Aug 5, 2025

Bug Report

Name Severity Example test case Description
Improper authorization check in unsend Medium User A schedules email, User B gets messageId, User A's scheduled time passes. User B calls unsend with stolen messageId and cancels User A's email. Missing authorization check in unsend allows any user to cancel another user's email if the scheduler has already sent the scheduled email and deleted from statusKV and payloadKV. But not from scheduledKV.

Comments? Email us.

@MrgSub MrgSub merged commit fec8e1b into staging Aug 5, 2025
6 checks passed
@MrgSub MrgSub deleted the scheduling branch August 5, 2025 05:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

content design Improvements & changes to design & UX High Priority High Priority Work

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants