Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
fd25387
Update schema for reply tracking with basic page
elie222 Feb 1, 2025
a86f528
rename page to reply tracker
elie222 Feb 1, 2025
b4e800e
inbound and outbound reply tracking
elie222 Feb 1, 2025
9cd438f
load threads on reply tracker page
elie222 Feb 1, 2025
199fd82
Merge branch 'main' into thread-track
elie222 Feb 1, 2025
25cd3c8
load threads on all tabs
elie222 Feb 1, 2025
8a41b16
Resolve thread
elie222 Feb 1, 2025
5e47d52
unresolve button
elie222 Feb 1, 2025
7359394
fix fetch threads by id
elie222 Feb 1, 2025
e91f86e
fix duplicates
elie222 Feb 1, 2025
8ef423f
Add trackReplies to schema
elie222 Feb 2, 2025
414a1b6
add tooltip for toggle
elie222 Feb 2, 2025
9b08c86
move user login check to page level
elie222 Feb 2, 2025
14313c1
add empty state screen for cold email blocker and reply tracker
elie222 Feb 2, 2025
3ba7886
fix build
elie222 Feb 2, 2025
7adba51
Merge branch 'main' into thread-track
elie222 Feb 2, 2025
cf11a35
enable reply tracker
elie222 Feb 2, 2025
6039a5d
Merge branch 'main' into thread-track
elie222 Feb 2, 2025
7c5ed1b
empty state
elie222 Feb 2, 2025
e517247
Reply tracking for inbound email
elie222 Feb 2, 2025
c28c6b1
Use ai to check if outbound email needs a reply
elie222 Feb 2, 2025
3c4bc5d
label outbound emails
elie222 Feb 2, 2025
191635e
label inbound emails
elie222 Feb 2, 2025
ff0902a
rename label file
elie222 Feb 2, 2025
de78aa8
remove deprecated fields
elie222 Feb 2, 2025
65b33ca
adjust labelling for tracker
elie222 Feb 2, 2025
d3fe293
reply tracker label by id
elie222 Feb 2, 2025
b75ce49
Add notes to architecture file
elie222 Feb 2, 2025
b477d92
adjust ui
elie222 Feb 2, 2025
96f5f7f
only show fully resolved threads
elie222 Feb 2, 2025
1fbe907
open reply for nudge
elie222 Feb 2, 2025
91a5071
Remove Novel and use Tiptap
elie222 Feb 3, 2025
a593ebd
code cleanup
elie222 Feb 3, 2025
e452eb9
Fix follow up on email
elie222 Feb 3, 2025
34eda18
fix tiptap types
elie222 Feb 3, 2025
0f78a8a
fix up email panel height
elie222 Feb 3, 2025
57e5d89
early access for reply tracker
elie222 Feb 3, 2025
5f25c04
Fix reply tracking bug
elie222 Feb 3, 2025
afe9230
loading state reply tracker
elie222 Feb 3, 2025
67aaf17
pagination for tracker page
elie222 Feb 3, 2025
a027efe
fix resolved page
elie222 Feb 3, 2025
1747021
Add sentat field for tracker
elie222 Feb 3, 2025
67bfdff
Add time range filter for reply tracker
elie222 Feb 3, 2025
c7dc110
update summary email to include reply tracker
elie222 Feb 3, 2025
651af18
load subjects. send plain text email
elie222 Feb 3, 2025
9c0b1ec
fix styling and transactional domain
elie222 Feb 3, 2025
1fad5ab
fix resolve tab and summary email
elie222 Feb 3, 2025
3bbd659
fix ts
elie222 Feb 3, 2025
203751a
unresolve button
elie222 Feb 3, 2025
eca2b6f
auto focus reply
elie222 Feb 3, 2025
bb6abd6
Merge branch 'main' into thread-track
elie222 Feb 4, 2025
e613899
use prisma tx
elie222 Feb 4, 2025
f08f641
Fix up error capture for allsettled
elie222 Feb 4, 2025
8c2a90f
is button loading
elie222 Feb 4, 2025
1b47b7b
Fix log
elie222 Feb 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .cursorrules
Original file line number Diff line number Diff line change
Expand Up @@ -208,4 +208,13 @@ export const POST = withError(async (request: Request) => {

return NextResponse.json(result);
});
```
```

## Utility Functions

- Use lodash utilities for common operations (arrays, objects, strings)
- Import specific lodash functions to minimize bundle size:
```ts
import groupBy from "lodash/groupBy";
```
- Create utility functions in `utils/` folder for reusable logic
30 changes: 28 additions & 2 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,32 @@ The project extensively uses environment variables for configuration. These vari
- Admin email addresses.
- Webhook URLs and API keys for internal communication.

## Conclusion
## Features

Inbox Zero exhibits a well-structured and modular architecture, leveraging a monorepo approach and modern technologies like Next.js, Prisma, and serverless functions. The architecture is designed for scalability, maintainability, and extensibility, with clear separation of concerns between the frontend, backend, and supporting services. The use of queues and background processing ensures efficient handling of asynchronous tasks like AI processing and email actions. The extensive use of environment variables promotes configuration flexibility and security. This architecture is well-suited for a complex SaaS application like Inbox Zero, providing a solid foundation for future development and feature enhancements.
### AI Personal Assistant

The user can set a prompt file which gets converted to individual rules in our database.
What is ultimately passed to the LLM is the database rules and not the prompt file.
We have a two way sync system between the db rules and the prompt file. This is messy, and maybe it would be better to just have one-way data flow via the prompt file.

The benefit to having database rules:

- In most cases, the AI is only deciding if conditions are matched.
- We have specific entries for each rule, so we can track how often each is called. If it were fully prompt based this wouldn't be possible. This is a potentially minor benefit to the user however.
- Because actions are static (unless using templates), the user can precisely define how the actions work without any LLM interference.

The current structure of the AI personal assistant is due to the product evolving. Had it been designed from scratch it would likely have been structured a little differently to avoid the two-way sync issues. This architecture may be changed in the future.

Another downside of not using the prompt file as the source of truth for the LLM is that some information included in the prompt file will not be passed to the LLM. Not something the user expects. For example, the user might write style guidelines at the top of the prompt file, but there's no natural way for this to be moved into the rules, as this information applies to all rules. We do have an `about` section that can be used for this on the `Settings` page, but this is separate.

### Reply Tracking

This feature is built off of the AI personal assistant.
There's a special type of rule for reply tracking.
I considered making it a separate feature similar to the cold email blocker. It makes things a little messy having this special type of rule, but the benefit is it integrates with the existing assistant and all the features built around that now.
This means each user has their own reply tracking prompt (but this is also annoying, because it makes it hard for us to do a global update for all users for the prompt, which is something we can do for the cold email blocker prompt).

### Cold email blocker

The cold email blocker monitors for incoming emails, if the user has never sent us an email before we run it through an LLM to decide if it's a cold email or not.
This feature is not connected to the AI personal assistant.
7 changes: 1 addition & 6 deletions apps/web/__tests__/ai-choose-args.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,12 +163,6 @@ function getAction(action: Partial<Action> = {}): Action {
cc: null,
bcc: null,
url: null,
labelPrompt: null,
subjectPrompt: null,
contentPrompt: null,
toPrompt: null,
ccPrompt: null,
bccPrompt: null,
...action,
};
}
Expand Down Expand Up @@ -196,6 +190,7 @@ function getRule(
categoryFilterType: null,
conditionalOperator: LogicalOperator.AND,
type: null,
trackReplies: null,
};
}

Expand Down
7 changes: 0 additions & 7 deletions apps/web/__tests__/ai-choose-rule.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,6 @@ describe.skipIf(!isAiTest)("aiChooseRule", () => {
cc: null,
bcc: null,
url: null,

labelPrompt: null,
subjectPrompt: null,
contentPrompt: null,
toPrompt: null,
ccPrompt: null,
bccPrompt: null,
},
]);

Expand Down
1 change: 1 addition & 0 deletions apps/web/__tests__/ai-process-user-request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,7 @@ function getRule(rule: Partial<RuleWithRelations>): RuleWithRelations {
type: RuleType.AI,
createdAt: new Date(),
updatedAt: new Date(),
trackReplies: null,
...rule,
};
}
Expand Down
30 changes: 11 additions & 19 deletions apps/web/app/(app)/cold-email-blocker/ColdEmailList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

import { useCallback, useState } from "react";
import useSWR from "swr";
import Link from "next/link";
import { ExternalLinkIcon } from "lucide-react";
import { useSession } from "next-auth/react";
import { LoadingContent } from "@/components/LoadingContent";
import type { ColdEmailsResponse } from "@/app/api/user/cold-email/route";
Expand All @@ -18,21 +16,16 @@ import {
import { DateCell } from "@/app/(app)/automation/ExecutedRulesTable";
import { TablePagination } from "@/components/TablePagination";
import { AlertBasic } from "@/components/Alert";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { getGmailSearchUrl } from "@/utils/url";
import { Button } from "@/components/ui/button";
import { useSearchParams } from "next/navigation";
import { markNotColdEmailAction } from "@/utils/actions/cold-email";
import { SectionDescription } from "@/components/Typography";
import { Checkbox } from "@/components/Checkbox";
import { useToggleSelect } from "@/hooks/useToggleSelect";
import { handleActionResult } from "@/utils/server-action";
import { useUser } from "@/hooks/useUser";
import { ViewEmailButton } from "@/components/ViewEmailButton";
import {
EmailMessageCell,
EmailMessageCellWithData,
} from "@/components/EmailMessageCell";
import { EmailMessageCellWithData } from "@/components/EmailMessageCell";
import { EnableFeatureCard } from "@/components/EnableFeatureCard";

export function ColdEmailList() {
const searchParams = useSearchParams();
Expand Down Expand Up @@ -213,16 +206,15 @@ function NoColdEmails() {

if (!data?.coldEmailBlocker || data?.coldEmailBlocker === "DISABLED") {
return (
<div className="mx-auto my-8 px-4 text-center">
<SectionDescription>
Cold email blocker is disabled. Enable it to start blocking cold
emails.
</SectionDescription>
<Button className="mt-4" asChild>
<Link href="/cold-email-blocker?tab=settings">
Enable Cold Email Blocker
</Link>
</Button>
<div className="mb-10">
<EnableFeatureCard
title="Cold Email Blocker"
description="Block unwanted cold emails automatically. Our AI identifies and filters out unsolicited sales emails before they reach your inbox."
imageSrc="https://illustrations.popsy.co/amber/calling-help.svg"
imageAlt="Cold email blocker"
buttonText="Set Up"
href="/cold-email-blocker?tab=settings"
/>
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export function ColdEmailPromptForm(props: {
);

return (
<form onSubmit={handleSubmit(onSubmit)} className="mt-2">
<form onSubmit={handleSubmit(onSubmit)}>
<Input
type="text"
autosizeTextarea
Expand Down
4 changes: 2 additions & 2 deletions apps/web/app/(app)/cold-email-blocker/ColdEmailSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ export function ColdEmailSettings() {
return (
<LoadingContent loading={isLoading} error={error}>
{data && (
<>
<div className="space-y-10">
<ColdEmailForm coldEmailBlocker={data.coldEmailBlocker} />
<ColdEmailPromptForm
coldEmailPrompt={data.coldEmailPrompt}
onSuccess={mutate}
/>
</>
</div>
)}
</LoadingContent>
);
Expand Down
Loading