Skip to content

Comments

Staging#749

Merged
MrgSub merged 27 commits intomainfrom
staging
Apr 22, 2025
Merged

Staging#749
MrgSub merged 27 commits intomainfrom
staging

Conversation

@MrgSub
Copy link
Collaborator

@MrgSub MrgSub commented Apr 22, 2025

Summary by CodeRabbit

  • New Features

    • Added support for selecting the "From" email address when composing, replying, or forwarding emails, including dropdown menus for alias selection.
    • Introduced AI-powered email body and subject generation, with improved error handling and user feedback.
    • Added Vietnamese language support and corresponding localization files.
    • Added a new hook to fetch email aliases for the current user.
  • Improvements

    • Streamlined environment setup by consolidating all environment variables into a single root .env file.
    • Enhanced draft loading, saving, and error handling in the email composer.
    • Improved localization and added new translation keys and corrections for English and Spanish.
  • Bug Fixes

    • Fixed recipient display and draft content preview in the drafts list.
    • Improved error handling for unauthorized access and AI failures.
  • Chores

    • Removed deprecated OAuth and database configuration files and scripts.
    • Updated documentation to reflect simplified setup and removed obsolete instructions.
    • Updated dependencies and development scripts for improved workflow consistency.
  • Style

    • Minor formatting and layout improvements across components and documentation.

ahmetskilinc and others added 27 commits April 19, 2025 01:28
- added cc and bcc when saving drafts
- save drafts less aggresively
chore: simplify and fix the dev env
* Create prompts with XML formatting

* Include XML formatted prompts in generate func

* remove unused regex and add helper functions/warnings

* error handling

* Update apps/mail/lib/prompts.ts

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* lint issues

* Update prompts.ts

* #706 (comment)

Coderabbit fix 1

* erabbitai bot 3 days ago ⚠️ Potential issue  errorOccurred state is stale inside finally  React state setters (setErrorOccurred) are asynchronous; the errorOccurred value captured at render time will not yet reflect changes made earlier in the same event loop. Consequently, the logic deciding whether to collapse/expand may run with an outdated flag.  -  } finally { -      setIsLoading(false); -      if (!errorOccurred || isAskingQuestion) { -        setIsExpanded(true); -      } else { -        setIsExpanded(false); // Collapse on errors -      } -  } +  } finally { +      setIsLoading(false); +      // Use a local flag to track errors deterministically +      const hadError = isAskingQuestion ? false : !!errorFlagRef.current; +      setIsExpanded(!hadError); +  } You can create const errorFlagRef = useRef(false); and update errorFlagRef.current = true every time an error is detected, ensuring reliable behaviour irrespective of React batching.  Committable suggestion skipped: line range outside the PR's diff.

* #706 (comment)

* #706 (comment)

* #706 (comment)

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
…users (#726)

* feat(i18n): add Vietnamese language support

Add Vietnamese ('vi') to the list of supported languages in the
i18n configuration and JSON file to expand language options.

* Add a new Vietnamese translation file to support Vietnamese language users.

* Clear Vietnamese translation strings
Co-authored-by: needle <122770437+needleXO@users.noreply.github.com>
@vercel
Copy link

vercel bot commented Apr 22, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
0 ✅ Ready (Inspect) Visit Preview 💬 Add feedback Apr 22, 2025 5:58pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Apr 22, 2025

Walkthrough

This update implements a major refactor and feature expansion across the mail application. Environment variable management is consolidated into a single root .env file, removing redundant .env files and related documentation. GitHub OAuth and related variables are removed, and Google OAuth setup is simplified. Database scripts and configuration are streamlined, with database-related scripts consolidated and the Drizzle config removed. The AI assistant logic is refactored to separate email body and subject generation, with improved prompt handling and error management. Email alias support is introduced, allowing users to select sender addresses when composing or replying to emails. Vietnamese language support is added, and various localization files are updated. Several legacy middleware and OAuth route files are deleted, and the manifest and package scripts are updated accordingly.

Changes

Files / Grouped Paths Change Summary
.env.example, packages/db/.env.example, apps/mail/lib/auth-providers.ts, README.md, .github/CONTRIBUTING.md Removed GitHub OAuth and Google redirect URI env vars; consolidated and simplified environment variable setup and documentation.
apps/mail/package.json, package.json, turbo.json, docker-compose.yaml Removed database scripts/configs, updated scripts to use dotenv, removed dependencies, updated Turbo tasks, removed app service from Docker compose.
apps/mail/drizzle.config.ts Deleted Drizzle Kit database config file.
apps/mail/app/api/v1/mail/auth/[providerId]/callback/route.ts, apps/mail/app/api/v1/mail/auth/[providerId]/init/route.ts Deleted OAuth callback/init API routes.
apps/mail/middleware-eg.ts, middleware-draft.ts Deleted legacy middleware files.
apps/mail/app/api/auth/early-access/count/route.ts, apps/mail/app/api/auth/early-access/route.ts Refactored IP extraction to use centralized utility.
apps/mail/app/api/driver/google.ts, apps/mail/app/api/driver/types.ts, apps/mail/types/index.ts Added email alias support to Google driver and MailManager interface; updated outgoing message handling to support custom sender.
apps/mail/actions/email-aliases.ts, apps/mail/hooks/use-email-aliases.ts Added new module and hook for fetching and using email aliases.
apps/mail/actions/ai.ts, apps/mail/lib/ai.ts, apps/mail/lib/prompts.ts Refactored AI assistant: separated body/subject generation, improved prompt structure, error handling, and logging; added new prompts module.
apps/mail/components/create/create-email.tsx, apps/mail/components/mail/reply-composer.tsx Added "From" email dropdown for sender selection; integrated email alias fetching and selection into compose and reply flows.
apps/mail/components/create/ai-assistant.tsx Refactored to use separate AI body and subject generation, improved error and state handling.
apps/mail/components/draft/drafts-list.tsx Improved draft recipient and content preview display.
apps/mail/app/manifest.ts Updated manifest: changed start URL, scope, and icons.
apps/mail/lib/auth.ts Added connection handler hook for account linking and synchronization; extended auth options.
apps/mail/lib/constants.ts Made emailProviders a readonly tuple with literal types.
apps/mail/app/api/utils.ts Modified unauthorized handler to throw error after sign-out.
apps/mail/i18n/config.ts, i18n.json, apps/mail/locales/vi.json, apps/mail/locales/en.json, apps/mail/locales/es.json Added Vietnamese language support; updated localization files and keys.
apps/mail/.nvmrc Removed Node version file from mail app.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI
    participant useEmailAliases Hook
    participant EmailAlias API
    participant Compose/Reply Handler
    participant SendEmail Action
    participant Mail Driver

    User->>UI: Open compose/reply email
    UI->>useEmailAliases Hook: Fetch aliases
    useEmailAliases Hook->>EmailAlias API: GET /api/driver/email-aliases
    EmailAlias API->>useEmailAliases Hook: Return aliases
    useEmailAliases Hook->>UI: Provide aliases
    User->>UI: Select "From" email
    User->>Compose/Reply Handler: Compose and send email
    Compose/Reply Handler->>SendEmail Action: sendEmail({ ..., fromEmail })
    SendEmail Action->>Mail Driver: create({ ..., fromEmail })
    Mail Driver->>SendEmail Action: Send email with selected alias
    SendEmail Action->>Compose/Reply Handler: Return result
    Compose/Reply Handler->>UI: Show status
Loading
sequenceDiagram
    participant User
    participant UI
    participant AI Assistant
    participant AI Backend

    User->>UI: Request AI email draft
    UI->>AI Assistant: Submit prompt
    AI Assistant->>AI Backend: generateAIEmailBody(prompt, ...)
    AI Backend-->>AI Assistant: Return body or question
    AI Assistant->>AI Backend: (If body) generateAISubject(body)
    AI Backend-->>AI Assistant: Return subject
    AI Assistant->>UI: Show generated subject and body
    User->>UI: Accept or edit draft
Loading

Possibly related PRs

  • Mail-0/Zero#739: Updates the app manifest configuration, including start URL, scope, and icon entries, directly overlapping with this PR's manifest changes.
  • Mail-0/Zero#706: Refactors AI email generation logic, splitting body and subject functions and updating prompt handling, matching the AI assistant changes in this PR.
  • Mail-0/Zero#726: Adds Vietnamese language support and locale files, mirroring the i18n changes in this PR.

Suggested reviewers

  • ahmetskilinc

Poem

In fields of code where emails bloom,
A rabbit hopped with brush and broom—
Tidied envs, let aliases spring,
Gave AI a smarter zing!
From fields to folders, languages grew,
Compose and reply, now fresh and new.
🐇✨ Hooray for a patch so bright—
The mail app’s future’s looking right!

✨ Finishing Touches
  • 📝 Generate Docstrings

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.
    • Generate unit testing code for this file.
    • 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 generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • 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 generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

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 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.

@MrgSub MrgSub merged commit cc0ffd5 into main Apr 22, 2025
3 of 4 checks passed
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: 13

🔭 Outside diff range comments (2)
apps/mail/app/api/driver/google.ts (1)

396-401: ⚠️ Potential issue

Use URL‑safe base‑64 when calling Gmail APIs

gmail.users.messages.send (and drafts.create/update) expects the URL‑safe variant of base‑64 (see Google API docs).
Buffer.from(...).toString('base64') produces the standard form containing + / =, which frequently yields 400 invalidArgument or silently drops attachments.

-const encodedMessage = Buffer.from(emailContent).toString('base64');
+const encodedMessage = Buffer.from(emailContent)
+  .toString('base64')
+  .replace(/\+/g, '-')
+  .replace(/\//g, '_')
+  .replace(/=+$/, '');

(You already do this in createDraft; bringing the two paths in sync also avoids future regressions.)

apps/mail/components/create/ai-assistant.tsx (1)

194-210: ⚠️ Potential issue

conversationId should be stable across renders

generateConversationId() is executed on every render, so follow‑up messages will not share the same context on the server side.

-const conversationId = generateConversationId();
+const conversationIdRef = useRef(generateConversationId());
+const conversationId = conversationIdRef.current;
🧹 Nitpick comments (10)
apps/mail/lib/ai.ts (2)

134-142: Over‑aggressive HTML / code‑block filter may block legitimate email bodies

The current regex rejects any <tag> or fenced code, yet many real emails legitimately contain
HTML (ordered lists, links, <strong>, etc.).

Recommend:

  • Allow a whitelist of harmless tags (<br>, <a>, <strong> …)
  • Only strip or escape instead of rejecting outright
  • Keep code‑block detection (```/~~~) if you need plain‑text only

This prevents false positives that surface as the generic “Unable to fulfil your request.” message.


160-163: Cap the stored history length to avoid runaway memory

You already slice the last four messages for embeddings, but the full history keeps growing.

conversationHistories[userId][convId].push({ role: 'assistant', content: generatedBody });
+// Keep only the most recent N entries (e.g. 50)
+const MAX_HISTORY = 50;
+const history = conversationHistories[userId][convId];
+if (history.length > MAX_HISTORY) history.splice(0, history.length - MAX_HISTORY);
apps/mail/hooks/use-email-aliases.ts (1)

9-12: Consider adding retry options for better resilience

While the implementation is solid, fetching email aliases is likely critical for the user experience. Consider adding retry options to handle temporary network issues.

  const { data, error, isLoading, mutate } = useSWRImmutable<EmailAlias[]>(
    '/api/driver/email-aliases',
    fetcher,
+   { 
+     errorRetryCount: 3,
+     errorRetryInterval: 2000 
+   }
  );
apps/mail/actions/email-aliases.ts (1)

13-36: Robust implementation with proper error handling

The getEmailAliases function is well-implemented with proper error handling and fallbacks. It correctly:

  1. Checks for valid authentication tokens
  2. Creates a driver for the specific provider
  3. Attempts to fetch email aliases
  4. Falls back to using just the primary email if fetching fails

A small improvement could be to add more specific error typing for better error handling.

Consider enhancing error handling with more specific error types:

-  } catch (error) {
+  } catch (error: unknown) {
     console.error('Error fetching email aliases:', error);
+    // Consider logging different error types differently
+    if (error instanceof Error) {
+      console.error(`Error name: ${error.name}, message: ${error.message}`);
+    }
     return [{ email: connection.email, primary: true }];
   }
apps/mail/components/draft/drafts-list.tsx (2)

32-33: Delete or wire‑up the bodyText state

The state pair is declared but never written to or read from. Keeping dead state:

  1. Increases bundle size ever so slightly.
  2. Confuses future readers who will look for its purpose.

Either remove it or actually set it (e.g. in an useEffect that calls extractTextFromHTML(message.processedHtml)).

-  const [bodyText, setBodyText] = React.useState('');

99-107: Show a real body preview instead of the (often empty) title

You already imported extractTextFromHTML; leverage it so that users see an actual snippet of the draft’s body.

-          {highlightText(message.title || 'No content', searchValue.highlight)}
+          {highlightText(
+            extractTextFromHTML(message.processedHtml ?? message.body ?? '')
+              .slice(0, 140) || // limit to first ~140 chars
+              'No content',
+            searchValue.highlight,
+          )}

This re‑uses the removed import and gives meaning to the list’s second line.

package.json (1)

19-23: Duplicated ‘dotenv’ prefix in nested Turbo pipelines

turbo run db:generate will itself execute the db:generate script inside packages; those inner scripts often also expect envs.
Running dotenv at the root and inside each package doubles the work and may load different .env files.

Consider limiting dotenv to either the root or the leaf scripts to avoid confusion.

apps/mail/app/api/driver/google.ts (1)

488-520: Filter out unverified or disabled aliases

sendAs.list can return items with verificationStatus!=="accepted" or isPrimary === false && isSuspended === true.
Attempting to send from such aliases raises a 400/403. Recommend filtering:

if (alias.verificationStatus === 'accepted' && !alias.isSuspended) {  }

This prevents the UI from showing unusable addresses.

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

610-647: Dropdown loses selection when aliases reload

When aliases refetch (e.g., after token refresh) selectedFromEmail stays null, so the UI flips back to the first alias instead of the user choice.

A quick fix:

useEffect(() => {
  if (!selectedFromEmail && aliases?.length) {
    setSelectedFromEmail(aliases[0].email);
  }
}, [aliases]);

This preserves UX consistency across re‑renders.

apps/mail/lib/prompts.ts (1)

8-15: Export the XML‑escaping helper for reuse & testing

escapeXml is currently file‑private. Exporting it (or moving to a shared util) lets other prompt files sanitise user names consistently and makes unit testing trivial.

-const escapeXml = (s: string) =>
+export const escapeXml = (s: string) =>
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 2c295d6 and f59fb33.

⛔ Files ignored due to path filters (4)
  • apps/mail/public/icons-pwa/icon-180.png is excluded by !**/*.png
  • apps/mail/public/icons-pwa/icon-192.png is excluded by !**/*.png
  • apps/mail/public/icons-pwa/icon-512.png is excluded by !**/*.png
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (40)
  • .env.example (0 hunks)
  • .github/CONTRIBUTING.md (4 hunks)
  • README.md (4 hunks)
  • apps/mail/.nvmrc (0 hunks)
  • apps/mail/actions/ai.ts (2 hunks)
  • apps/mail/actions/email-aliases.ts (1 hunks)
  • apps/mail/actions/send.ts (3 hunks)
  • apps/mail/app/api/auth/early-access/count/route.ts (2 hunks)
  • apps/mail/app/api/auth/early-access/route.ts (3 hunks)
  • apps/mail/app/api/driver/google.ts (6 hunks)
  • apps/mail/app/api/driver/types.ts (1 hunks)
  • apps/mail/app/api/utils.ts (1 hunks)
  • apps/mail/app/api/v1/mail/auth/[providerId]/callback/route.ts (0 hunks)
  • apps/mail/app/api/v1/mail/auth/[providerId]/init/route.ts (0 hunks)
  • apps/mail/app/manifest.ts (1 hunks)
  • apps/mail/components/connection/add.tsx (3 hunks)
  • apps/mail/components/create/ai-assistant.tsx (7 hunks)
  • apps/mail/components/create/create-email.tsx (10 hunks)
  • apps/mail/components/draft/drafts-list.tsx (4 hunks)
  • apps/mail/components/mail/reply-composer.tsx (11 hunks)
  • apps/mail/drizzle.config.ts (0 hunks)
  • apps/mail/hooks/use-email-aliases.ts (1 hunks)
  • apps/mail/i18n/config.ts (1 hunks)
  • apps/mail/lib/ai.ts (1 hunks)
  • apps/mail/lib/auth-providers.ts (1 hunks)
  • apps/mail/lib/auth.ts (3 hunks)
  • apps/mail/lib/constants.ts (1 hunks)
  • apps/mail/lib/prompts.ts (1 hunks)
  • apps/mail/locales/en.json (2 hunks)
  • apps/mail/locales/es.json (2 hunks)
  • apps/mail/locales/vi.json (1 hunks)
  • apps/mail/middleware-eg.ts (0 hunks)
  • apps/mail/package.json (2 hunks)
  • apps/mail/types/index.ts (1 hunks)
  • docker-compose.yaml (1 hunks)
  • i18n.json (1 hunks)
  • middleware-draft.ts (0 hunks)
  • package.json (2 hunks)
  • packages/db/.env.example (0 hunks)
  • turbo.json (2 hunks)
💤 Files with no reviewable changes (8)
  • .env.example
  • apps/mail/.nvmrc
  • apps/mail/drizzle.config.ts
  • apps/mail/app/api/v1/mail/auth/[providerId]/init/route.ts
  • middleware-draft.ts
  • apps/mail/middleware-eg.ts
  • apps/mail/app/api/v1/mail/auth/[providerId]/callback/route.ts
  • packages/db/.env.example
🧰 Additional context used
🧬 Code Graph Analysis (5)
apps/mail/app/api/auth/early-access/count/route.ts (1)
apps/mail/app/api/utils.ts (1)
  • processIP (64-73)
apps/mail/app/api/utils.ts (1)
apps/mail/app/error.tsx (1)
  • Error (8-39)
apps/mail/app/api/auth/early-access/route.ts (1)
apps/mail/app/api/utils.ts (1)
  • processIP (64-73)
apps/mail/hooks/use-email-aliases.ts (1)
apps/mail/actions/email-aliases.ts (1)
  • EmailAlias (7-11)
apps/mail/components/draft/drafts-list.tsx (5)
apps/mail/types/index.ts (2)
  • ThreadProps (90-102)
  • ParsedMessage (37-62)
apps/mail/components/mail/use-mail.ts (1)
  • useMail (23-25)
apps/mail/hooks/use-search-value.ts (1)
  • useSearchValue (19-21)
apps/mail/lib/email-utils.client.tsx (1)
  • highlightText (59-77)
apps/mail/lib/utils.ts (1)
  • cn (52-52)
🔇 Additional comments (49)
i18n.json (1)

22-23: Language support expansion looks good.

Adding Persian ("fa") and Vietnamese ("vi") to the target locales properly extends the application's internationalization capabilities.

apps/mail/package.json (2)

10-10: Database scripts removal aligns with centralization strategy.

The removal of database-related scripts from the mail app's package.json is consistent with the PR's objective to consolidate database configuration.


59-59: Dependency version update.

Updating better-auth from 1.2.1 to 1.2.7 appears to be a standard maintenance update.

docker-compose.yaml (1)

33-33: Minor quoting style change.

Changing from double quotes to single quotes for the Redis connection string is a stylistic change with no functional impact.

apps/mail/app/manifest.ts (2)

8-9: Improved PWA configuration.

Setting the scope to "/" and updating the start URL to "/mail/inbox" enhances the Progressive Web App experience by directing users directly to the inbox view.


13-31: Enhanced icon support for better device compatibility.

Replacing the single favicon with multiple PNG icons at different resolutions (512x512, 192x192, 180x180) significantly improves the app's appearance across various devices and platforms. The "maskable" purpose attribute ensures proper display on devices that use icon masks.

apps/mail/lib/ai.ts (1)

252-260: checkIfQuestion misses many interrogatives & returns false positives

Edge cases:

  • “Any thoughts on …” (no ?)
  • “Let me know if …?” (ends with ? but is not a clarification request)

Consider NLP‑based detection (e.g. simple classification prompt) or enrich the heuristic (regex for “\bany\b.*\bquestions?\b”, etc.).
Marking a body as a question incorrectly will break the composing UI.

apps/mail/types/index.ts (1)

119-120: Propagate the new fromEmail field through all send / draft flows

fromEmail is optional here, but callers like sendEmail, draft creation, and alias pickers
should treat absence as:

  1. Default to the primary account address, or
  2. Block send with a validation error

Ensure unit tests cover:

  • alias chosen → correct “From” header set
  • no alias → default address
apps/mail/i18n/config.ts (1)

18-19: Nice addition – Vietnamese locale wired correctly

vi is added to both the constant and derived arrays; no further action needed. 👍

apps/mail/app/api/driver/types.ts (1)

39-39: LGTM: Clean implementation of email alias support.

The new getEmailAliases() method is well-defined with appropriate return type structure to support the email alias selection feature. The interface extension follows the existing pattern in the MailManager interface.

apps/mail/actions/send.ts (1)

17-17: LGTM: Consistent implementation of sender alias support.

The addition of the optional fromEmail parameter and its proper propagation to the driver allows users to specify sender addresses when composing emails. This correctly implements the email alias selection feature.

Also applies to: 27-27, 56-56

apps/mail/locales/es.json (1)

281-281: Improved Spanish translations for mail folders.

The changes improve grammatical accuracy in Spanish:

  • "Enviado" → "Enviados" (singular to plural form for "sent")
  • "Archivar" → "Archivados" (verb form to noun form for "archive")

These changes better align with standard email folder naming conventions in Spanish.

Also applies to: 283-283, 443-443

apps/mail/app/api/utils.ts (1)

28-28: Ensure consistent error propagation after sign-out.

This change ensures that the function always throws an error after sign-out, maintaining consistent error flow. This makes the function behavior match its name and ensures callers can reliably catch the unauthorized condition.

apps/mail/lib/constants.ts (1)

100-106: Good type safety enhancement with as const

Adding the as const assertion to the emailProviders array converts it from a regular array to a readonly tuple with literal types. This improves type safety when working with provider IDs throughout the application, making it easier to catch errors at compile time rather than runtime.

apps/mail/app/api/auth/early-access/count/route.ts (1)

4-4: Good refactoring to use centralized IP processing

Replacing manual IP extraction with the centralized processIP utility improves code maintainability and consistency. The utility function provides more robust handling of different IP sources (CF-Connecting-IP, x-forwarded-for) and better fallback mechanisms.

Also applies to: 18-18

apps/mail/locales/en.json (2)

90-90: Added localization support for new sender selection feature

The addition of the "from" key supports the new UI components for selecting sender email addresses, aligning with the email alias feature introduced in this PR.


140-140: Minor formatting improvement for consistency

Fixed spacing in the "bcc" translation value for better consistency across the localization file.

apps/mail/hooks/use-email-aliases.ts (1)

1-20: Well-implemented hook for email alias management

This hook follows React best practices by using SWR for efficient data fetching and providing appropriate loading/error states. Using useSWRImmutable is a good choice since email aliases don't change frequently during a session.

apps/mail/components/connection/add.tsx (3)

10-10: New import for auth client added.

The addition of authClient import corresponds to the shift from link-based navigation to programmatic authentication.


56-56: Element type changed from anchor to div.

Changed from <motion.a> to <motion.div> to match the new authentication flow that no longer relies on direct URL navigation.

Also applies to: 78-78


67-71: Authentication flow changed to use programmatic method.

The implementation now correctly uses the authClient.linkSocial method to handle OAuth authentication, replacing the previous direct URL navigation. This provides better control over the authentication process and aligns with the centralized authentication approach.

apps/mail/lib/auth-providers.ts (2)

35-35: Removal of GOOGLE_REDIRECT_URI from required environment variables.

Simplified the OAuth setup by removing the GOOGLE_REDIRECT_URI requirement. This aligns with the broader efforts to streamline the authentication configuration.


39-39: Removed the corresponding environment variable information.

Properly updated the envVarInfo array to match the changes to requiredEnvVars, maintaining consistency in the configuration.

.github/CONTRIBUTING.md (5)

34-34: Simplified environment setup instructions.

Updated to reference a single .env file in the project root instead of multiple .env files in different locations, simplifying the developer setup process.


45-45: Removed unnecessary instruction.

The step to install database dependencies (bun db:dependencies) was removed as it's no longer required in the updated workflow.


116-117: Simplified database command instructions.

Removed database dependency instructions between commands, cleaning up the documentation while maintaining the essential database operation instructions.

Also applies to: 119-120, 122-123


130-132: Updated database connection instructions.

Correctly updated to reference a single .env file for database configuration, consistent with the project's new environment variable management approach.


179-180: Improved formatting for better readability.

Added blank lines for better visual separation, improving the documentation's readability without changing its content.

Also applies to: 186-187

turbo.json (3)

3-3: Added loose environment mode.

The "envMode": "loose" setting allows tasks to access all environment variables without explicit declaration, supporting the simplified environment configuration approach of the project.


11-11: Made dev task dependent on database studio.

Added dependency on db:studio for the dev task, which means running bun dev will automatically start the database studio. This improves the developer experience by requiring fewer manual steps.


21-32: Added database management tasks.

Centralized database management by adding dedicated tasks for schema generation, migration, pushing changes, and running the database studio. Setting "cache": false ensures these tasks always execute when called, which is appropriate for database operations that need to run reliably.

apps/mail/app/api/auth/early-access/route.ts (4)

4-4: Good addition of centralized IP processing

Importing the processIP utility function enhances code maintainability by centralizing IP extraction logic.


7-7: Import reordering looks good

Reordering imports to maintain a logical grouping structure.


35-35: Refactored IP extraction improves reliability

Replacing direct header access with the processIP utility function improves error handling and cross-platform compatibility. The utility properly handles multiple IP header formats and provides appropriate fallbacks.


90-90: Simplified date calculation

The date calculation for scheduling emails is more concise and readable now.

apps/mail/actions/email-aliases.ts (1)

7-11: Well-defined EmailAlias type

Good use of TypeScript to define a clear interface for email aliases with appropriate optional properties.

README.md (6)

78-81: Simplified environment setup instructions

Good simplification of the environment setup process by consolidating to a single .env file in the project root.


83-83: Clearer database setup instructions

Improved clarity in database setup steps by removing unnecessary dependencies installation.


167-168: Updated OAuth redirect URI guidance

The warning message has been updated to reference the correct environment file location.


172-173: Streamlined environment variables section

Instructions for environment variable configuration have been simplified to reference a single .env file.


213-213: Clarified database connection configuration

Updated instructions to ensure the database connection string is properly configured in the single .env file.


245-245: Helpful note about database studio automation

Good addition explaining that running bun dev automatically runs the database studio, which improves the developer experience.

apps/mail/components/draft/drafts-list.tsx (1)

62-75: Recipient fallback is case‑sensitive and may throw

includes('no-sender') fails for values like "No‑Sender" and will blow up if either name or email is null/undefined (possible when Gmail omits the display name).

-                message.to.some(
-                  (to: { name: string; email: string }) =>
-                    to.name.includes('no-sender') || to.email.includes('no-sender'),
-                )
+                message.to.some(
+                  (to: { name?: string | null; email?: string | null }) => {
+                    const n = (to.name ?? '').toLowerCase();
+                    const e = (to.email ?? '').toLowerCase();
+                    return n.includes('no-sender') || e.includes('no-sender');
+                  },
+                )

Use safe‑access and toLowerCase() for robustness.

package.json (1)

7-12: Scripts implicitly depend on dotenv-cli – ensure it’s available in all environments

dotenv -- turbo … relies on the dotenv bin from dotenv-cli.
Because it’s a devDependency, production environments (e.g., Docker image, CI build step) that run npm ci --only=production will miss it and the scripts will fail.

Options:

  1. Move dotenv-cli to dependencies.
  2. Replace the script with "node -r dotenv/config $(npm bin)/turbo run …" which doesn’t need the CLI.
apps/mail/components/mail/reply-composer.tsx (2)

777-813: DRY: duplicate “From” dropdown appears twice

The markup that renders the alias dropdown is copied almost verbatim for the editing and compact header modes.
Consider extracting a small FromEmailSelector component to keep the JSX readable and to avoid bugs when future changes touch only one copy.

Benefits:
• Easier maintenance
• Consistent behaviour & styling
• Single place for future enhancements (e.g. search/filter)

[ suggest_optional_refactor ]

Also applies to: 871-909


341-349: Graceful fallback when aliases are still loading

fromEmail falls back to aliases?.[0]?.email or userEmail if no alias is selected.
When the user clicks Send before the alias query has finished, aliases is still undefined, so we will send from the primary account. That’s fine, but there is no visual hint that the pending alias list might change the actual “From”.
A tiny UX improvement would be to disable the send button while isLoadingAliases is true or show a spinner inside the dropdown.

[ suggest_nitpick ]

apps/mail/actions/ai.ts (2)

4-8: Unnecessary await around headers()

next/headers exports a synchronous function; awaiting a non‑promise has no effect but does add lint noise.

-const headersList = await headers();
+const headersList = headers();

[ suggest_nitpick ]


138-152: Utility duplication

createJsonContentFromBody is re‑implemented here while a very similar helper already exists in several files. Moving this to a shared utility module prevents divergence (e.g. differing paragraph wrapping).

[ suggest_optional_refactor ]

apps/mail/components/create/ai-assistant.tsx (1)

468-476: Disable Accept button when an error occurred

hasContent={!!generatedBody && !errorOccurred} is correct, but the Accept handler still fires if the user somehow triggers it while an error is flagged. Guard inside the handler:

if (errorOccurred || !generatedBody) return;

[ suggest_nitpick ]

Comment on lines 43 to +45
const headersList = await headers();
const session = await auth.api.getSession({ headers: headersList });
const userName = session?.user.name || 'User';
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove the unnecessary await to avoid a compile‑time error

headers() is synchronous and returns a ReadonlyHeaders.
Using await on a non‑Promise value will cause a TS error (TS2536) and break the build.

-  const headersList = await headers();
+  const headersList = headers();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const headersList = await headers();
const session = await auth.api.getSession({ headers: headersList });
const userName = session?.user.name || 'User';
const headersList = headers();
const session = await auth.api.getSession({ headers: headersList });
const userName = session?.user.name || 'User';

Comment on lines +25 to 33
// Keyed by user to prevent cross‑tenant bleed‑through and allow GC per‑user
const conversationHistories: Record<
string,
{ role: 'user' | 'assistant' | 'system'; content: string }[]
string, // userId
Record<
string, // conversationId
{ role: 'user' | 'assistant' | 'system'; content: string }[]
>
> = {};

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add eviction / per‑request scoping to prevent memory & data‑leak issues

conversationHistories is a long‑lived in‑memory singleton:

  1. It grows unbounded – a busy server process will eventually OOM.
  2. Un‑authenticated users are bucketed under the static key "anonymous", so different guests share the same history and may read each other’s prompts / completions.

Consider:

-const conversationHistories: Record<...> = {};
+const conversationHistories: Record<...> = new Map();
/* 1️⃣  TTL / max‑length eviction
   2️⃣  Use crypto.randomUUID() for guests
   3️⃣  Optionally persist to a per‑user KV store (Redis) instead of process memory */

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +70 to 82
let dynamicContext = '\n\n<dynamic_context>\n';
if (subject) {
dynamicContext += ` <current_subject>${subject}</current_subject>\n`;
}

// Add context about current email if it exists
if (currentContent) {
systemPrompt += `\n\nThe user's current email draft is:\n\n${currentContent}`;
dynamicContext += ` <current_draft>${currentContent}</current_draft>\n`;
}

// Add context about recipients
if (recipients && recipients.length > 0) {
systemPrompt += `\n\nThe email is addressed to: ${recipients.join(', ')}`;
dynamicContext += ` <recipients>${recipients.join(', ')}</recipients>\n`;
}
dynamicContext += '</dynamic_context>\n';
const fullSystemPrompt = baseSystemPrompt + (dynamicContext.length > 30 ? dynamicContext : '');

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Dynamic‑context shouldn’t be gated by an arbitrary length check

dynamicContext.length > 30 will silently drop valid context (e.g. a short subject line only).
Drop the length heuristic or switch to an explicit “has‑any‑field” check:

-const fullSystemPrompt = baseSystemPrompt + (dynamicContext.length > 30 ? dynamicContext : '');
+const hasDynamic = !!(subject || currentContent || (recipients?.length));
+const fullSystemPrompt = baseSystemPrompt + (hasDynamic ? dynamicContext : '');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let dynamicContext = '\n\n<dynamic_context>\n';
if (subject) {
dynamicContext += ` <current_subject>${subject}</current_subject>\n`;
}
// Add context about current email if it exists
if (currentContent) {
systemPrompt += `\n\nThe user's current email draft is:\n\n${currentContent}`;
dynamicContext += ` <current_draft>${currentContent}</current_draft>\n`;
}
// Add context about recipients
if (recipients && recipients.length > 0) {
systemPrompt += `\n\nThe email is addressed to: ${recipients.join(', ')}`;
dynamicContext += ` <recipients>${recipients.join(', ')}</recipients>\n`;
}
dynamicContext += '</dynamic_context>\n';
const fullSystemPrompt = baseSystemPrompt + (dynamicContext.length > 30 ? dynamicContext : '');
let dynamicContext = '\n\n<dynamic_context>\n';
if (subject) {
dynamicContext += ` <current_subject>${subject}</current_subject>\n`;
}
if (currentContent) {
dynamicContext += ` <current_draft>${currentContent}</current_draft>\n`;
}
if (recipients && recipients.length > 0) {
dynamicContext += ` <recipients>${recipients.join(', ')}</recipients>\n`;
}
dynamicContext += '</dynamic_context>\n';
const hasDynamic = !!(subject || currentContent || recipients?.length);
const fullSystemPrompt = baseSystemPrompt + (hasDynamic ? dynamicContext : '');

Comment on lines +1 to +450
{
"common": {
"actions": {
"logout": "",
"back": "",
"create": "",
"saveChanges": "",
"saving": "",
"resetToDefaults": "",
"close": "",
"signingOut": "",
"signedOutSuccess": "",
"signOutError": "",
"refresh": "",
"loading": "",
"featureNotImplemented": "",
"moving": "",
"movedToInbox": "",
"movingToInbox": "",
"movedToSpam": "",
"movingToSpam": "",
"archiving": "",
"archived": "",
"failedToMove": "",
"addingToFavorites": "",
"removingFromFavorites": "",
"addedToFavorites": "",
"removedFromFavorites": "",
"failedToAddToFavorites": "",
"failedToRemoveFromFavorites": "",
"failedToModifyFavorites": "",
"movingToBin": "",
"movedToBin": "",
"failedToMoveToBin": "",
"markingAsRead": "",
"markingAsUnread": "",
"hiddenImagesWarning": "",
"showImages": "",
"disableImages": "",
"trustSender": "",
"cancel": "",
"save": "",
"remove": "",
"settings": ""
},
"themes": {
"dark": "",
"light": "",
"system": ""
},
"commandPalette": {
"title": "",
"description": "",
"placeholder": "",
"noResults": "",
"groups": {
"mail": "",
"settings": "",
"actions": "",
"help": "",
"navigation": ""
},
"commands": {
"goToInbox": "",
"goToDrafts": "",
"goToSent": "",
"goToSpam": "",
"goToArchive": "",
"goToBin": "",
"goToSettings": "",
"newEmail": "",
"composeMessage": "",
"searchEmails": "",
"toggleTheme": "",
"backToMail": "",
"goToDocs": "",
"helpWithShortcuts": ""
}
},
"searchBar": {
"pickDateRange": "",
"search": "",
"clearSearch": "",
"advancedSearch": "",
"quickFilters": "",
"searchIn": "",
"recipient": "",
"sender": "",
"subject": "",
"dateRange": "",
"category": "",
"folder": "",
"allMail": "",
"unread": "",
"hasAttachment": "",
"starred": "",
"applyFilters": "",
"reset": "",
"searching": "",
"aiSuggestions": "",
"aiSearching": "",
"aiSearchError": "",
"aiNoResults": "",
"aiEnhancedQuery": ""
},
"navUser": {
"customerSupport": "",
"documentation": "",
"appTheme": "",
"accounts": "",
"signIn": ""
},
"mailCategories": {
"primary": "",
"allMail": "",
"important": "",
"personal": "",
"updates": "",
"promotions": "",
"social": "",
"unread": ""
},
"replyCompose": {
"replyTo": "",
"thisEmail": "",
"dropFiles": "",
"attachments": "",
"attachmentCount": "",
"fileCount": "",
"saveDraft": "",
"send": "",
"forward": ""
},
"mailDisplay": {
"details": "",
"from": "",
"to": "",
"cc": "",
"bcc": "",
"date": "",
"mailedBy": "",
"signedBy": "",
"security": "",
"standardEncryption": "",
"loadingMailContent": "",
"unsubscribe": "",
"unsubscribed": "",
"unsubscribeDescription": "",
"unsubscribeOpenSiteDescription": "",
"cancel": "",
"goToWebsite": "",
"failedToUnsubscribe": ""
},
"threadDisplay": {
"exitFullscreen": "",
"enterFullscreen": "",
"archive": "",
"reply": "",
"moreOptions": "",
"moveToSpam": "",
"replyAll": "",
"forward": "",
"markAsUnread": "",
"markAsRead": "",
"addLabel": "",
"muteThread": "",
"favourites": "",
"disableImages": "",
"enableImages": ""
},
"notes": {
"title": "",
"empty": "",
"emptyDescription": "",
"addNote": "",
"addYourNote": "",
"editNote": "",
"deleteNote": "",
"deleteConfirm": "",
"deleteConfirmDescription": "",
"cancel": "",
"delete": "",
"save": "",
"toSave": "",
"label": "",
"search": "",
"noteCount": "",
"notePinned": "",
"noteUnpinned": "",
"colorChanged": "",
"noteUpdated": "",
"noteDeleted": "",
"noteCopied": "",
"noteAdded": "",
"notesReordered": "",
"noMatchingNotes": "",
"clearSearch": "",
"pinnedNotes": "",
"otherNotes": "",
"created": "",
"updated": "",
"errors": {
"failedToLoadNotes": "",
"failedToLoadThreadNotes": "",
"failedToAddNote": "",
"failedToUpdateNote": "",
"failedToDeleteNote": "",
"failedToUpdateNoteColor": "",
"noValidNotesToReorder": "",
"failedToReorderNotes": ""
},
"colors": {
"default": "",
"red": "",
"orange": "",
"yellow": "",
"green": "",
"blue": "",
"purple": "",
"pink": ""
},
"actions": {
"pin": "",
"unpin": "",
"edit": "",
"delete": "",
"copy": "",
"changeColor": ""
}
},
"settings": {
"notFound": "",
"saved": "",
"failedToSave": "",
"languageChanged": ""
},
"mail": {
"replies": "",
"deselectAll": "",
"selectedEmails": "",
"noEmailsToSelect": "",
"markedAsRead": "",
"markedAsUnread": "",
"failedToMarkAsRead": "",
"failedToMarkAsUnread": "",
"selected": "",
"clearSelection": "",
"mute": "",
"moveToSpam": "",
"moveToInbox": "",
"unarchive": "",
"archive": "",
"moveToBin": "",
"restoreFromBin": "",
"markAsUnread": "",
"markAsRead": "",
"addFavorite": "",
"removeFavorite": "",
"muteThread": "",
"moving": "",
"moved": "",
"errorMoving": "",
"reply": "",
"replyAll": "",
"forward": "",
"labels": "",
"createNewLabel": "",
"noLabelsAvailable": "",
"loadMore": "",
"imagesHidden": "",
"showImages": "",
"noEmails": "",
"noSearchResults": "",
"clearSearch": ""
},
"units": {
"mb": ""
}
},
"navigation": {
"sidebar": {
"inbox": "",
"drafts": "",
"sent": "",
"spam": "",
"archive": "",
"bin": "",
"feedback": "",
"settings": ""
},
"settings": {
"general": "",
"connections": "",
"security": "",
"appearance": "",
"signatures": "",
"shortcuts": ""
}
},
"pages": {
"error": {
"notFound": {
"title": "",
"description": "",
"goBack": ""
},
"settingsNotFound": ""
},
"settings": {
"general": {
"title": "",
"description": "",
"language": "",
"selectLanguage": "",
"timezone": "",
"selectTimezone": "",
"dynamicContent": "",
"dynamicContentDescription": "",
"externalImages": "",
"externalImagesDescription": "",
"trustedSenders": "",
"trustedSendersDescription": "",
"languageChangedTo": "",
"customPrompt": "",
"customPromptPlaceholder": "",
"customPromptDescription": "",
"noResultsFound": ""
},
"connections": {
"title": "",
"description": "",
"disconnectTitle": "",
"disconnectDescription": "",
"cancel": "",
"remove": "",
"disconnectSuccess": "",
"disconnectError": "",
"addEmail": "",
"connectEmail": "",
"connectEmailDescription": "",
"moreComingSoon": ""
},
"security": {
"title": "",
"description": "",
"twoFactorAuth": "",
"twoFactorAuthDescription": "",
"loginNotifications": "",
"loginNotificationsDescription": "",
"deleteAccount": "",
"loadImagesDefault": "",
"loadImagesDefaultDescription": ""
},
"appearance": {
"title": "",
"description": "",
"theme": "",
"inboxType": ""
},
"signatures": {
"title": "",
"description": "",
"enableSignature": "",
"enableSignatureDescription": "",
"includeByDefault": "",
"includeByDefaultDescription": "",
"signatureContent": "",
"signatureContentPlaceholder": "",
"signaturePreview": "",
"signatureSaved": "",
"signaturePreviewDescription": "",
"editorType": "",
"editorTypeDescription": "",
"plainText": "",
"richText": "",
"richTextDescription": "",
"richTextPlaceholder": "",
"signatureContentHelp": ""
},
"shortcuts": {
"title": "",
"description": "",
"actions": {
"newEmail": "",
"sendEmail": "",
"reply": "",
"replyAll": "",
"forward": "",
"drafts": "",
"inbox": "",
"sentMail": "",
"delete": "",
"search": "",
"markAsUnread": "",
"muteThread": "",
"printEmail": "",
"archiveEmail": "",
"markAsSpam": "",
"moveToFolder": "",
"undoLastAction": "",
"viewEmailDetails": "",
"goToDrafts": "",
"expandEmailView": "",
"helpWithShortcuts": "",
"recordHotkey": "",
"pressKeys": "",
"releaseKeys": "",
"selectAll": ""
}
}
},
"createEmail": {
"invalidEmail": "",
"body": "",
"example": "",
"attachments": "",
"addMore": "",
"dropFilesToAttach": "",
"writeYourMessageHere": "",
"emailSentSuccessfully": "",
"failedToSendEmail": "",
"signature": {
"title": "",
"include": "",
"remove": "",
"enable": "",
"disable": "",
"add": ""
},
"addLink": "",
"addUrlToCreateALink": "",
"editor": {
"menuBar": {
"heading1": "",
"heading2": "",
"heading3": "",
"bold": "",
"italic": "",
"strikethrough": "",
"underline": "",
"link": "",
"bulletList": "",
"orderedList": "",
"viewAttachments": "",
"attachFiles": ""
}
}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Empty translation values need to be populated

The Vietnamese localization file structure looks comprehensive, but all translation values are empty strings. This file appears to be a template ready for translation.

Before deploying this file to production, ensure all translation keys have proper Vietnamese translations. Empty translations would result in blank text throughout the UI for Vietnamese users.

#!/bin/bash
# Check if any translations are missing in the Vietnamese locale file
grep -o '": ""' apps/mail/locales/vi.json | wc -l

Comment on lines +8 to 9
import { extractTextFromHTML } from '@/actions/extractText';
import { useSearchValue } from '@/hooks/use-search-value';
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove unused import to keep the build green

extractTextFromHTML is imported but never referenced.
With noUnusedLocals / noUnusedParameters (or the ESLint rule @typescript-eslint/no-unused-vars) enabled, this will break CI.

-import { extractTextFromHTML } from '@/actions/extractText';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { extractTextFromHTML } from '@/actions/extractText';
import { useSearchValue } from '@/hooks/use-search-value';
import { useSearchValue } from '@/hooks/use-search-value';

Comment on lines +877 to +879
const msg = createMimeMessage();
msg.setSender('me');
msg.setTo(data.to);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Hard‑coded "me" breaks alias support and violates RFC 5322

msg.setSender('me') inserts a literal address me, which is not a valid mailbox and overrides the alias the user just selected in the UI.
Pass the same fromEmail that parseOutgoing supports (falling back to config.auth?.email) so drafts accurately reflect the chosen identity.

-msg.setSender('me');
+msg.setSender({ addr: data.fromEmail ?? config.auth?.email ?? 'noreply@example.com' });

Make sure the calling code (createDraft(...) in the action layer) forwards fromEmail just like the send flow.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const msg = createMimeMessage();
msg.setSender('me');
msg.setTo(data.to);
const msg = createMimeMessage();
msg.setSender({ addr: data.fromEmail ?? config.auth?.email ?? 'noreply@example.com' });
msg.setTo(data.to);

Comment on lines 274 to 283

try {
setIsLoading(true);
const draftData = {
to: toEmails.join(', '),
cc: ccEmails.join(', '),
bcc: bccEmails.join(', '),
subject: subjectInput,
message: messageContent || '',
attachments: attachments,
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Draft save omits the selected “From” address

createDraft presently receives toccbcc, etc., but not the user‑chosen fromEmail.
As a result, reopening a draft defaults back to the primary address, which is confusing and may cause the message to be sent from the wrong identity.

Extend both the payload and the backend handler:

 const draftData = {
   to: toEmails.join(', '),
   cc: ccEmails.join(', '),
   bcc: bccEmails.join(', '),
+  fromEmail: selectedFromEmail ?? userEmail,
   subject: subjectInput,
   message: messageContent || '',
   attachments,
   id: draftId,
 };

(Be sure to propagate the field through the createDraft action and driver.)

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
setIsLoading(true);
const draftData = {
to: toEmails.join(', '),
cc: ccEmails.join(', '),
bcc: bccEmails.join(', '),
subject: subjectInput,
message: messageContent || '',
attachments: attachments,
try {
setIsLoading(true);
const draftData = {
to: toEmails.join(', '),
cc: ccEmails.join(', '),
bcc: bccEmails.join(', '),
fromEmail: selectedFromEmail ?? userEmail,
subject: subjectInput,
message: messageContent || '',
attachments: attachments,
id: draftId,
};
// …rest of createDraft call…

Comment on lines +184 to 186
const [selectedFromEmail, setSelectedFromEmail] = useState<string | null>(null);
const { aliases, isLoading: isLoadingAliases } = useEmailAliases();
const ccInputRef = useRef<HTMLInputElement | null>(null);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Reset selectedFromEmail when composer closes

selectedFromEmail is never reset in useEffect that cleans up the composer (see lines 914–925).
If the user opens a fresh composer after having chosen a different alias, the previous alias silently carries over, which is surprising and can even reference a stale alias list for another account.

useEffect(() => {
  if (!composerIsOpen) {
    ...
+   setSelectedFromEmail(null);   // reset alias
  }
}, [composerIsOpen, reset, mode]);

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +49 to +60
const responses = await generateEmailBody(
prompt,
currentContent,
to,
subject,
conversationId,
userContext,
);

const questionResponse = responses.find((r) => r.type === 'question');
if (questionResponse) {
return {
content: questionResponse.content,
jsonContent: createJsonContent([questionResponse.content]),
type: 'question',
};
const response = responses[0];
if (!response) {
console.error('AI Action Error (Body): Received no response array item from generateEmailBody');
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

generateEmailBody return type is assumed to be an array

const responses = await generateEmailBody(...); const response = responses[0];

Unless the helper is guaranteed to return an array, this will explode at runtime.
Either:

  1. Update the helper’s signature to Promise<AIGeneratedEmail[]> and annotate it properly, or
  2. Remove the array access.
-const responses = await generateEmailBody(...);
-const response = responses[0];
+const response = await generateEmailBody(...); // if single object

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 260 to +278
// Handle submit
const handleSubmit = async (e?: React.MouseEvent) => {
const handleSubmit = async (e?: React.MouseEvent, overridePrompt?: string): Promise<void> => {
e?.stopPropagation();
if (!prompt.trim()) return;
const promptToUse = overridePrompt || prompt;
if (!promptToUse.trim() || isLoading) return;

try {
setIsLoading(true);
setErrorOccurred(false);
errorFlagRef.current = false;

// Track AI assistant usage
posthog.capture('Create Email AI Assistant Submit');
addMessage('user', promptToUse, 'question');

// Add user message
addMessage('user', prompt, 'question');

// Reset states
setIsAskingQuestion(false);
setShowActions(false);

// Call the server action
const result = await generateAIEmailContent({
prompt,
currentContent: generatedContent?.content || currentContent,
setGeneratedBody(null);
setGeneratedSubject(undefined);

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Resetting generatedBody before using its value

Immediately after setGeneratedBody(null) the code tries to read generatedBody?.content, which is now undefined, defeating incremental prompt building.

- setGeneratedBody(null);
...
- currentContent: generatedBody?.content || currentContent,
+ const prevBody = generatedBody?.content;
+ setGeneratedBody(null);
...
+ currentContent: prevBody || currentContent,

Committable suggestion skipped: line range outside the PR's diff.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants