diff --git a/packages/twenty-front/src/modules/activities/components/ParticipantChip.tsx b/packages/twenty-front/src/modules/activities/components/ParticipantChip.tsx
index 0bd0ff2e5d..f95a3565ec 100644
--- a/packages/twenty-front/src/modules/activities/components/ParticipantChip.tsx
+++ b/packages/twenty-front/src/modules/activities/components/ParticipantChip.tsx
@@ -2,6 +2,7 @@ import { styled } from '@linaria/react';
import React from 'react';
import { getDisplayNameFromParticipant } from '@/activities/emails/utils/getDisplayNameFromParticipant';
+import { getEmailParticipantAvatarColorSeed } from '@/activities/emails/utils/getEmailParticipantAvatarColorSeed';
import { CoreObjectNameSingular } from 'twenty-shared/types';
import { RecordChip } from '@/object-record/components/RecordChip';
import { getAbsoluteImageUrl } from '~/utils/image/getAbsoluteImageUrl';
@@ -73,6 +74,9 @@ export const ParticipantChip = ({
avatarUrl={getAbsoluteImageUrl(avatarUrl)}
type="rounded"
placeholder={displayName}
+ placeholderColorSeed={getEmailParticipantAvatarColorSeed(
+ participant,
+ )}
size="sm"
/>
diff --git a/packages/twenty-front/src/modules/activities/emails/components/EmailThreadPreview.tsx b/packages/twenty-front/src/modules/activities/emails/components/EmailThreadPreview.tsx
index 3bd7cadf9e..bd9d975b2c 100644
--- a/packages/twenty-front/src/modules/activities/emails/components/EmailThreadPreview.tsx
+++ b/packages/twenty-front/src/modules/activities/emails/components/EmailThreadPreview.tsx
@@ -2,6 +2,7 @@ import { styled } from '@linaria/react';
import { ActivityRow } from '@/activities/components/ActivityRow';
import { EmailThreadNotShared } from '@/activities/emails/components/EmailThreadNotShared';
+import { getEmailParticipantAvatarColorSeed } from '@/activities/emails/utils/getEmailParticipantAvatarColorSeed';
import { useOpenRecordInSidePanel } from '@/side-panel/hooks/useOpenRecordInSidePanel';
import { useContext } from 'react';
@@ -78,6 +79,13 @@ type EmailThreadPreviewProps = {
thread: TimelineThread;
};
+type LastAvatar = {
+ displayedName: string | undefined;
+ avatarUrl: string | undefined;
+ isCountIcon: boolean;
+ placeholderColorSeed: string | undefined;
+};
+
export const EmailThreadPreview = ({ thread }: EmailThreadPreviewProps) => {
const { theme } = useContext(ThemeContext);
const { openRecordInSidePanel } = useOpenRecordInSidePanel();
@@ -93,14 +101,29 @@ export const EmailThreadPreview = ({ thread }: EmailThreadPreviewProps) => {
? `, ${thread.lastTwoParticipants?.[1]?.displayName}`
: '');
- const [finalDisplayedName, finalAvatarUrl, isCountIcon] =
+ const lastParticipant = thread?.lastTwoParticipants?.[1];
+
+ const {
+ displayedName,
+ avatarUrl,
+ isCountIcon,
+ placeholderColorSeed,
+ }: LastAvatar =
thread.participantCount > 3
- ? [`${thread.participantCount}`, '', true]
- : [
- thread?.lastTwoParticipants?.[1]?.displayName,
- thread?.lastTwoParticipants?.[1]?.avatarUrl,
- false,
- ];
+ ? {
+ displayedName: `${thread.participantCount}`,
+ avatarUrl: '',
+ isCountIcon: true,
+ placeholderColorSeed: undefined,
+ }
+ : {
+ displayedName: lastParticipant?.displayName,
+ avatarUrl: lastParticipant?.avatarUrl,
+ isCountIcon: false,
+ placeholderColorSeed: isDefined(lastParticipant)
+ ? getEmailParticipantAvatarColorSeed(lastParticipant)
+ : undefined,
+ };
const handleThreadClick = () => {
const canOpen =
@@ -122,10 +145,9 @@ export const EmailThreadPreview = ({ thread }: EmailThreadPreviewProps) => {
{isDefined(thread?.lastTwoParticipants?.[0]) && (
@@ -135,19 +157,19 @@ export const EmailThreadPreview = ({ thread }: EmailThreadPreviewProps) => {
thread.lastTwoParticipants[0].avatarUrl,
)}
placeholder={thread.lastTwoParticipants[0].displayName}
- placeholderColorSeed={
- thread.lastTwoParticipants[0].workspaceMemberId ||
- thread.lastTwoParticipants[0].personId
- }
+ placeholderColorSeed={getEmailParticipantAvatarColorSeed(
+ thread.lastTwoParticipants[0],
+ )}
type="rounded"
/>
)}
- {finalDisplayedName && (
+ {displayedName && (
{
+ it('uses the person ID consistently across participant shapes', () => {
+ expect(
+ getEmailParticipantAvatarColorSeed({
+ personId: 'person-id',
+ workspaceMemberId: 'workspace-member-id',
+ }),
+ ).toBe('person-id');
+
+ expect(
+ getEmailParticipantAvatarColorSeed({
+ person: { id: 'person-id' },
+ workspaceMember: { id: 'workspace-member-id' },
+ }),
+ ).toBe('person-id');
+ });
+
+ it('falls back to the workspace member ID', () => {
+ expect(
+ getEmailParticipantAvatarColorSeed({
+ workspaceMemberId: 'workspace-member-id',
+ }),
+ ).toBe('workspace-member-id');
+
+ expect(
+ getEmailParticipantAvatarColorSeed({
+ workspaceMember: { id: 'workspace-member-id' },
+ }),
+ ).toBe('workspace-member-id');
+ });
+
+ it('falls back to the normalized handle, then the display name', () => {
+ expect(
+ getEmailParticipantAvatarColorSeed({
+ handle: ' Person@Example.com ',
+ displayName: 'Person',
+ }),
+ ).toBe('person@example.com');
+
+ expect(
+ getEmailParticipantAvatarColorSeed({
+ displayName: 'Person',
+ }),
+ ).toBe('Person');
+ });
+});
diff --git a/packages/twenty-front/src/modules/activities/emails/utils/getEmailParticipantAvatarColorSeed.ts b/packages/twenty-front/src/modules/activities/emails/utils/getEmailParticipantAvatarColorSeed.ts
new file mode 100644
index 0000000000..c31fa08110
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/emails/utils/getEmailParticipantAvatarColorSeed.ts
@@ -0,0 +1,19 @@
+type EmailParticipantAvatarColorSeedInput = {
+ personId?: string | null;
+ workspaceMemberId?: string | null;
+ person?: { id: string } | null;
+ workspaceMember?: { id: string } | null;
+ handle?: string | null;
+ displayName?: string | null;
+};
+
+export const getEmailParticipantAvatarColorSeed = (
+ participant: EmailParticipantAvatarColorSeedInput,
+) =>
+ participant.personId ||
+ participant.person?.id ||
+ participant.workspaceMemberId ||
+ participant.workspaceMember?.id ||
+ participant.handle?.trim().toLowerCase() ||
+ participant.displayName ||
+ '';