diff --git a/apps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsx b/apps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsx
index 99b3a5117b..c37543d56d 100644
--- a/apps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsx
+++ b/apps/web/app/(app)/[emailAccountId]/assistant/ActionSteps.tsx
@@ -533,9 +533,16 @@ function ActionCard({
const isDraftEmailWithoutManualContent =
actionType === ActionType.DRAFT_EMAIL && !contentSetManually;
+ const isNotifySender = actionType === ActionType.NOTIFY_SENDER;
+
const rightContent = (
<>
- {isDraftEmailWithoutManualContent ? (
+ {isNotifySender ? (
+
+ Sends an automated notification from Inbox Zero informing the sender
+ their email was filtered as cold outreach.
+
+ ) : isDraftEmailWithoutManualContent ? (
Our AI generates a draft reply from your email history and knowledge
base.
diff --git a/apps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsx b/apps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsx
index ab11571eff..08f92d2f68 100644
--- a/apps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsx
+++ b/apps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsx
@@ -203,6 +203,12 @@ export function ActionSummaryCard({
summaryContent = `Folder: ${action.folderName?.value || "unset"}`;
break;
+ case ActionType.NOTIFY_SENDER:
+ summaryContent = "Notify sender";
+ tooltipText =
+ "Sends an automated notification from Inbox Zero (not from your email) informing the sender their email was filtered as cold outreach.";
+ break;
+
default:
summaryContent = actionTypeLabel;
}
diff --git a/apps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsx b/apps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsx
index d701e7327a..f7b62e4e0f 100644
--- a/apps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsx
+++ b/apps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsx
@@ -21,6 +21,7 @@ const actionNames: Record
= {
[ActionType.SEND_EMAIL]: "Send email",
[ActionType.CALL_WEBHOOK]: "Call webhook",
[ActionType.DIGEST]: "Add to digest",
+ [ActionType.NOTIFY_SENDER]: "Notify sender",
};
const actionTooltips: Partial> = {
diff --git a/apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx b/apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
index 09d1bdcc29..f033179449 100644
--- a/apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
+++ b/apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
@@ -5,6 +5,7 @@ import { useRouter } from "next/navigation";
import { type SubmitHandler, useFieldArray, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { usePostHog } from "posthog-js/react";
+import { env } from "@/env";
import {
PencilIcon,
TrashIcon,
@@ -52,7 +53,6 @@ import { getRuleConfig } from "@/utils/rule/consts";
import { RuleSectionCard } from "@/app/(app)/[emailAccountId]/assistant/RuleSectionCard";
import { ConditionSteps } from "@/app/(app)/[emailAccountId]/assistant/ConditionSteps";
import { ActionSteps } from "@/app/(app)/[emailAccountId]/assistant/ActionSteps";
-import { env } from "@/env";
export function Rule({
ruleId,
@@ -339,10 +339,21 @@ export function RuleForm({
value: ActionType.CALL_WEBHOOK,
icon: getActionIcon(ActionType.CALL_WEBHOOK),
},
+ // NOTIFY_SENDER is only available for cold email rules
+ ...(rule.systemType === SystemType.COLD_EMAIL &&
+ env.NEXT_PUBLIC_IS_RESEND_CONFIGURED
+ ? [
+ {
+ label: "Notify sender",
+ value: ActionType.NOTIFY_SENDER,
+ icon: getActionIcon(ActionType.NOTIFY_SENDER),
+ },
+ ]
+ : []),
];
return options;
- }, [provider, terminology.label.action]);
+ }, [provider, terminology.label.action, rule.systemType]);
const [isNameEditMode, setIsNameEditMode] = useState(alwaysEditMode);
const [isDeleting, setIsDeleting] = useState(false);
diff --git a/apps/web/app/(app)/[emailAccountId]/assistant/constants.ts b/apps/web/app/(app)/[emailAccountId]/assistant/constants.ts
index f8876876e9..7bce481e21 100644
--- a/apps/web/app/(app)/[emailAccountId]/assistant/constants.ts
+++ b/apps/web/app/(app)/[emailAccountId]/assistant/constants.ts
@@ -10,6 +10,7 @@ import {
WebhookIcon,
FileTextIcon,
FolderInputIcon,
+ BellIcon,
} from "lucide-react";
import { ActionType } from "@/generated/prisma/enums";
@@ -25,6 +26,7 @@ const ACTION_TYPE_COLORS = {
[ActionType.CALL_WEBHOOK]: "bg-gray-500",
[ActionType.DIGEST]: "bg-teal-500",
[ActionType.MOVE_FOLDER]: "bg-emerald-500",
+ [ActionType.NOTIFY_SENDER]: "bg-amber-500",
} as const;
export const ACTION_TYPE_TEXT_COLORS = {
@@ -39,6 +41,7 @@ export const ACTION_TYPE_TEXT_COLORS = {
[ActionType.CALL_WEBHOOK]: "text-gray-500",
[ActionType.DIGEST]: "text-teal-500",
[ActionType.MOVE_FOLDER]: "text-emerald-500",
+ [ActionType.NOTIFY_SENDER]: "text-amber-500",
} as const;
export const ACTION_TYPE_ICONS = {
@@ -53,6 +56,7 @@ export const ACTION_TYPE_ICONS = {
[ActionType.CALL_WEBHOOK]: WebhookIcon,
[ActionType.DIGEST]: FileTextIcon,
[ActionType.MOVE_FOLDER]: FolderInputIcon,
+ [ActionType.NOTIFY_SENDER]: BellIcon,
} as const;
// Helper function to get action type from string (for RulesPrompt.tsx)
diff --git a/apps/web/app/(app)/[emailAccountId]/cold-email-blocker/ColdEmailContent.tsx b/apps/web/app/(app)/[emailAccountId]/cold-email-blocker/ColdEmailContent.tsx
index eadb9317e6..3ec02d5a6e 100644
--- a/apps/web/app/(app)/[emailAccountId]/cold-email-blocker/ColdEmailContent.tsx
+++ b/apps/web/app/(app)/[emailAccountId]/cold-email-blocker/ColdEmailContent.tsx
@@ -15,11 +15,11 @@ export function ColdEmailContent({ searchParam }: { searchParam?: string }) {
const { emailAccountId } = useAccount();
return (
-
+
- Test
Cold Emails
Marked Not Cold
+ Test
Settings
diff --git a/apps/web/app/(landing)/components/page.tsx b/apps/web/app/(landing)/components/page.tsx
index 12cc2ded5a..0c94dd6ada 100644
--- a/apps/web/app/(landing)/components/page.tsx
+++ b/apps/web/app/(landing)/components/page.tsx
@@ -330,6 +330,11 @@ export default function Components() {
label: "Digest",
id: "digest",
},
+ {
+ type: ActionType.NOTIFY_SENDER,
+ label: "Notify sender",
+ id: "notify_sender",
+ },
]}
provider="gmail"
labels={[{ id: "label", name: "Label" }]}
diff --git a/apps/web/components/PlanBadge.tsx b/apps/web/components/PlanBadge.tsx
index f633fd2c05..23fbbb8f59 100644
--- a/apps/web/components/PlanBadge.tsx
+++ b/apps/web/components/PlanBadge.tsx
@@ -179,6 +179,8 @@ function getActionLabel(type: ActionType, provider: string) {
return "Mark as spam";
case ActionType.MARK_READ:
return "Mark as read";
+ case ActionType.NOTIFY_SENDER:
+ return "Notify Sender";
default:
return capitalCase(type);
}
@@ -223,6 +225,8 @@ export function getActionColor(actionType: ActionType): Color {
case ActionType.CALL_WEBHOOK:
case ActionType.DIGEST:
return "purple";
+ case ActionType.NOTIFY_SENDER:
+ return "purple";
default: {
const exhaustiveCheck: never = actionType;
return exhaustiveCheck;
diff --git a/apps/web/components/email-list/EmailMessage.tsx b/apps/web/components/email-list/EmailMessage.tsx
index 635e2b4817..915d05d6a6 100644
--- a/apps/web/components/email-list/EmailMessage.tsx
+++ b/apps/web/components/email-list/EmailMessage.tsx
@@ -25,6 +25,7 @@ import { EmailAttachments } from "@/components/email-list/EmailAttachments";
import { Loading } from "@/components/Loading";
import { MessageText } from "@/components/Typography";
import { useAccount } from "@/providers/EmailAccountProvider";
+import { formatReplySubject } from "@/utils/email/subject";
export function EmailMessage({
message,
@@ -328,7 +329,7 @@ const prepareReplyingToEmail = (
// If following an email from yourself, don't add "Re:" prefix
subject: sentFromUser
? message.headers.subject
- : `Re: ${message.headers.subject}`,
+ : formatReplySubject(message.headers.subject),
headerMessageId: message.headers["message-id"]!,
threadId: message.threadId!,
// Keep original CC
diff --git a/apps/web/env.ts b/apps/web/env.ts
index 4655e252a6..2abb69e28f 100644
--- a/apps/web/env.ts
+++ b/apps/web/env.ts
@@ -190,6 +190,8 @@ export const env = createEnv({
NEXT_PUBLIC_DIGEST_ENABLED: z.coerce.boolean().optional(),
NEXT_PUBLIC_MEETING_BRIEFS_ENABLED: z.coerce.boolean().optional(),
NEXT_PUBLIC_INTEGRATIONS_ENABLED: z.coerce.boolean().optional(),
+ // Derived from presence of RESEND_API_KEY
+ NEXT_PUBLIC_IS_RESEND_CONFIGURED: z.boolean().optional(),
},
// For Next.js >= 13.4.4, you only need to destructure client variables:
experimental__runtimeEnv: {
@@ -251,5 +253,6 @@ export const env = createEnv({
process.env.NEXT_PUBLIC_MEETING_BRIEFS_ENABLED,
NEXT_PUBLIC_INTEGRATIONS_ENABLED:
process.env.NEXT_PUBLIC_INTEGRATIONS_ENABLED,
+ NEXT_PUBLIC_IS_RESEND_CONFIGURED: !!process.env.RESEND_API_KEY,
},
});
diff --git a/apps/web/prisma/migrations/20251219012216_add_notify_sender_action_type/migration.sql b/apps/web/prisma/migrations/20251219012216_add_notify_sender_action_type/migration.sql
new file mode 100644
index 0000000000..cd5a0435c7
--- /dev/null
+++ b/apps/web/prisma/migrations/20251219012216_add_notify_sender_action_type/migration.sql
@@ -0,0 +1,2 @@
+-- AlterEnum
+ALTER TYPE "ActionType" ADD VALUE 'NOTIFY_SENDER';
diff --git a/apps/web/prisma/schema.prisma b/apps/web/prisma/schema.prisma
index a558478efa..f5047bcea3 100644
--- a/apps/web/prisma/schema.prisma
+++ b/apps/web/prisma/schema.prisma
@@ -1051,6 +1051,7 @@ enum ActionType {
// TRACK_THREAD // @deprecated - No longer used. We rely on rule SystemType instead to run this.
DIGEST
MOVE_FOLDER
+ NOTIFY_SENDER // Sends notification from Inbox Zero (not user's email). Only for cold email rules.
// SUMMARIZE
// SNOOZE
// ADD_TO_DO
diff --git a/apps/web/utils/action-display.tsx b/apps/web/utils/action-display.tsx
index 5d956538c7..fefa76f279 100644
--- a/apps/web/utils/action-display.tsx
+++ b/apps/web/utils/action-display.tsx
@@ -2,6 +2,7 @@ import { ActionType } from "@/generated/prisma/enums";
import { getEmailTerminology } from "@/utils/terminology";
import {
ArchiveIcon,
+ BellIcon,
FolderInputIcon,
ForwardIcon,
ReplyIcon,
@@ -76,6 +77,8 @@ export function getActionDisplay(
return "Digest";
case ActionType.CALL_WEBHOOK:
return "Call Webhook";
+ case ActionType.NOTIFY_SENDER:
+ return "Notify Sender";
default: {
const exhaustiveCheck: never = action.type;
return exhaustiveCheck;
@@ -107,6 +110,8 @@ export function getActionIcon(actionType: ActionType) {
return WebhookIcon;
case ActionType.DIGEST:
return NewspaperIcon;
+ case ActionType.NOTIFY_SENDER:
+ return BellIcon;
default: {
const exhaustiveCheck: never = actionType;
return exhaustiveCheck;
diff --git a/apps/web/utils/action-item.ts b/apps/web/utils/action-item.ts
index 09205194d4..6b1bfaece9 100644
--- a/apps/web/utils/action-item.ts
+++ b/apps/web/utils/action-item.ts
@@ -149,6 +149,9 @@ export const actionInputs: Record<
},
],
},
+ [ActionType.NOTIFY_SENDER]: {
+ fields: [],
+ },
};
export function getActionFields(fields: Action | ExecutedAction | undefined) {
@@ -275,6 +278,9 @@ export function sanitizeActionFields(
url: action.url ?? null,
};
}
+ case ActionType.NOTIFY_SENDER: {
+ return base;
+ }
default:
// biome-ignore lint/correctness/noSwitchDeclarations: intentional exhaustive check
const exhaustiveCheck: never = action.type;
diff --git a/apps/web/utils/actions/rule.validation.ts b/apps/web/utils/actions/rule.validation.ts
index 0508c51b0e..083548ea13 100644
--- a/apps/web/utils/actions/rule.validation.ts
+++ b/apps/web/utils/actions/rule.validation.ts
@@ -26,6 +26,7 @@ const zodActionType = z.enum([
ActionType.MARK_READ,
ActionType.DIGEST,
ActionType.MOVE_FOLDER,
+ ActionType.NOTIFY_SENDER,
]);
const zodConditionType = z.enum([ConditionType.AI, ConditionType.STATIC]);
diff --git a/apps/web/utils/ai/actions.ts b/apps/web/utils/ai/actions.ts
index bdb05c9cb2..edd766545a 100644
--- a/apps/web/utils/ai/actions.ts
+++ b/apps/web/utils/ai/actions.ts
@@ -10,6 +10,9 @@ import { filterNullProperties } from "@/utils";
import { labelMessageAndSync } from "@/utils/label.server";
import { hasVariables } from "@/utils/template";
import prisma from "@/utils/prisma";
+import { sendColdEmailNotification } from "@/utils/cold-email/send-notification";
+import { extractEmailAddress } from "@/utils/email";
+import { captureException } from "@/utils/error";
const MODULE = "ai-actions";
@@ -73,6 +76,8 @@ export const runActionFunction = async (options: {
return digest(opts);
case ActionType.MOVE_FOLDER:
return move_folder(opts);
+ case ActionType.NOTIFY_SENDER:
+ return notify_sender(opts);
default:
throw new Error(`Unknown action: ${action}`);
}
@@ -321,6 +326,44 @@ const move_folder: ActionFunction<{ folderId?: string | null }> = async ({
await client.moveThreadToFolder(email.threadId, userEmail, args.folderId);
};
+const notify_sender: ActionFunction> = async ({
+ email,
+ userEmail,
+ logger,
+}) => {
+ const senderEmail = extractEmailAddress(email.headers.from);
+ if (!senderEmail) {
+ logger.error("Could not extract sender email for notify_sender action");
+ return;
+ }
+
+ const result = await sendColdEmailNotification({
+ senderEmail,
+ recipientEmail: userEmail,
+ originalSubject: email.headers.subject,
+ originalMessageId: email.headers["message-id"],
+ logger,
+ });
+
+ if (!result.success) {
+ // Best-effort: don't fail the whole rule run if notification can't be sent.
+ logger.error("Cold email notification failed", {
+ senderEmail,
+ error: result.error,
+ });
+
+ captureException(
+ new Error(result.error ?? "Cold email notification failed"),
+ {
+ extra: { actionType: ActionType.NOTIFY_SENDER, senderEmail },
+ sampleRate: 0.01,
+ },
+ userEmail,
+ );
+ return;
+ }
+};
+
async function lazyUpdateActionLabelId({
labelName,
labelId,
diff --git a/apps/web/utils/cold-email/send-notification.ts b/apps/web/utils/cold-email/send-notification.ts
new file mode 100644
index 0000000000..3f459a2a29
--- /dev/null
+++ b/apps/web/utils/cold-email/send-notification.ts
@@ -0,0 +1,55 @@
+import { sendColdEmailNotification as sendColdEmailNotificationViaResend } from "@inboxzero/resend";
+import { env } from "@/env";
+import { getErrorMessage } from "@/utils/error";
+import type { Logger } from "@/utils/logger";
+import { formatReplySubject } from "@/utils/email/subject";
+
+export async function sendColdEmailNotification({
+ senderEmail,
+ recipientEmail,
+ originalSubject,
+ originalMessageId,
+ logger,
+}: {
+ senderEmail: string; // The cold emailer we're notifying
+ recipientEmail: string; // The user who received the cold email
+ originalSubject: string;
+ originalMessageId?: string; // Message-ID of the original email for threading
+ logger: Logger;
+}): Promise<{ success: boolean; error?: string }> {
+ if (!env.RESEND_API_KEY) {
+ logger.warn("Resend not configured, skipping cold email notification");
+ return { success: false, error: "Resend not configured" };
+ }
+
+ const subject = formatReplySubject(originalSubject);
+
+ try {
+ const result = await sendColdEmailNotificationViaResend({
+ from: env.RESEND_FROM_EMAIL,
+ to: senderEmail,
+ replyTo: recipientEmail,
+ subject,
+ inReplyTo: originalMessageId,
+ emailProps: {
+ baseUrl: env.NEXT_PUBLIC_BASE_URL,
+ },
+ });
+
+ logger.info("Cold email notification sent", {
+ senderEmail,
+ messageId: result.data?.id,
+ });
+
+ return { success: true };
+ } catch (error) {
+ logger.error("Error sending cold email notification", {
+ error,
+ senderEmail,
+ });
+ return {
+ success: false,
+ error: error instanceof Error ? error.message : "Unknown error",
+ };
+ }
+}
diff --git a/apps/web/utils/email/subject.ts b/apps/web/utils/email/subject.ts
index 9706060040..8683cf21a7 100644
--- a/apps/web/utils/email/subject.ts
+++ b/apps/web/utils/email/subject.ts
@@ -5,6 +5,10 @@
*/
export function formatReplySubject(subject: string): string {
const trimmed = (subject ?? "").trim();
+ // Avoid "Re: " with no subject
+ if (!trimmed) {
+ return "Re: (no subject)";
+ }
// Avoid duplicate "Re:" prefix (case-insensitive check)
if (/^re:/i.test(trimmed)) {
return trimmed;
diff --git a/apps/web/utils/error.ts b/apps/web/utils/error.ts
index aa8fbc2cb5..fadc551d75 100644
--- a/apps/web/utils/error.ts
+++ b/apps/web/utils/error.ts
@@ -32,7 +32,7 @@ export function isGmailError(
export function captureException(
error: unknown,
- additionalInfo?: { extra?: Record },
+ additionalInfo?: { extra?: Record; sampleRate?: number },
userEmail?: string,
) {
if (isKnownApiError(error)) {
@@ -41,6 +41,15 @@ export function captureException(
return;
}
+ const sampleRate = additionalInfo?.sampleRate;
+ if (
+ Number.isFinite(sampleRate) &&
+ process.env.NODE_ENV === "production" &&
+ Math.random() >= (sampleRate as number)
+ ) {
+ return;
+ }
+
if (userEmail) setUser({ email: userEmail });
sentryCaptureException(error, additionalInfo);
}
@@ -208,3 +217,33 @@ export function checkCommonErrors(
return null;
}
+
+export function getErrorMessage(error: unknown): string | undefined {
+ if (typeof error === "string") return error;
+ if (error instanceof Error) return error.message;
+
+ const outer = asRecord(error);
+ if (!outer) return undefined;
+
+ const directMessage = getStringProp(outer, "message");
+ if (directMessage) return directMessage;
+
+ const nested = asRecord(outer.error);
+ if (!nested) return undefined;
+
+ return getStringProp(nested, "message");
+}
+
+function asRecord(value: unknown): Record | null {
+ return typeof value === "object" && value !== null
+ ? (value as Record)
+ : null;
+}
+
+function getStringProp(
+ obj: Record,
+ key: string,
+): string | undefined {
+ const value = obj[key];
+ return typeof value === "string" ? value : undefined;
+}
diff --git a/packages/resend/emails/cold-email-notification.tsx b/packages/resend/emails/cold-email-notification.tsx
new file mode 100644
index 0000000000..1b1c84615a
--- /dev/null
+++ b/packages/resend/emails/cold-email-notification.tsx
@@ -0,0 +1,89 @@
+import {
+ Body,
+ Container,
+ Head,
+ Hr,
+ Html,
+ Img,
+ Link,
+ Section,
+ Tailwind,
+ Text,
+} from "@react-email/components";
+import type { FC } from "react";
+
+export type ColdEmailNotificationProps = {
+ baseUrl: string;
+};
+
+type ColdEmailNotificationComponent = FC & {
+ PreviewProps: ColdEmailNotificationProps;
+};
+
+const ColdEmailNotification: ColdEmailNotificationComponent = ({
+ baseUrl = "https://www.getinboxzero.com",
+}: ColdEmailNotificationProps) => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ Inbox Zero
+
+
+
+
+
+
+ The recipient uses{" "}
+
+ Inbox Zero
+ {" "}
+ to automatically detect and filter cold emails from first-time
+ senders.
+
+
+ Your email was identified as unsolicited outreach and has been
+ filtered.
+
+
+ If this was sent in error or you need to reach them, please try
+ an alternative contact method.
+
+
+
+
+
+
+ This is an automated message from{" "}
+
+ Inbox Zero
+
+ .
+
+
+
+
+
+
+ );
+};
+
+export default ColdEmailNotification;
+
+ColdEmailNotification.PreviewProps = {
+ baseUrl: "https://www.getinboxzero.com",
+};
diff --git a/packages/resend/src/send.tsx b/packages/resend/src/send.tsx
index 4c6ada7b43..1ed5dd369e 100644
--- a/packages/resend/src/send.tsx
+++ b/packages/resend/src/send.tsx
@@ -14,6 +14,12 @@ import MeetingBriefingEmail, {
type MeetingBriefingEmailProps,
generateMeetingBriefingSubject,
} from "../emails/meeting-briefing";
+import ColdEmailNotification, {
+ type ColdEmailNotificationProps,
+} from "../emails/cold-email-notification";
+
+const RESEND_NOT_CONFIGURED_MESSAGE =
+ "Resend is not configured. You need to add a RESEND_API_KEY in your .env file for emails to work.";
const sendEmail = async ({
from,
@@ -34,9 +40,7 @@ const sendEmail = async ({
unsubscribeToken: string;
}) => {
if (!resend) {
- console.log(
- "Resend is not configured. You need to add a RESEND_API_KEY in your .env file for emails to work.",
- );
+ console.log(RESEND_NOT_CONFIGURED_MESSAGE);
return Promise.resolve();
}
@@ -198,3 +202,60 @@ export const sendMeetingBriefingEmail = async ({
],
});
};
+
+/**
+ * Send a notification to a cold emailer informing them their email was filtered.
+ * This is different from other emails - it goes to an external sender, not our user,
+ * so it doesn't have an unsubscribe token.
+ */
+export const sendColdEmailNotification = async ({
+ from,
+ to,
+ replyTo,
+ subject,
+ inReplyTo,
+ emailProps,
+}: {
+ from: string;
+ to: string; // The cold emailer we're notifying
+ replyTo: string; // The user who received the cold email
+ subject: string;
+ inReplyTo?: string; // Message-ID of original email for threading
+ emailProps: ColdEmailNotificationProps;
+}) => {
+ if (!resend) {
+ console.log(RESEND_NOT_CONFIGURED_MESSAGE);
+ return { data: null, error: null };
+ }
+
+ const react = ;
+ const text = await render(react, { plainText: true });
+
+ const result = await resend.emails.send({
+ from,
+ to,
+ replyTo,
+ subject,
+ react,
+ text,
+ // Threading headers - In-Reply-To and References make the reply appear in the same thread
+ headers: inReplyTo
+ ? { "In-Reply-To": inReplyTo, References: inReplyTo }
+ : undefined,
+ tags: [
+ {
+ name: "category",
+ value: "cold-email-notification",
+ },
+ ],
+ });
+
+ if (result.error) {
+ console.error("Error sending cold email notification", result.error);
+ throw new Error(
+ `Error sending cold email notification: ${result.error.message}`,
+ );
+ }
+
+ return result;
+};
diff --git a/version.txt b/version.txt
index df206df50e..098c5314fa 100644
--- a/version.txt
+++ b/version.txt
@@ -1 +1 @@
-v2.24.8
+v2.25.0