From 40c98522dce3950507b387a3b9d3c368908ebfc2 Mon Sep 17 00:00:00 2001 From: maaz519 Date: Sat, 18 Jul 2026 16:30:28 +0530 Subject: [PATCH] feat(mail): tag mail threads source=crm-mail (list separately from chat) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ingest() puts metadata on the interaction, not the thread, and listThreads filters THREAD metadata — so MailService now tags the thread directly after deposit (source=crm-mail). Lets the CRM list mail threads apart from Messenger chat (source=crm-messenger). 7 tests. NOTE: requires an IIOS redeploy. Co-Authored-By: Claude Opus 4.8 --- packages/iios-service/src/mail/mail.service.spec.ts | 3 ++- packages/iios-service/src/mail/mail.service.ts | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/iios-service/src/mail/mail.service.spec.ts b/packages/iios-service/src/mail/mail.service.spec.ts index ea1dc08..21c2b8f 100644 --- a/packages/iios-service/src/mail/mail.service.spec.ts +++ b/packages/iios-service/src/mail/mail.service.spec.ts @@ -25,7 +25,7 @@ function mail(): MailService { const ingest = new IngestService(asService, makeFakePorts(), actors); const outbound = new OutboundService(asService, new CapabilityBroker(makeFakePorts(), new CapabilityProviderRegistry()), new IdempotencyService(asService)); const sender = new TemplatedSender(templates, outbound, makeFakePorts()); - return new MailService(templates, ingest, sender, actors); + return new MailService(templates, ingest, sender, actors, asService); } const messages = () => new MessageService(asService, makeFakePorts(), actors); @@ -56,6 +56,7 @@ describe('MailService.postInternal', () => { const thread = await prisma.iiosThread.findUniqueOrThrow({ where: { id: res.threadId } }); expect(thread.subject).toBe('Welcome Dana'); + expect((thread.metadata as { source?: string } | null)?.source).toBe('crm-mail'); // lists separately from chat // The load-bearing assertion: the RECIPIENT can see the thread in their inbox. const bobThreads = await messages().listThreads(bob); diff --git a/packages/iios-service/src/mail/mail.service.ts b/packages/iios-service/src/mail/mail.service.ts index f0ed3a3..9aeb4b4 100644 --- a/packages/iios-service/src/mail/mail.service.ts +++ b/packages/iios-service/src/mail/mail.service.ts @@ -1,4 +1,6 @@ import { Injectable } from '@nestjs/common'; +import { Prisma } from '@prisma/client'; +import { PrismaService } from '../prisma/prisma.service'; import { IngestService } from '../interactions/ingest.service'; import { ActorResolver, type MessagePrincipal } from '../identity/actor.resolver'; import { TemplateService } from '../templates/template.service'; @@ -55,8 +57,12 @@ export class MailService { private readonly ingest: IngestService, private readonly sender: TemplatedSender, private readonly actors: ActorResolver, + private readonly prisma: PrismaService, ) {} + /** Marks a thread as CRM mail so it lists separately from chat (Messenger uses source=crm-messenger). */ + static readonly SOURCE = 'crm-mail'; + async postInternal(principal: MessagePrincipal, input: PostInternalInput): Promise<{ threadId: string; interactionId: string }> { const { content } = await this.templates.render(input.source, input.vars ?? {}, { channel: 'INTERNAL', ...(input.locale ? { locale: input.locale } : {}) }); return this.deposit(principal, input.recipientUserId, content, input.idempotencyKey, 'PORTAL'); @@ -103,6 +109,13 @@ export class MailService { await this.actors.ensureParticipant(res.threadId, senderActor.id); await this.actors.ensureParticipant(res.threadId, recipientActor.id); + // Tag the thread as CRM mail (thread metadata) so it lists separately from Messenger chat. + // ingest() puts req.metadata on the interaction, not the thread, so we set it here directly. + await this.prisma.iiosThread.update({ + where: { id: res.threadId }, + data: { metadata: { source: MailService.SOURCE } as Prisma.InputJsonValue }, + }); + return { threadId: res.threadId, interactionId: res.interactionId }; } } -- 2.52.0