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:
@@ -43,6 +43,11 @@ export class EmailAdapter implements ChannelAdapter {
|
||||
return hmacVerify(rawBody, secret, typeof sig === 'string' ? sig : undefined);
|
||||
}
|
||||
|
||||
externalEventId(payload: unknown): string | undefined {
|
||||
const id = (payload as EmailInboundPayload)?.messageId;
|
||||
return typeof id === 'string' ? id : undefined;
|
||||
}
|
||||
|
||||
normalize(payload: unknown, ctx: NormalizeContext): IngestInteractionRequest {
|
||||
const p = payload as EmailInboundPayload;
|
||||
// A reply's thread root is the first Message-ID in its chain → same IIOS thread as the original.
|
||||
|
||||
@@ -34,6 +34,8 @@ export interface ChannelAdapter {
|
||||
channelType: string;
|
||||
capability: AdapterCapability;
|
||||
verifySignature(rawBody: string, headers: Record<string, string | undefined>, secret: string): boolean;
|
||||
/** Provider-native id used to dedup replays/retries at the inbound edge (e.g. eventId, Message-ID). */
|
||||
externalEventId?(payload: unknown): string | undefined;
|
||||
normalize(payload: unknown, ctx: NormalizeContext): IngestInteractionRequest;
|
||||
fetch?(ref: string): Promise<unknown>;
|
||||
send(cmd: OutboundCommandInput): Promise<SendResult>;
|
||||
|
||||
@@ -37,6 +37,11 @@ export class WebhookAdapter implements ChannelAdapter {
|
||||
return hmacVerify(rawBody, secret, typeof sig === 'string' ? sig : undefined);
|
||||
}
|
||||
|
||||
externalEventId(payload: unknown): string | undefined {
|
||||
const id = (payload as WebhookPayload)?.eventId;
|
||||
return typeof id === 'string' ? id : undefined;
|
||||
}
|
||||
|
||||
normalize(payload: unknown, ctx: NormalizeContext): IngestInteractionRequest {
|
||||
const p = payload as WebhookPayload;
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user