Skip to content

Commit a645f3e

Browse files
committed
fix(sendblue): address review — status-aware webhook dedup, shared routing map, uniform output casing, api-key authType
1 parent e945547 commit a645f3e

5 files changed

Lines changed: 19 additions & 18 deletions

File tree

apps/docs/content/docs/en/triggers/sendblue.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Trigger when an inbound iMessage or SMS is received in Sendblue
2222

2323
| Parameter | Type | Description |
2424
| --------- | ---- | ----------- |
25-
| `accountEmail` | string | Email of the Sendblue account |
25+
| `account_email` | string | Email of the Sendblue account |
2626
| `content` | string | Message text content |
2727
| `media_url` | string | CDN link to attached media, if any |
2828
| `is_outbound` | boolean | True for outbound messages, false for inbound |
@@ -62,7 +62,7 @@ Trigger when an outbound message status changes (SENT, DELIVERED, ERROR) in Send
6262

6363
| Parameter | Type | Description |
6464
| --------- | ---- | ----------- |
65-
| `accountEmail` | string | Email of the Sendblue account |
65+
| `account_email` | string | Email of the Sendblue account |
6666
| `content` | string | Message text content |
6767
| `media_url` | string | CDN link to attached media, if any |
6868
| `is_outbound` | boolean | True for outbound messages, false for inbound |

apps/sim/lib/integrations/integrations.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12744,7 +12744,7 @@
1274412744
}
1274512745
],
1274612746
"triggerCount": 2,
12747-
"authType": "none",
12747+
"authType": "api-key",
1274812748
"category": "tools",
1274912749
"integrationType": "communication",
1275012750
"tags": ["messaging", "automation", "webhooks"]

apps/sim/lib/webhooks/providers/sendblue.ts

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,22 @@ import type {
66
FormatInputResult,
77
WebhookProviderHandler,
88
} from '@/lib/webhooks/providers/types'
9+
import { SENDBLUE_TRIGGER_IS_OUTBOUND } from '@/triggers/sendblue/utils'
910

1011
const logger = createLogger('WebhookProvider:Sendblue')
1112

12-
/**
13-
* Maps Sendblue trigger IDs to the expected value of the payload `is_outbound`
14-
* flag. Inbound messages are routed to the "message received" trigger and
15-
* outbound status callbacks to the "message status updated" trigger.
16-
*/
17-
const TRIGGER_IS_OUTBOUND: Record<string, boolean> = {
18-
sendblue_message_received: false,
19-
sendblue_message_status_updated: true,
20-
}
21-
2213
export const sendblueHandler: WebhookProviderHandler = {
2314
matchEvent({ body, webhook, requestId }: EventMatchContext): boolean {
2415
const providerConfig = getProviderConfig(webhook)
2516
const triggerId = providerConfig.triggerId as string | undefined
26-
if (!triggerId || !(triggerId in TRIGGER_IS_OUTBOUND)) return true
17+
if (!triggerId || !(triggerId in SENDBLUE_TRIGGER_IS_OUTBOUND)) return true
2718

2819
if (!isRecord(body)) {
2920
logger.warn(`[${requestId}] Sendblue webhook payload was not an object`)
3021
return false
3122
}
3223

33-
const expected = TRIGGER_IS_OUTBOUND[triggerId]
24+
const expected = SENDBLUE_TRIGGER_IS_OUTBOUND[triggerId]
3425
const isOutbound = body.is_outbound === true
3526
if (isOutbound !== expected) {
3627
logger.info(`[${requestId}] Sendblue event did not match trigger`, { triggerId, isOutbound })
@@ -43,14 +34,19 @@ export const sendblueHandler: WebhookProviderHandler = {
4334
extractIdempotencyId(body: unknown): string | null {
4435
if (!isRecord(body)) return null
4536
const handle = body.message_handle
46-
return typeof handle === 'string' && handle.length > 0 ? handle : null
37+
if (typeof handle !== 'string' || handle.length === 0) return null
38+
// A single outbound message emits multiple status callbacks (e.g. SENT then
39+
// DELIVERED) that share one message_handle, so the status is part of the key
40+
// to keep distinct transitions from being deduped as retries.
41+
const status = typeof body.status === 'string' && body.status.length > 0 ? body.status : null
42+
return status ? `${handle}:${status}` : handle
4743
},
4844

4945
async formatInput({ body }: FormatInputContext): Promise<FormatInputResult> {
5046
const b = isRecord(body) ? body : {}
5147
return {
5248
input: {
53-
accountEmail: b.accountEmail ?? b.account_email ?? null,
49+
account_email: b.accountEmail ?? b.account_email ?? null,
5450
content: b.content ?? null,
5551
media_url: b.media_url ?? null,
5652
is_outbound: b.is_outbound ?? null,

apps/sim/triggers/sendblue/utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { TriggerOutput } from '@/triggers/types'
33
/**
44
* Maps Sendblue trigger IDs to the expected value of the webhook payload's
55
* `is_outbound` flag, used to route inbound vs. outbound status events.
6+
* Imported by the webhook provider handler so routing lives in one place.
67
*/
78
export const SENDBLUE_TRIGGER_IS_OUTBOUND: Record<string, boolean> = {
89
sendblue_message_received: false,
@@ -38,7 +39,7 @@ export function sendblueSetupInstructions(eventType: string): string {
3839
*/
3940
export function buildSendblueOutputs(): Record<string, TriggerOutput> {
4041
return {
41-
accountEmail: { type: 'string', description: 'Email of the Sendblue account' },
42+
account_email: { type: 'string', description: 'Email of the Sendblue account' },
4243
content: { type: 'string', description: 'Message text content' },
4344
media_url: { type: 'string', description: 'CDN link to attached media, if any' },
4445
is_outbound: { type: 'boolean', description: 'True for outbound messages, false for inbound' },

scripts/generate-docs.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,10 @@ async function buildToolDescriptionMap(): Promise<ToolMaps> {
535535
* 'api-key' if it uses a plain API key field, or 'none' otherwise.
536536
*/
537537
function extractAuthType(blockContent: string): 'oauth' | 'api-key' | 'none' {
538+
// Prefer the authoritative `authMode` declaration when present.
539+
if (/authMode\s*:\s*AuthMode\.OAuth\b/.test(blockContent)) return 'oauth'
540+
if (/authMode\s*:\s*AuthMode\.(?:ApiKey|BotToken)\b/.test(blockContent)) return 'api-key'
541+
// Fall back to credential subBlock heuristics for blocks without authMode.
538542
if (/type\s*:\s*['"]oauth-input['"]/.test(blockContent)) return 'oauth'
539543
if (/\bid\s*:\s*['"](?:apiKey|api_key|accessToken)['"]/.test(blockContent)) return 'api-key'
540544
return 'none'

0 commit comments

Comments
 (0)