diff --git a/.cursor/commands/address-pr-comments.md b/.claude/commands/address-pr-comments.md similarity index 100% rename from .cursor/commands/address-pr-comments.md rename to .claude/commands/address-pr-comments.md diff --git a/.cursor/commands/create-pr.md b/.claude/commands/create-pr.md similarity index 100% rename from .cursor/commands/create-pr.md rename to .claude/commands/create-pr.md diff --git a/.cursor/commands/next-step.md b/.claude/commands/next-step.md similarity index 100% rename from .cursor/commands/next-step.md rename to .claude/commands/next-step.md diff --git a/.cursor/commands/polish.md b/.claude/commands/polish.md similarity index 100% rename from .cursor/commands/polish.md rename to .claude/commands/polish.md diff --git a/.cursor/commands/review.md b/.claude/commands/review.md similarity index 100% rename from .cursor/commands/review.md rename to .claude/commands/review.md diff --git a/.cursor/commands/step-by-step.md b/.claude/commands/step-by-step.md similarity index 100% rename from .cursor/commands/step-by-step.md rename to .claude/commands/step-by-step.md diff --git a/apps/web/__tests__/ai-meeting-briefing.test.ts b/apps/web/__tests__/ai-meeting-briefing.test.ts index 609e1930a3..8184569142 100644 --- a/apps/web/__tests__/ai-meeting-briefing.test.ts +++ b/apps/web/__tests__/ai-meeting-briefing.test.ts @@ -43,6 +43,7 @@ function getMeetingBriefingData( return { event: getCalendarEvent(), externalGuests: [{ email: "alice@external.com", name: "Alice External" }], + internalTeamMembers: [], emailThreads: [], pastMeetings: [], ...overrides, diff --git a/apps/web/utils/meeting-briefs/gather-context.ts b/apps/web/utils/meeting-briefs/gather-context.ts index 9f2cc78dd6..1b443be8a5 100644 --- a/apps/web/utils/meeting-briefs/gather-context.ts +++ b/apps/web/utils/meeting-briefs/gather-context.ts @@ -23,9 +23,15 @@ export interface ExternalGuest { name?: string; } +export interface InternalTeamMember { + email: string; + name?: string; +} + export interface MeetingBriefingData { event: CalendarEvent; externalGuests: ExternalGuest[]; + internalTeamMembers: InternalTeamMember[]; emailThreads: EmailThread[]; pastMeetings: CalendarEvent[]; } @@ -46,10 +52,16 @@ export async function gatherContextForEvent({ logger: Logger; }): Promise { const externalAttendees = getExternalAttendees(event, userEmail, userDomain); + const internalAttendees = getInternalTeamMembers( + event, + userEmail, + userDomain, + ); const participantEmails = externalAttendees.map((a) => a.email); logger.info("Gathering context for external guests", { guestCount: externalAttendees.length, + internalTeamCount: internalAttendees.length, }); const [emailProvider, calendarProviders] = await Promise.all([ @@ -91,6 +103,10 @@ export async function gatherContextForEvent({ email: a.email, name: a.name, })), + internalTeamMembers: internalAttendees.map((a) => ({ + email: a.email, + name: a.name, + })), emailThreads: cappedThreads, pastMeetings, }; @@ -165,6 +181,27 @@ function getExternalAttendees( }); } +function getInternalTeamMembers( + event: CalendarEvent, + userEmail: string, + userDomain: string, +): CalendarEventAttendee[] { + const normalizedUserEmail = userEmail.trim().toLowerCase(); + const normalizedUserDomain = userDomain.trim().toLowerCase(); + + return event.attendees.filter((attendee) => { + const normalizedAttendeeEmail = attendee.email.trim().toLowerCase(); + const attendeeDomain = extractDomainFromEmail(normalizedAttendeeEmail); + + // Internal team members share the same domain but are not the user themselves + return ( + attendeeDomain && + attendeeDomain === normalizedUserDomain && + normalizedAttendeeEmail !== normalizedUserEmail + ); + }); +} + async function fetchPastMeetingsWithParticipants({ calendarProviders, participantEmails, diff --git a/apps/web/utils/meeting-briefs/process.ts b/apps/web/utils/meeting-briefs/process.ts index cea6574a1a..85caa007de 100644 --- a/apps/web/utils/meeting-briefs/process.ts +++ b/apps/web/utils/meeting-briefs/process.ts @@ -209,6 +209,7 @@ export async function runMeetingBrief({ await sendBriefingEmail({ event, briefingContent, + internalTeamMembers: briefingData.internalTeamMembers, emailAccountId, userEmail, provider, diff --git a/apps/web/utils/meeting-briefs/send-briefing.ts b/apps/web/utils/meeting-briefs/send-briefing.ts index 30b3d604cf..5deae6e1ef 100644 --- a/apps/web/utils/meeting-briefs/send-briefing.ts +++ b/apps/web/utils/meeting-briefs/send-briefing.ts @@ -6,6 +6,7 @@ import MeetingBriefingEmail, { generateMeetingBriefingSubject, type MeetingBriefingEmailProps, type BriefingContent, + type InternalTeamMember, } from "@inboxzero/resend/emails/meeting-briefing"; import type { CalendarEvent } from "@/utils/calendar/event-types"; import type { Logger } from "@/utils/logger"; @@ -15,6 +16,7 @@ import { formatTimeInUserTimezone } from "@/utils/date"; export async function sendBriefingEmail({ event, briefingContent, + internalTeamMembers, emailAccountId, userEmail, provider, @@ -23,6 +25,7 @@ export async function sendBriefingEmail({ }: { event: CalendarEvent; briefingContent: BriefingContent; + internalTeamMembers: InternalTeamMember[]; emailAccountId: string; userEmail: string; provider: string; @@ -35,6 +38,14 @@ export async function sendBriefingEmail({ const unsubscribeToken = await createUnsubscribeToken({ emailAccountId }); + // Merge internal team members into briefing content for the email. + // The AI generates only {guests}, while internalTeamMembers comes from + // gather-context.ts (domain-based filtering, not AI-researched). + const briefingContentWithTeam: BriefingContent = { + ...briefingContent, + internalTeamMembers, + }; + const emailProps: MeetingBriefingEmailProps = { baseUrl: env.NEXT_PUBLIC_BASE_URL, emailAccountId, @@ -42,7 +53,7 @@ export async function sendBriefingEmail({ formattedTime, videoConferenceLink: event.videoConferenceLink ?? "", eventUrl: event.eventUrl ?? "", - briefingContent, + briefingContent: briefingContentWithTeam, unsubscribeToken, }; diff --git a/packages/resend/emails/meeting-briefing.tsx b/packages/resend/emails/meeting-briefing.tsx index bece701ac1..0668f4c73f 100644 --- a/packages/resend/emails/meeting-briefing.tsx +++ b/packages/resend/emails/meeting-briefing.tsx @@ -16,8 +16,14 @@ export type GuestBriefing = { bullets: string[]; }; +export type InternalTeamMember = { + name?: string; + email: string; +}; + export type BriefingContent = { guests: GuestBriefing[]; + internalTeamMembers?: InternalTeamMember[]; }; export type MeetingBriefingEmailProps = { @@ -51,6 +57,20 @@ function renderGuestBriefings(guests: GuestBriefing[]) { )); } +function renderInternalTeamNote(internalTeamMembers: InternalTeamMember[]) { + if (internalTeamMembers.length === 0) return null; + + const names = internalTeamMembers + .map((member) => member.name || member.email) + .join(", "); + + return ( + + Also attending: {names} (internal team members - no briefing included) + + ); +} + export default function MeetingBriefingEmail({ baseUrl = "https://www.getinboxzero.com", emailAccountId, @@ -99,6 +119,9 @@ export default function MeetingBriefingEmail({
{renderGuestBriefings(briefingContent.guests)} + {renderInternalTeamNote( + briefingContent.internalTeamMembers ?? [], + )}
@@ -162,6 +185,10 @@ MeetingBriefingEmail.PreviewProps = { ], }, ], + internalTeamMembers: [ + { name: "Alice Chen", email: "alice@mycompany.com" }, + { name: "Bob Williams", email: "bob@mycompany.com" }, + ], }, };