feat(mail): tag mail threads source=crm-mail (list separately from chat) #3

Merged
maaz519 merged 1 commits from feat/s3-storage into dev 2026-07-18 11:12:36 +00:00
2 changed files with 15 additions and 1 deletions
Showing only changes of commit 40c98522dc - Show all commits
@@ -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);
@@ -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 };
}
}