Adds adapter-owned externalEventId extraction to the ChannelAdapter contract (WebhookAdapter→eventId, EmailAdapter→Message-ID) and uses it in InboundService, so email replays dedup on Message-ID (previously only payload.eventId worked). Adds an email-dedup spec + smoke-email.mjs proving inbound → normalized, a reply joins the same email thread, bad signature rejected, and outbound EMAIL SENT. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -113,6 +113,17 @@ describe('Email adapter — inbound + threading', () => {
|
||||
expect(await prisma.iiosThread.count()).toBe(1); // one email conversation
|
||||
});
|
||||
|
||||
it('redelivered email (same Message-ID) → one raw event, one interaction (dedup)', async () => {
|
||||
const svc = inbound();
|
||||
const { body, headers } = signedFixture(emailInboundFixture, SECRET);
|
||||
await svc.receive('EMAIL', body, headers);
|
||||
const second = await svc.receive('EMAIL', body, headers);
|
||||
expect(second.deduped).toBe(true);
|
||||
await normalizeAll();
|
||||
expect(await prisma.iiosInboundRawEvent.count()).toBe(1);
|
||||
expect(await prisma.iiosInteraction.count()).toBe(1);
|
||||
});
|
||||
|
||||
it('bad signature on EMAIL → REJECTED, nothing ingested (fail-closed)', async () => {
|
||||
const { body } = signedFixture(emailInboundFixture, SECRET);
|
||||
await expect(inbound().receive('EMAIL', body, { 'x-iios-signature': 'sha256=bad' })).rejects.toThrow();
|
||||
|
||||
Reference in New Issue
Block a user