Conversation
…lidation in the schedule send picker. Ensure consistent duration settings across components and improve user feedback for past date selections.
… dates and improve user feedback. The component now checks for valid date input and displays 'Send later' when no valid date is provided.
…SendPicker to ensure valid date selection. Added user feedback for invalid scheduling attempts and improved date handling logic.
Bug ReportName: Unhandled JSON parsing error in processScheduledEmails Comments? Email us. |
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
|
Note Other AI code review bot(s) detectedCodeRabbit 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 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. 📒 Files selected for processing (6)
WalkthroughThis 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
Sequence Diagram(s)Scheduled Email Send & Undo FlowsequenceDiagram
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 14
🔭 Outside diff range comments (4)
apps/mail/app/(routes)/settings/general/page.tsx (1)
129-139: Add default forundoSendEnabledto keep React quietThe form’s
defaultValuesomits the new flag, so first render passesundefinedinto<Switch checked>, which React doesn’t love. Drop inundoSendEnabled: falseand 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
base64string andarrayBufferfunction 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 nitLove 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-resettingisScheduleValidtotruewhenscheduleAtbecomesundefined.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 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.tsxapps/mail/hooks/use-undo-send.tsapps/server/src/lib/schemas.tsapps/server/src/main.tsapps/mail/components/create/schedule-send-picker.tsxapps/server/src/lib/driver/google.tsapps/server/src/trpc/routes/mail.tsapps/mail/app/(routes)/settings/general/page.tsxapps/mail/components/create/create-email.tsxapps/mail/components/create/email-composer.tsxapps/mail/lib/email-utils.tsapps/server/src/env.tsapps/mail/components/mail/reply-composer.tsxapps/server/src/lib/attachments.tsapps/server/src/types.tsapps/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.tsxapps/mail/hooks/use-undo-send.tsapps/server/src/lib/schemas.tsapps/server/src/main.tsapps/mail/components/create/schedule-send-picker.tsxapps/server/src/lib/driver/google.tsapps/server/src/trpc/routes/mail.tsapps/mail/app/(routes)/settings/general/page.tsxapps/mail/components/create/create-email.tsxapps/mail/components/create/email-composer.tsxapps/mail/lib/email-utils.tsapps/server/src/env.tsapps/mail/components/mail/reply-composer.tsxapps/server/src/lib/attachments.tsapps/server/src/types.tsapps/server/src/db/schema.ts
**/*.{ts,tsx}
📄 CodeRabbit Inference Engine (AGENT.md)
Enable TypeScript strict mode
Files:
apps/mail/components/ui/toast.tsxapps/mail/hooks/use-undo-send.tsapps/server/src/lib/schemas.tsapps/server/src/main.tsapps/mail/components/create/schedule-send-picker.tsxapps/server/src/lib/driver/google.tsapps/server/src/trpc/routes/mail.tsapps/mail/app/(routes)/settings/general/page.tsxapps/mail/components/create/create-email.tsxapps/mail/components/create/email-composer.tsxapps/mail/lib/email-utils.tsapps/server/src/env.tsapps/mail/components/mail/reply-composer.tsxapps/server/src/lib/attachments.tsapps/server/src/types.tsapps/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 asstarting,not-*,inert,nth-*,in-*,open(for:popover-open), and**for all descendants.
Do not use deprecated utilities likebg-opacity-*,text-opacity-*,border-opacity-*, anddivide-opacity-*; use the new syntax (e.g.,bg-black/50).
Use renamed utilities:shadow-smis nowshadow-xs,shadowis nowshadow-sm,drop-shadow-smis nowdrop-shadow-xs,drop-shadowis nowdrop-shadow-sm,blur-smis nowblur-xs,bluris nowblur-sm,rounded-smis nowrounded-xs,roundedis nowrounded-sm,outline-noneis nowoutline-hidden.
Usebg-(--brand-color)syntax for CSS variables in arbitrary values instead ofbg-[--brand-color].
Stacked variants now apply left-to-right instead of right-to-left.
Files:
apps/mail/components/ui/toast.tsxapps/mail/hooks/use-undo-send.tsapps/server/src/lib/schemas.tsapps/server/src/main.tsapps/mail/components/create/schedule-send-picker.tsxapps/server/src/lib/driver/google.tsapps/server/src/trpc/routes/mail.tsapps/mail/app/(routes)/settings/general/page.tsxapps/mail/components/create/create-email.tsxapps/mail/components/create/email-composer.tsxapps/mail/lib/email-utils.tsapps/server/src/env.tsapps/mail/components/mail/reply-composer.tsxapps/server/src/lib/attachments.tsapps/server/src/types.tsapps/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.tsxapps/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.tsxapps/mail/components/create/create-email.tsxapps/mail/components/create/email-composer.tsxapps/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.tsxapps/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.tsxapps/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.tsapps/server/src/main.tsapps/mail/components/create/schedule-send-picker.tsxapps/server/src/trpc/routes/mail.tsapps/mail/app/(routes)/settings/general/page.tsxapps/mail/components/create/create-email.tsxapps/mail/components/create/email-composer.tsxapps/mail/lib/email-utils.tsapps/mail/components/mail/reply-composer.tsxapps/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.tsapps/server/src/lib/driver/google.tsapps/server/src/trpc/routes/mail.tsapps/mail/lib/email-utils.tsapps/server/src/lib/attachments.tsapps/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.tsapps/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 ofundoSendEnabledneeds data back-fillGreat 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
undefinedand 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 launchKV namespaces
pending_emails_*,scheduled_emailsand queuesend_email_queuemust 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 solidThe 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 sincedata.toarray. The code is cleaner and more efficient. ThescheduleAtparameter is properly passed through to the mutation.
124-124: Perfect integration of the undo-send functionality!Capturing the result and passing it to
handleUndoSendwith 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
commonButtonClassesconstant 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-1to 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-flextow-full flexmakes 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
IEmailSendBatchinterface 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
scheduleAtparameter through the function signature and thesendEmailcall 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
SerializedAttachmentinterface 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 stingyAfter 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
toAttachmentFilesis 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 definitionThe imports are well-organized and the interface is crystal clear. Props are properly typed with optional fields marked appropriately.
15-19: Clean timezone handlingNice implementation for converting to local input value. The timezone offset calculation is spot on.
63-63: ScheduleAt ISO format compatibility confirmedThe backend’s
new Date(scheduleAt).getTime()call fully supports ISO-formatted strings, so there’s no mismatch between the frontend’stoISOString()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 tasksProcessing scheduled emails before expired subscriptions makes sense. The order is logical.
There was a problem hiding this comment.
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.
Bug Report
Comments? Email us. |
Bug Report
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
Bug Report
Comments? Email us. |
There was a problem hiding this comment.
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 properAttachmentFile[]typing - this is exactly the kind of precision engineering we need when we're launching emails into the digital stratosphere. The buffer slicing technique withbuffer.byteOffsetis 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
safeParseapproach in your type guards automatically handles all the edge cases that were previously flagged in reviews - no more worrying aboutsendAtbeing 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
UserSettingstyping instead ofany- that's the kind of type safety that prevents mission failures! The time calculation logic is rock solid, and usingtimeRemainingfor 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
continueat 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 validationFrontend 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 101Your 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 pathEvery 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 formattingWhy 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 rocketGoing from
MessageBatch<any>toanyis 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 MarsIterating 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:
- Sharding by time buckets
- Using a proper job scheduling system
- 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
📒 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.tsapps/mail/components/mail/reply-composer.tsxapps/mail/app/(routes)/settings/general/page.tsxapps/mail/hooks/use-undo-send.tsapps/server/src/types.tsapps/server/src/lib/attachments.tsapps/mail/lib/email-utils.tsapps/mail/components/create/schedule-send-picker.tsxapps/server/src/trpc/routes/mail.tsapps/mail/components/create/email-composer.tsxapps/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.tsapps/mail/components/mail/reply-composer.tsxapps/mail/app/(routes)/settings/general/page.tsxapps/mail/hooks/use-undo-send.tsapps/server/src/types.tsapps/server/src/lib/attachments.tsapps/mail/lib/email-utils.tsapps/mail/components/create/schedule-send-picker.tsxapps/server/src/trpc/routes/mail.tsapps/mail/components/create/email-composer.tsxapps/server/src/main.ts
**/*.{ts,tsx}
📄 CodeRabbit Inference Engine (AGENT.md)
Enable TypeScript strict mode
Files:
apps/server/src/lib/driver/google.tsapps/mail/components/mail/reply-composer.tsxapps/mail/app/(routes)/settings/general/page.tsxapps/mail/hooks/use-undo-send.tsapps/server/src/types.tsapps/server/src/lib/attachments.tsapps/mail/lib/email-utils.tsapps/mail/components/create/schedule-send-picker.tsxapps/server/src/trpc/routes/mail.tsapps/mail/components/create/email-composer.tsxapps/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 asstarting,not-*,inert,nth-*,in-*,open(for:popover-open), and**for all descendants.
Do not use deprecated utilities likebg-opacity-*,text-opacity-*,border-opacity-*, anddivide-opacity-*; use the new syntax (e.g.,bg-black/50).
Use renamed utilities:shadow-smis nowshadow-xs,shadowis nowshadow-sm,drop-shadow-smis nowdrop-shadow-xs,drop-shadowis nowdrop-shadow-sm,blur-smis nowblur-xs,bluris nowblur-sm,rounded-smis nowrounded-xs,roundedis nowrounded-sm,outline-noneis nowoutline-hidden.
Usebg-(--brand-color)syntax for CSS variables in arbitrary values instead ofbg-[--brand-color].
Stacked variants now apply left-to-right instead of right-to-left.
Files:
apps/server/src/lib/driver/google.tsapps/mail/components/mail/reply-composer.tsxapps/mail/app/(routes)/settings/general/page.tsxapps/mail/hooks/use-undo-send.tsapps/server/src/types.tsapps/server/src/lib/attachments.tsapps/mail/lib/email-utils.tsapps/mail/components/create/schedule-send-picker.tsxapps/server/src/trpc/routes/mail.tsapps/mail/components/create/email-composer.tsxapps/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.tsapps/server/src/types.tsapps/server/src/lib/attachments.tsapps/mail/lib/email-utils.tsapps/server/src/trpc/routes/mail.tsapps/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.tsapps/server/src/types.tsapps/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.tsxapps/mail/app/(routes)/settings/general/page.tsxapps/mail/hooks/use-undo-send.tsapps/server/src/types.tsapps/server/wrangler.jsoncapps/mail/lib/email-utils.tsapps/mail/components/create/schedule-send-picker.tsxapps/server/src/trpc/routes/mail.tsapps/mail/components/create/email-composer.tsxapps/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.tsxapps/mail/app/(routes)/settings/general/page.tsxapps/mail/hooks/use-undo-send.tsapps/mail/lib/email-utils.tsapps/mail/components/create/email-composer.tsxapps/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.tsxapps/mail/hooks/use-undo-send.tsapps/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.tsxapps/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.tsapps/server/src/trpc/routes/mail.tsapps/mail/components/create/email-composer.tsxapps/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.tsapps/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.tsapps/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.tsxapps/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
IEmailSendBatchinterface is beautifully architected. OptionalsendAtfor scheduling, clean extension ofIOutgoingMessage, and the optionaldraftId- 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, andscheduled_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 yetGood 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 solidOptional parameter for scheduling - clean and backwards compatible. Ship it.
151-152: State management on pointClean 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 disablingGood 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 integrationUsing 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 rightGood 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 sequenceThe 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 tasksGood 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
Bug ReportName: Insecure Date Parsing in Email Scheduling Example test case:
Description: Name: Email Unsend Authorization Bypass via Payload Manipulation Example test case:
Description: Comments? Email us. |
Bug Report
Comments? Email us. |
Bug Report
Comments? Email us. |
Bug ReportName: Race condition in unsend mutation leads to unauthorized cancellation Comments? Email us. |
Bug Report
Comments? Email us. |
Summary by CodeRabbit
New Features
Enhancements
Bug Fixes
Localization
Chores