From b9862fbd5834a059edc9cf27ec4a554dbccb0fcc Mon Sep 17 00:00:00 2001 From: BlankParticle Date: Sun, 11 May 2025 10:55:14 +0530 Subject: [PATCH 01/15] fix: local dev for zero backend --- .env.example | 5 ++++- apps/server/package.json | 2 +- apps/server/wrangler.jsonc | 8 -------- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/.env.example b/.env.example index 07d5bc0e8c..d2474a2fb2 100644 --- a/.env.example +++ b/.env.example @@ -7,7 +7,9 @@ DATABASE_URL="postgresql://postgres:postgres@localhost:5432/zerodotemail" BETTER_AUTH_SECRET=my-better-auth-secret BETTER_AUTH_URL=http://localhost:3000 BETTER_AUTH_TRUSTED_ORIGINS=http://localhost:3000 -COOKIE_DOMAIN=localhost + +COOKIE_DOMAIN="localhost" + # Change to your project's client ID and secret, these work with localhost:3000 and localhost:3001 GOOGLE_CLIENT_ID= GOOGLE_CLIENT_SECRET= @@ -27,6 +29,7 @@ AI_SYSTEM_PROMPT="" GROQ_API_KEY="" +DEFAULT_BRAIN_LIMIT="500" BRAIN_URL="" NODE_ENV="development" diff --git a/apps/server/package.json b/apps/server/package.json index 6da99b1fea..b692f16bd5 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -6,7 +6,7 @@ "dev": "wrangler dev --show-interactive-dev-session=false --env staging", "build": "wrangler deploy --minify --dry-run", "deploy": "wrangler deploy --minify", - "types": "wrangler types --e staging" + "types": "wrangler types --env staging" }, "exports": { "./trpc": "./src/trpc/index.ts", diff --git a/apps/server/wrangler.jsonc b/apps/server/wrangler.jsonc index fc7b0e756a..9c1d0b35aa 100644 --- a/apps/server/wrangler.jsonc +++ b/apps/server/wrangler.jsonc @@ -26,12 +26,8 @@ }, ], "vars": { - "DEFAULT_BRAIN_LIMIT": "500", "NODE_ENV": "development", "FORCE_GMAIL_CONSENT": "true", - "NEXT_PUBLIC_BACKEND_URL": "https://sapi.0.email", - "NEXT_PUBLIC_APP_URL": "https://staging.0.email", - "COOKIE_DOMAIN": "0.email", }, "kv_namespaces": [ { @@ -64,12 +60,8 @@ }, ], "vars": { - "DEFAULT_BRAIN_LIMIT": "500", "NODE_ENV": "production", "FORCE_GMAIL_CONSENT": "true", - "NEXT_PUBLIC_BACKEND_URL": "https://api.0.email", - "NEXT_PUBLIC_APP_URL": "https://0.email", - "COOKIE_DOMAIN": "0.email", }, "kv_namespaces": [ { From 0c231500475843531fd1030cc2caefd315716011 Mon Sep 17 00:00:00 2001 From: Aj Wazzan Date: Sun, 11 May 2025 11:32:14 -0700 Subject: [PATCH 02/15] refactor: restore headers logic and NotificationProvider in MailLayout for improved functionality --- apps/mail/app/(routes)/mail/layout.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/mail/app/(routes)/mail/layout.tsx b/apps/mail/app/(routes)/mail/layout.tsx index 32f8527dd8..a945fb37eb 100644 --- a/apps/mail/app/(routes)/mail/layout.tsx +++ b/apps/mail/app/(routes)/mail/layout.tsx @@ -4,13 +4,13 @@ import { NotificationProvider } from '@/components/party'; import { AppSidebar } from '@/components/ui/app-sidebar'; import { headers } from 'next/headers'; export default async function MailLayout({ children }: { children: React.ReactNode }) { - // const headersList = await headers(); + const headersList = await headers(); return (
{children}
- {/* */} +
); } From 1e1a0024a724d0f727c2cee965da90a51ec0e8c8 Mon Sep 17 00:00:00 2001 From: BlankParticle Date: Mon, 12 May 2025 00:08:11 +0530 Subject: [PATCH 03/15] fix: use different cookie based on NODE_ENV --- apps/server/src/lib/auth.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/server/src/lib/auth.ts b/apps/server/src/lib/auth.ts index 96d33f511f..6cb82f8ec7 100644 --- a/apps/server/src/lib/auth.ts +++ b/apps/server/src/lib/auth.ts @@ -6,11 +6,11 @@ import { session, userHotkeys, } from '@zero/db/schema'; +import { type Account, betterAuth, type BetterAuthOptions } from 'better-auth'; import { createAuthMiddleware, customSession } from 'better-auth/plugins'; import { defaultUserSettings } from '@zero/db/user_settings_default'; import { getBrowserTimezone, isValidTimezone } from './timezones'; import { drizzleAdapter } from 'better-auth/adapters/drizzle'; -import { type Account, betterAuth } from 'better-auth'; import { getSocialProviders } from './auth-providers'; import { getActiveDriver } from './driver/utils'; import { APIError } from 'better-auth/api'; @@ -205,6 +205,7 @@ const createAuthConfig = () => { ipAddress: { disableIpTracking: true, }, + cookiePrefix: env.NODE_ENV === 'development' ? 'better-auth-dev' : 'better-auth', crossSubDomainCookies: { enabled: true, domain: env.COOKIE_DOMAIN, @@ -320,7 +321,7 @@ const createAuthConfig = () => { }; }), ], - }; + } satisfies BetterAuthOptions; }; export const createSimpleAuth = () => { From 0abf236d3390a2b04dd586430eea2cc1b29709d4 Mon Sep 17 00:00:00 2001 From: Aj Wazzan Date: Sun, 11 May 2025 12:36:49 -0700 Subject: [PATCH 04/15] refactor: simplify room assignment logic in NotificationProvider for improved clarity --- apps/mail/components/party.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/mail/components/party.tsx b/apps/mail/components/party.tsx index 82809ceebc..07fe8e652b 100644 --- a/apps/mail/components/party.tsx +++ b/apps/mail/components/party.tsx @@ -11,9 +11,7 @@ export const NotificationProvider = ({ headers }: { headers: Record Date: Sun, 11 May 2025 13:01:56 -0700 Subject: [PATCH 05/15] fix: update room assignment logic in NotificationProvider to use connection ID for improved accuracy --- apps/mail/components/party.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/mail/components/party.tsx b/apps/mail/components/party.tsx index 07fe8e652b..59ca62ddf6 100644 --- a/apps/mail/components/party.tsx +++ b/apps/mail/components/party.tsx @@ -11,7 +11,7 @@ export const NotificationProvider = ({ headers }: { headers: Record Date: Sun, 11 May 2025 23:41:04 +0300 Subject: [PATCH 06/15] fix(thread): attachment rendering --- apps/server/src/lib/driver/google.ts | 91 +++++++++++++++++----------- 1 file changed, 56 insertions(+), 35 deletions(-) diff --git a/apps/server/src/lib/driver/google.ts b/apps/server/src/lib/driver/google.ts index 0a2738fea9..6fba6bb4e6 100644 --- a/apps/server/src/lib/driver/google.ts +++ b/apps/server/src/lib/driver/google.ts @@ -293,45 +293,34 @@ export class GoogleMailManager implements MailManager { }); } + const attachmentParts = message.payload?.parts + ? this.findAttachments(message.payload.parts) + : []; + const attachments = await Promise.all( - message.payload?.parts - ?.filter((part) => { - if (!part.filename || part.filename.length === 0) return false; - - const contentDisposition = - part.headers?.find((h) => h.name?.toLowerCase() === 'content-disposition') - ?.value || ''; - const isInline = contentDisposition.toLowerCase().includes('inline'); - - const hasContentId = part.headers?.some( - (h) => h.name?.toLowerCase() === 'content-id', - ); - - return !isInline || (isInline && !hasContentId); - }) - ?.map(async (part) => { - const attachmentId = part.body?.attachmentId; - if (!attachmentId) { - return null; - } + attachmentParts.map(async (part) => { + const attachmentId = part.body?.attachmentId; + if (!attachmentId) { + return null; + } - try { - if (!message.id) { - return null; - } - const attachmentData = await this.getAttachment(message.id, attachmentId); - return { - filename: part.filename || '', - mimeType: part.mimeType || '', - size: Number(part.body?.size || 0), - attachmentId: attachmentId, - headers: part.headers || [], - body: attachmentData ?? '', - }; - } catch { + try { + if (!message.id) { return null; } - }) || [], + const attachmentData = await this.getAttachment(message.id, attachmentId); + return { + filename: part.filename || '', + mimeType: part.mimeType || '', + size: Number(part.body?.size || 0), + attachmentId: attachmentId, + headers: part.headers || [], + body: attachmentData ?? '', + }; + } catch { + return null; + } + }) ).then((attachments) => attachments.filter((a): a is NonNullable => a !== null), ); @@ -1044,4 +1033,36 @@ export class GoogleMailManager implements MailManager { throw new StandardizedError(error, operation, context); } } + + private findAttachments(parts: any[]): any[] { + let results: any[] = []; + + for (const part of parts) { + if (part.filename && part.filename.length > 0) { + const contentDisposition = + part.headers?.find((h: any) => h.name?.toLowerCase() === 'content-disposition') + ?.value || ''; + const isInline = contentDisposition.toLowerCase().includes('inline'); + const hasContentId = part.headers?.some( + (h: any) => h.name?.toLowerCase() === 'content-id', + ); + + if (!isInline || (isInline && !hasContentId)) { + results.push(part); + } + } + + if (part.parts && Array.isArray(part.parts)) { + results = results.concat(this.findAttachments(part.parts)); + } + + if (part.body?.attachmentId && part.mimeType === 'message/rfc822') { + if (part.filename && part.filename.length > 0) { + results.push(part); + } + } + } + + return results; + } } From 7df47bf9cc84e292a097737f6434ce2d301d8758 Mon Sep 17 00:00:00 2001 From: Aj Wazzan Date: Sun, 11 May 2025 13:43:22 -0700 Subject: [PATCH 07/15] fix: update room assignment logic in NotificationProvider to use only connection ID for improved accuracy --- apps/mail/components/party.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/mail/components/party.tsx b/apps/mail/components/party.tsx index 59ca62ddf6..afa3861535 100644 --- a/apps/mail/components/party.tsx +++ b/apps/mail/components/party.tsx @@ -11,7 +11,7 @@ export const NotificationProvider = ({ headers }: { headers: Record Date: Sun, 11 May 2025 14:25:19 -0700 Subject: [PATCH 08/15] refactor: update defaultLabels structure in mail component for improved clarity and usability --- apps/mail/components/mail/mail.tsx | 51 ++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/apps/mail/components/mail/mail.tsx b/apps/mail/components/mail/mail.tsx index 9b50e96679..522fa54264 100644 --- a/apps/mail/components/mail/mail.tsx +++ b/apps/mail/components/mail/mail.tsx @@ -66,22 +66,37 @@ interface Tag { text: string; } -const defaultLabels = [ - 'urgent', - 'review', - 'followup', - 'decision', - 'work', - 'finance', - 'legal', - 'hiring', - 'sales', - 'product', - 'support', - 'vendors', - 'marketing', - 'meeting', - 'investors', +export const defaultLabels = [ + { + name: 'to respond', + usecase: 'emails you need to respond to. NOT sales, marketing, or promotions.', + }, + { + name: 'FYI', + usecase: + 'emails that are not important, but you should know about. NOT sales, marketing, or promotions.', + }, + { + name: 'comment', + usecase: + 'Team chats in tools like Google Docs, Slack, etc. NOT marketing, sales, or promotions.', + }, + { + name: 'notification', + usecase: 'Automated updates from services you use. NOT sales, marketing, or promotions.', + }, + { + name: 'promotion', + usecase: 'Sales, marketing, cold emails, special offers or promotions. NOT to respond to.', + }, + { + name: 'meeting', + usecase: 'Calendar events, invites, etc. NOT sales, marketing, or promotions.', + }, + { + name: 'billing', + usecase: 'Billing notifications. NOT sales, marketing, or promotions.', + }, ]; const AutoLabelingSettings = () => { @@ -101,7 +116,9 @@ const AutoLabelingSettings = () => { }, [storedLabels]); const handleResetToDefault = useCallback(() => { - setLabels(defaultLabels.map((label) => ({ id: label, name: label, text: label }))); + setLabels( + defaultLabels.map((label) => ({ id: label.name, name: label.name, text: label.name })), + ); }, [storedLabels]); return ( From d0e5d4fa1bc2ab1cd2d9756e1c433cbdd6060a06 Mon Sep 17 00:00:00 2001 From: Aj Wazzan Date: Sun, 11 May 2025 14:46:55 -0700 Subject: [PATCH 09/15] feat: enhance NotificationProvider to handle message types and invalidate queries based on thread ID --- apps/mail/components/party.tsx | 16 ++++++++++++---- apps/server/src/main.ts | 23 ++++++++++++++++++++--- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/apps/mail/components/party.tsx b/apps/mail/components/party.tsx index afa3861535..954e7fff1e 100644 --- a/apps/mail/components/party.tsx +++ b/apps/mail/components/party.tsx @@ -1,4 +1,5 @@ 'use client'; +import { useQueryClient } from '@tanstack/react-query'; import { atom, useAtomValue, useSetAtom } from 'jotai'; import { useTRPC } from '@/providers/query-provider'; import { usePartySocket } from 'partysocket/react'; @@ -6,9 +7,9 @@ import { useSession } from '@/lib/auth-client'; import { useEffect } from 'react'; export const NotificationProvider = ({ headers }: { headers: Record }) => { - // const trpc = useTRPC(); + const trpc = useTRPC(); const { data: session } = useSession(); - + const queryClient = useQueryClient(); usePartySocket({ party: 'durable-mailbox', room: session?.activeConnection?.id ? `${session.activeConnection.id}` : 'general', @@ -19,8 +20,15 @@ export const NotificationProvider = ({ headers }: { headers: Record { - console.log(message); + onMessage: (message: MessageEvent) => { + const [threadId, type] = message.data.split(':'); + if (type === 'end') { + console.log('invalidating thread', threadId); + queryClient.invalidateQueries({ + queryKey: trpc.mail.get.queryKey({ id: threadId }), + }); + } + console.log('party message', threadId, type); }, }); diff --git a/apps/server/src/main.ts b/apps/server/src/main.ts index a9c43841dc..21f7e12df2 100644 --- a/apps/server/src/main.ts +++ b/apps/server/src/main.ts @@ -86,11 +86,28 @@ export default class extends WorkerEntrypoint { return app.fetch(request); } - public notifyUser({ email }: { email: string }) { - const durableObject = env.DURABLE_MAILBOX.idFromString(`${email}:general`); + public async notifyUser({ + connectionId, + threadId, + type, + }: { + connectionId: string; + threadId: string; + type: 'start' | 'end'; + }) { + console.log(`Notifying user ${connectionId} for thread ${threadId} with type ${type}`); + const durableObject = env.DURABLE_MAILBOX.idFromName(`${connectionId}`); if (env.DURABLE_MAILBOX.get(durableObject)) { const stub = env.DURABLE_MAILBOX.get(durableObject); - if (stub) stub.broadcast(`HELLO ${email}`); + if (stub) { + console.log(`Broadcasting message for thread ${threadId} with type ${type}`); + await stub.broadcast(threadId + ':' + type); + console.log(`Successfully broadcasted message for thread ${threadId}`); + } else { + console.log(`No stub found for connection ${connectionId}`); + } + } else { + console.log(`No durable object found for connection ${connectionId}`); } } } From fe7e088015dc2962b9d6cf83c9bc56969fffa7c8 Mon Sep 17 00:00:00 2001 From: Aj Wazzan Date: Sun, 11 May 2025 14:50:57 -0700 Subject: [PATCH 10/15] feat: integrate useThreads hook and enhance message handling in NotificationProvider --- apps/mail/components/party.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/mail/components/party.tsx b/apps/mail/components/party.tsx index 954e7fff1e..380daeceb9 100644 --- a/apps/mail/components/party.tsx +++ b/apps/mail/components/party.tsx @@ -3,6 +3,7 @@ import { useQueryClient } from '@tanstack/react-query'; import { atom, useAtomValue, useSetAtom } from 'jotai'; import { useTRPC } from '@/providers/query-provider'; import { usePartySocket } from 'partysocket/react'; +import { useThreads } from '@/hooks/use-threads'; import { useSession } from '@/lib/auth-client'; import { useEffect } from 'react'; @@ -10,6 +11,7 @@ export const NotificationProvider = ({ headers }: { headers: Record) => { + onMessage: async (message: MessageEvent) => { const [threadId, type] = message.data.split(':'); if (type === 'end') { console.log('invalidating thread', threadId); - queryClient.invalidateQueries({ + await queryClient.invalidateQueries({ queryKey: trpc.mail.get.queryKey({ id: threadId }), }); + await refetchThreads(); } console.log('party message', threadId, type); }, From ccb57838d47245fb02a19a5bff4de3e4b1e242b0 Mon Sep 17 00:00:00 2001 From: Aj Wazzan Date: Sun, 11 May 2025 14:59:56 -0700 Subject: [PATCH 11/15] fix: update logging in NotificationProvider to use console.warn for message handling --- apps/mail/components/party.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/mail/components/party.tsx b/apps/mail/components/party.tsx index 380daeceb9..d3cc1d54bf 100644 --- a/apps/mail/components/party.tsx +++ b/apps/mail/components/party.tsx @@ -23,6 +23,7 @@ export const NotificationProvider = ({ headers }: { headers: Record) => { + console.warn('party message', message); const [threadId, type] = message.data.split(':'); if (type === 'end') { console.log('invalidating thread', threadId); @@ -31,7 +32,7 @@ export const NotificationProvider = ({ headers }: { headers: Record Date: Sun, 11 May 2025 15:04:05 -0700 Subject: [PATCH 12/15] refactor: simplify thread summary retrieval in brainRouter by using zero.getSummary --- apps/server/src/trpc/routes/brain.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/server/src/trpc/routes/brain.ts b/apps/server/src/trpc/routes/brain.ts index 592ac67dc1..9d1faf82b0 100644 --- a/apps/server/src/trpc/routes/brain.ts +++ b/apps/server/src/trpc/routes/brain.ts @@ -63,11 +63,10 @@ export const brainRouter = router({ ) .query(async ({ input, ctx }) => { const { threadId } = input; - if (!ctx.brainServerAvailable) return null; - const response = await fetch(env.BRAIN_URL + `/brain/thread/summary/${threadId}`).then( - (res) => res.json(), - ); - return (response as { short: string }) ?? null; + return (await env.zero.getSummary({ type: 'thread', id: threadId })) as { + long: string; + short: string; + }; }), getState: activeConnectionProcedure.use(brainServerAvailableMiddleware).query(async ({ ctx }) => { const connection = ctx.activeConnection; From 6033deee2c67d44ab1f5637ef6f0c3bfdcb83a76 Mon Sep 17 00:00:00 2001 From: Aj Wazzan Date: Sun, 11 May 2025 15:09:22 -0700 Subject: [PATCH 13/15] fix: update AiSummary to correctly access summary data structure --- apps/mail/components/mail/mail-display.tsx | 4 ++-- apps/server/src/trpc/routes/brain.ts | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/mail/components/mail/mail-display.tsx b/apps/mail/components/mail/mail-display.tsx index 589743cb35..82e9d44fa3 100644 --- a/apps/mail/components/mail/mail-display.tsx +++ b/apps/mail/components/mail/mail-display.tsx @@ -250,7 +250,7 @@ const AiSummary = () => { }; if (isLoading) return null; - if (!summary?.short?.length) return null; + if (!summary?.data.short?.length) return null; return (
{ )}
{showSummary && ( - {summary?.short || ''} + {summary?.data.short || ''} )} ); diff --git a/apps/server/src/trpc/routes/brain.ts b/apps/server/src/trpc/routes/brain.ts index 9d1faf82b0..1d4923bfe0 100644 --- a/apps/server/src/trpc/routes/brain.ts +++ b/apps/server/src/trpc/routes/brain.ts @@ -64,8 +64,10 @@ export const brainRouter = router({ .query(async ({ input, ctx }) => { const { threadId } = input; return (await env.zero.getSummary({ type: 'thread', id: threadId })) as { - long: string; - short: string; + data: { + long: string; + short: string; + }; }; }), getState: activeConnectionProcedure.use(brainServerAvailableMiddleware).query(async ({ ctx }) => { From 03b36a9ad087df5e8fc0819c42bf39c83ef4972b Mon Sep 17 00:00:00 2001 From: Aj Wazzan Date: Sun, 11 May 2025 15:34:02 -0700 Subject: [PATCH 14/15] refactor: remove unused imports and add console warning for thread refetching in NotificationProvider --- apps/mail/components/party.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/mail/components/party.tsx b/apps/mail/components/party.tsx index d3cc1d54bf..b57ccd33fb 100644 --- a/apps/mail/components/party.tsx +++ b/apps/mail/components/party.tsx @@ -1,11 +1,9 @@ 'use client'; import { useQueryClient } from '@tanstack/react-query'; -import { atom, useAtomValue, useSetAtom } from 'jotai'; import { useTRPC } from '@/providers/query-provider'; import { usePartySocket } from 'partysocket/react'; import { useThreads } from '@/hooks/use-threads'; import { useSession } from '@/lib/auth-client'; -import { useEffect } from 'react'; export const NotificationProvider = ({ headers }: { headers: Record }) => { const trpc = useTRPC(); @@ -31,6 +29,7 @@ export const NotificationProvider = ({ headers }: { headers: Record Date: Sun, 11 May 2025 15:56:39 -0700 Subject: [PATCH 15/15] refactor: improve layout and styling in PricingPage component for better readability --- apps/mail/app/(full-width)/pricing/page.tsx | 25 +++++++++++---------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/apps/mail/app/(full-width)/pricing/page.tsx b/apps/mail/app/(full-width)/pricing/page.tsx index e6bceaa965..5c993a7cb5 100644 --- a/apps/mail/app/(full-width)/pricing/page.tsx +++ b/apps/mail/app/(full-width)/pricing/page.tsx @@ -221,12 +221,12 @@ export default function PricingPage() {

Pricing

-

Choose the plan that's right for you

+

Choose the plan that's right for you

-
+
{/* Free Plan */} -
+

Free

$0 / mo @@ -237,18 +237,22 @@ export default function PricingPage() {
  • - AI chat (25 per day) + AI chat{' '} + (25 per day)
  • AI writing assistant
  • - Auto labeling(25 per day) + Auto labeling + (25 per day)
  • - AI thread summaries (25 per day) + + AI thread summaries (25 per day) +
  • {/*
  • Instant thread AI-generated @@ -263,14 +267,12 @@ export default function PricingPage() {
  • */} - +
    {/* Pro Plan */} -
    +

    Pro

    @@ -298,7 +300,6 @@ export default function PricingPage() {
  • Verified checkmark
  • -
    {/* Enterprise Plan */} -
    +

    Enterprise

    Contact us