Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 18 additions & 0 deletions apps/web/app/api/ai/digest/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { aiSummarizeEmailForDigest } from "@/utils/ai/digest/summarize-email-for
import { getEmailAccountWithAi } from "@/utils/user/get";
import type { StoredDigestContent } from "@/app/api/resend/digest/validation";
import { withError } from "@/utils/middleware";
import { isAssistantEmail } from "@/utils/assistant/is-assistant-email";
import { env } from "@/env";

export const POST = withError(
verifySignatureAppRouter(async (request: Request) => {
Expand All @@ -28,6 +30,22 @@ export const POST = withError(
throw new Error("Email account not found");
}

// Don't summarize Digest emails (this will actually block all emails that we send, but that's okay)
if (message.from === env.RESEND_FROM_EMAIL) {
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Sep 14, 2025

Choose a reason for hiding this comment

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

Normalize From and RESEND_FROM_EMAIL to bare, lowercase addresses before comparing; strict equality will miss cases with display names or different casing and fail to skip our own emails.

Prompt for AI agents
Address the following comment on apps/web/app/api/ai/digest/route.ts at line 34:

<comment>Normalize From and RESEND_FROM_EMAIL to bare, lowercase addresses before comparing; strict equality will miss cases with display names or different casing and fail to skip our own emails.</comment>

<file context>
@@ -28,6 +30,22 @@ export const POST = withError(
       }
 
+      // Don&#39;t summarize Digest emails (this will actually block all emails that we send, but that&#39;s okay)
+      if (message.from === env.RESEND_FROM_EMAIL) {
+        logger.info(&quot;Skipping digest item because it is from us&quot;);
+        return new NextResponse(&quot;OK&quot;, { status: 200 });
</file context>
Fix with Cubic

logger.info("Skipping digest item because it is from us");
return new NextResponse("OK", { status: 200 });
}
Comment on lines +33 to +37
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Make “from us” check robust to display names/casing

Direct string equality may miss cases like different display names or casing. Normalize to the bare address before compare.

Apply:

-      // Don't summarize Digest emails (this will actually block all emails that we send, but that's okay)
-      if (message.from === env.RESEND_FROM_EMAIL) {
+      // Don't summarize any emails we send ourselves
+      const fromAddr =
+        (message.from?.match(/<([^>]+)>/)?.[1] ?? message.from)?.toLowerCase();
+      const resendAddr =
+        (env.RESEND_FROM_EMAIL?.match(/<([^>]+)>/)?.[1] ??
+          env.RESEND_FROM_EMAIL)?.toLowerCase();
+      if (fromAddr && resendAddr && fromAddr === resendAddr) {
         logger.info("Skipping digest item because it is from us");
         return new NextResponse("OK", { status: 200 });
       }

You may want to also check Reply-To/Sender if those can differ from From in your pipeline.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Print file (if exists) ==="
if [ -f "apps/web/app/api/ai/digest/route.ts" ]; then
  sed -n '1,240p' apps/web/app/api/ai/digest/route.ts
else
  echo "FILE_NOT_FOUND: apps/web/app/api/ai/digest/route.ts"
fi

echo
echo "=== Search for RESEND_FROM_EMAIL across repo ==="
rg -n --hidden --no-ignore-vcs 'RESEND_FROM_EMAIL' || true

echo
echo "=== Search for 'message.from' usage across repo ==="
rg -n --hidden --no-ignore-vcs '\bmessage\.from\b' || true

echo
echo "=== Show surrounding lines for 'message.from' matches (in file if present) ==="
rg -n -C3 '\bmessage\.from\b' apps -S || true

echo
echo "=== Show env exports / definitions that reference RESEND_FROM_EMAIL (common places) ==="
rg -n --hidden --no-ignore-vcs 'RESEND_FROM_EMAIL' -S || true

Length of output: 9624


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== rg digestBody ==="
rg -n 'digestBody' -S || true

echo
echo "=== validation file (if exists) ==="
if [ -f "apps/web/app/api/ai/digest/validation.ts" ]; then
  sed -n '1,240p' apps/web/app/api/ai/digest/validation.ts
else
  echo "NOT_FOUND: apps/web/app/api/ai/digest/validation.ts"
fi

echo
echo "=== env.ts ==="
sed -n '1,240p' apps/web/env.ts || true

echo
echo "=== resend summary route (around 220-260) ==="
sed -n '200,300p' apps/web/app/api/resend/summary/route.ts || true

echo
echo "=== resend digest route (around 270-300) ==="
sed -n '260,320p' apps/web/app/api/resend/digest/route.ts || true

echo
echo "=== outlook message mapping (around 320-360) ==="
sed -n '320,380p' apps/web/utils/outlook/message.ts || true

echo
echo "=== microsoft email parsing (around 740-780) ==="
sed -n '720,800p' apps/web/utils/email/microsoft.ts || true

echo
echo "=== assistant is-assistant-email utility ==="
sed -n '1,240p' apps/web/utils/assistant/is-assistant-email.ts || true

echo
echo "=== ai group find-receipts snippet (around 120-140) ==="
sed -n '110,140p' apps/web/utils/ai/group/find-receipts.ts || true

Length of output: 21445


Make “from us” check robust to display names/casing

Strict equality fails because env.RESEND_FROM_EMAIL defaults to a display-name form (e.g. "Inbox Zero updates@transactional.getinboxzero.com") while incoming messages may have only the bare address. Normalize to the bare address before comparing.

File: apps/web/app/api/ai/digest/route.ts (around lines 33–37) — add import:
import { extractEmailAddress } from "@/utils/email";

Replace the equality check with:

// Don't summarize any emails we send ourselves
const fromAddr =
  extractEmailAddress(message.from)?.toLowerCase() ??
  message.from?.toLowerCase();
const resendAddr =
  extractEmailAddress(env.RESEND_FROM_EMAIL)?.toLowerCase() ??
  env.RESEND_FROM_EMAIL?.toLowerCase();
if (fromAddr && resendAddr && fromAddr === resendAddr) {
  logger.info("Skipping digest item because it is from us");
  return new NextResponse("OK", { status: 200 });
}

Also consider checking Reply-To / Sender if those can differ in your pipeline.

🤖 Prompt for AI Agents
In apps/web/app/api/ai/digest/route.ts around lines 33 to 37, the strict
equality check against env.RESEND_FROM_EMAIL fails for display-name forms and
different casing; import extractEmailAddress from "@/utils/email" and replace
the check by extracting the bare email address from both message.from and
env.RESEND_FROM_EMAIL, lowercasing both (falling back to the original string if
extraction returns undefined), then compare the normalized addresses and return
the 200 response if they match; optionally extend the same normalization/check
to Reply-To or Sender headers if those may differ in your pipeline.


const isFromAssistant = isAssistantEmail({
userEmail: emailAccount.email,
emailToCheck: message.from,
});

if (isFromAssistant) {
logger.info("Skipping digest item because it is from the assistant");
return new NextResponse("OK", { status: 200 });
}

const ruleName = await resolveRuleName(actionId);
const summary = await aiSummarizeEmailForDigest({
ruleName,
Expand Down
7 changes: 2 additions & 5 deletions apps/web/app/api/resend/digest/all/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ async function sendDigestAllUpdate() {
const emailAccounts = await prisma.emailAccount.findMany({
where: {
digestSchedule: {
nextOccurrenceAt: {
lte: now,
},
nextOccurrenceAt: { lte: now },
},
// Only send to premium users
user: {
Expand All @@ -35,9 +33,8 @@ async function sendDigestAllUpdate() {
],
},
},
// User at least 4 days old
createdAt: {
lt: subDays(now, 4),
lt: subDays(now, 1),
},
},
select: {
Expand Down
5 changes: 3 additions & 2 deletions apps/web/hooks/useFeatureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ export type WelcomeTestimonialVariant = "control" | "hidden";

export function useWelcomeTestimonialVariant() {
return (
(useFeatureFlagVariantKey("welcome-testimonial") as WelcomeTestimonialVariant) ||
"control"
(useFeatureFlagVariantKey(
"welcome-testimonial",
) as WelcomeTestimonialVariant) || "control"
);
}
2 changes: 1 addition & 1 deletion version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v2.9.26
v2.9.27
Loading