From eec74f4e186a7cb7c7a103b0da54c8d8848f2199 Mon Sep 17 00:00:00 2001
From: Eliezer Steinbock <3090527+elie222@users.noreply.github.com>
Date: Tue, 2 Dec 2025 23:59:53 -0500
Subject: [PATCH 1/2] fix direct url
---
packages/cli/src/main.ts | 3 +++
packages/cli/src/utils.ts | 2 ++
version.txt | 2 +-
3 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/packages/cli/src/main.ts b/packages/cli/src/main.ts
index b88cd9c3ef..78a3ce9823 100644
--- a/packages/cli/src/main.ts
+++ b/packages/cli/src/main.ts
@@ -525,15 +525,18 @@ Full guide: https://docs.getinboxzero.com/self-hosting/microsoft-oauth`,
if (runWebInDocker) {
// Web app runs in Docker: use container hostnames
env.DATABASE_URL = `postgresql://${env.POSTGRES_USER}:${env.POSTGRES_PASSWORD}@db:5432/${env.POSTGRES_DB}`;
+ env.DIRECT_URL = env.DATABASE_URL;
env.UPSTASH_REDIS_URL = "http://serverless-redis-http:80";
} else {
// Web app runs on host: containers expose ports to localhost
env.DATABASE_URL = `postgresql://${env.POSTGRES_USER}:${env.POSTGRES_PASSWORD}@localhost:${postgresPort}/${env.POSTGRES_DB}`;
+ env.DIRECT_URL = env.DATABASE_URL;
env.UPSTASH_REDIS_URL = `http://localhost:${redisPort}`;
}
} else {
// External infrastructure - set placeholders for user to fill in
env.DATABASE_URL = "postgresql://user:password@your-host:5432/inboxzero";
+ env.DIRECT_URL = env.DATABASE_URL;
env.UPSTASH_REDIS_URL = "https://your-redis-url";
env.UPSTASH_REDIS_TOKEN = "your-redis-token";
}
diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils.ts
index e37b48dfd1..0353aefec1 100644
--- a/packages/cli/src/utils.ts
+++ b/packages/cli/src/utils.ts
@@ -50,11 +50,13 @@ export function generateEnvFile(config: {
setValue("POSTGRES_PASSWORD", env.POSTGRES_PASSWORD);
setValue("POSTGRES_DB", env.POSTGRES_DB);
setValue("DATABASE_URL", wrapInQuotes(env.DATABASE_URL));
+ setValue("DIRECT_URL", wrapInQuotes(env.DIRECT_URL));
setValue("UPSTASH_REDIS_URL", wrapInQuotes(env.UPSTASH_REDIS_URL));
setValue("UPSTASH_REDIS_TOKEN", env.UPSTASH_REDIS_TOKEN);
} else {
// External infra - set placeholders
setValue("DATABASE_URL", wrapInQuotes(env.DATABASE_URL));
+ setValue("DIRECT_URL", wrapInQuotes(env.DIRECT_URL));
setValue("UPSTASH_REDIS_URL", wrapInQuotes(env.UPSTASH_REDIS_URL));
setValue("UPSTASH_REDIS_TOKEN", env.UPSTASH_REDIS_TOKEN);
}
diff --git a/version.txt b/version.txt
index 81e4389812..7ff31a5a05 100644
--- a/version.txt
+++ b/version.txt
@@ -1 +1 @@
-v2.21.30
+v2.21.31
From 18e3dfa82a6772b62bc60bd2939532a0cbe1c261 Mon Sep 17 00:00:00 2001
From: Eliezer Steinbock <3090527+elie222@users.noreply.github.com>
Date: Wed, 3 Dec 2025 00:08:51 -0500
Subject: [PATCH 2/2] fix after cookie issue
---
.../assistant/ResultDisplay.tsx | 2 +-
.../[emailAccountId]/onboarding/page.tsx | 9 +++-
apps/web/app/(landing)/welcome/page.tsx | 9 +++-
apps/web/app/(landing)/welcome/utms.tsx | 45 ++++++++++++-------
4 files changed, 45 insertions(+), 20 deletions(-)
diff --git a/apps/web/app/(app)/[emailAccountId]/assistant/ResultDisplay.tsx b/apps/web/app/(app)/[emailAccountId]/assistant/ResultDisplay.tsx
index 910fb2fa5b..7602e21f49 100644
--- a/apps/web/app/(app)/[emailAccountId]/assistant/ResultDisplay.tsx
+++ b/apps/web/app/(app)/[emailAccountId]/assistant/ResultDisplay.tsx
@@ -128,7 +128,7 @@ export function ResultDisplayContent({ result }: { result: RunRulesResult }) {
{result.actionItems?.length ? (
<>
-
Actions taken:
+
Actions:
({
diff --git a/apps/web/app/(app)/[emailAccountId]/onboarding/page.tsx b/apps/web/app/(app)/[emailAccountId]/onboarding/page.tsx
index cab181928c..d3bb0769ca 100644
--- a/apps/web/app/(app)/[emailAccountId]/onboarding/page.tsx
+++ b/apps/web/app/(app)/[emailAccountId]/onboarding/page.tsx
@@ -3,7 +3,10 @@ import type { Metadata } from "next";
import { cookies } from "next/headers";
import { after } from "next/server";
import { OnboardingContent } from "@/app/(app)/[emailAccountId]/onboarding/OnboardingContent";
-import { fetchUserAndStoreUtms } from "@/app/(landing)/welcome/utms";
+import {
+ extractUtmValues,
+ fetchUserAndStoreUtms,
+} from "@/app/(landing)/welcome/utms";
import { auth } from "@/utils/auth";
export const maxDuration = 300;
@@ -25,10 +28,12 @@ export default async function OnboardingPage(props: {
const authPromise = auth();
const cookieStore = await cookies();
+ const utmValues = extractUtmValues(cookieStore);
+
after(async () => {
const user = await authPromise;
if (!user?.user) return;
- await fetchUserAndStoreUtms(user.user.id, cookieStore);
+ await fetchUserAndStoreUtms(user.user.id, utmValues);
});
return (
diff --git a/apps/web/app/(landing)/welcome/page.tsx b/apps/web/app/(landing)/welcome/page.tsx
index 85593284ab..0168279978 100644
--- a/apps/web/app/(landing)/welcome/page.tsx
+++ b/apps/web/app/(landing)/welcome/page.tsx
@@ -6,7 +6,10 @@ import { OnboardingForm } from "@/app/(landing)/welcome/form";
import { SquaresPattern } from "@/app/(landing)/home/SquaresPattern";
import { PageHeading, TypographyP } from "@/components/Typography";
import { CardBasic } from "@/components/ui/card";
-import { fetchUserAndStoreUtms } from "@/app/(landing)/welcome/utms";
+import {
+ extractUtmValues,
+ fetchUserAndStoreUtms,
+} from "@/app/(landing)/welcome/utms";
import { auth } from "@/utils/auth";
export const metadata: Metadata = {
@@ -27,10 +30,12 @@ export default async function WelcomePage(props: {
const authPromise = auth();
const cookieStore = await cookies();
+ const utmValues = extractUtmValues(cookieStore);
+
after(async () => {
const user = await authPromise;
if (!user?.user) return;
- await fetchUserAndStoreUtms(user.user.id, cookieStore);
+ await fetchUserAndStoreUtms(user.user.id, utmValues);
});
return (
diff --git a/apps/web/app/(landing)/welcome/utms.tsx b/apps/web/app/(landing)/welcome/utms.tsx
index 4fbf6c5c6f..0748376231 100644
--- a/apps/web/app/(landing)/welcome/utms.tsx
+++ b/apps/web/app/(landing)/welcome/utms.tsx
@@ -4,9 +4,31 @@ import type { ReadonlyRequestCookies } from "next/dist/server/web/spec-extension
const logger = createScopedLogger("utms");
+type UtmValues = {
+ utmCampaign?: string;
+ utmMedium?: string;
+ utmSource?: string;
+ utmTerm?: string;
+ affiliate?: string;
+};
+
+// Extract UTM values from cookies before passing to after() callback
+// This is required because request APIs (cookies/headers) cannot be used
+// inside after() in Server Components - only in Server Actions and Route Handlers
+// See: https://nextjs.org/docs/app/api-reference/functions/after
+export function extractUtmValues(cookies: ReadonlyRequestCookies): UtmValues {
+ return {
+ utmCampaign: cookies.get("utm_campaign")?.value,
+ utmMedium: cookies.get("utm_medium")?.value,
+ utmSource: cookies.get("utm_source")?.value,
+ utmTerm: cookies.get("utm_term")?.value,
+ affiliate: cookies.get("affiliate")?.value,
+ };
+}
+
export async function fetchUserAndStoreUtms(
userId: string,
- cookies: ReadonlyRequestCookies,
+ utmValues: UtmValues,
) {
const user = await prisma.user
.findUnique({
@@ -19,26 +41,19 @@ export async function fetchUserAndStoreUtms(
});
if (user && !user.utms) {
- await storeUtms(userId, cookies);
+ await storeUtms(userId, utmValues);
}
}
-// `cookies` passed in as we can't do await cookies() in the `after` hook
-async function storeUtms(userId: string, cookies: ReadonlyRequestCookies) {
+async function storeUtms(userId: string, utmValues: UtmValues) {
logger.info("Storing utms", { userId });
- const utmCampaign = cookies.get("utm_campaign");
- const utmMedium = cookies.get("utm_medium");
- const utmSource = cookies.get("utm_source");
- const utmTerm = cookies.get("utm_term");
- const affiliate = cookies.get("affiliate");
-
const utms = {
- utmCampaign: utmCampaign?.value,
- utmMedium: utmMedium?.value,
- utmSource: utmSource?.value,
- utmTerm: utmTerm?.value,
- affiliate: affiliate?.value,
+ utmCampaign: utmValues.utmCampaign,
+ utmMedium: utmValues.utmMedium,
+ utmSource: utmValues.utmSource,
+ utmTerm: utmValues.utmTerm,
+ affiliate: utmValues.affiliate,
};
try {