@absolutejs/dispatch-twilio 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # @absolutejs/dispatch-twilio
2
+
3
+ Twilio-backed `SmsAdapter` for
4
+ [@absolutejs/dispatch](https://github.com/absolutejs/dispatch).
5
+
6
+ ## Install
7
+
8
+ ```sh
9
+ bun add @absolutejs/dispatch @absolutejs/dispatch-twilio twilio
10
+ ```
11
+
12
+ ## Usage
13
+
14
+ ```ts
15
+ import twilio from 'twilio';
16
+ import { createDispatcher } from '@absolutejs/dispatch';
17
+ import { createTwilioAdapter } from '@absolutejs/dispatch-twilio';
18
+
19
+ const twilioClient = twilio(
20
+ process.env.TWILIO_ACCOUNT_SID!,
21
+ process.env.TWILIO_AUTH_TOKEN!
22
+ );
23
+
24
+ const dispatcher = createDispatcher({
25
+ sms: createTwilioAdapter({
26
+ client: twilioClient,
27
+ defaultFrom: '+15551234567',
28
+ // OR — instead of a single from number, use a Messaging Service:
29
+ // messagingServiceSid: 'MG...',
30
+ // Optional webhook for status updates (queued → sent → delivered/failed):
31
+ // statusCallback: 'https://hooks.acme.io/twilio',
32
+ }),
33
+ });
34
+
35
+ const result = await dispatcher.sms({
36
+ to: '+12025550100',
37
+ body: 'Your verification code: 482910',
38
+ });
39
+
40
+ console.log(result.id); // Twilio Message SID (SM...)
41
+ ```
42
+
43
+ ## API
44
+
45
+ ```ts
46
+ createTwilioAdapter({
47
+ client, // Required — your twilio(sid, token)
48
+ defaultFrom?, // E.164 origination — required if no service
49
+ messagingServiceSid?, // Use service-based routing instead of `from`
50
+ statusCallback?, // Webhook URL for delivery status updates
51
+ });
52
+ ```
53
+
54
+ ### Sender precedence
55
+
56
+ 1. `message.from` (per-call) — always wins
57
+ 2. `defaultFrom` (adapter option)
58
+ 3. `messagingServiceSid` (adapter option)
59
+
60
+ If none are set, the adapter throws.
61
+
62
+ ## Error mapping
63
+
64
+ Twilio's SDK rejects on most errors (rate limit, invalid number,
65
+ account suspended). The adapter lets the rejection propagate so
66
+ `@absolutejs/dispatch`'s error path runs (failed counter, ERROR span,
67
+ `dispatch.sms.failed` audit).
68
+
69
+ A returned response with `errorCode != null` is the
70
+ bulk-send/queue-rejection case — Twilio doesn't throw but signals
71
+ the error in the response. The adapter throws on this too:
72
+
73
+ ```ts
74
+ { errorCode: 21610, errorMessage: 'Recipient unsubscribed' }
75
+ // → throws "Twilio errorCode 21610: Recipient unsubscribed"
76
+ ```
77
+
78
+ ## License
79
+
80
+ [Apache 2.0](../LICENSE). Tier B substrate-adjacent — rides
81
+ `@absolutejs/dispatch` (BSL Tier A) and `twilio` (MIT).
@@ -0,0 +1,68 @@
1
+ /**
2
+ * @absolutejs/dispatch-twilio — Twilio-backed `SmsAdapter` for
3
+ * `@absolutejs/dispatch`.
4
+ *
5
+ * Takes the user's Twilio client. Maps `SmsMessage` to Twilio's
6
+ * `messages.create` params; surfaces the Message SID as
7
+ * `DispatchResult.id`.
8
+ *
9
+ * Twilio's `messages.create` throws on API errors (rate limiting,
10
+ * invalid number, account suspended, etc), so the adapter's error
11
+ * path is "let Twilio's rejection propagate." `@absolutejs/dispatch`
12
+ * captures it on the `dispatch.sms.send` span, bumps the failed
13
+ * counter, and emits `dispatch.sms.failed`.
14
+ *
15
+ * **Two ways to identify the sender.** Twilio supports either a phone
16
+ * number (`from`) OR a Messaging Service SID (`messagingServiceSid`).
17
+ * Pass `messagingServiceSid` in adapter options to use service-based
18
+ * sending (per-region routing, sender pool, content opt-out
19
+ * management); leave it unset to require `from` per message or
20
+ * `defaultFrom`.
21
+ */
22
+ import type { SmsAdapter } from '@absolutejs/dispatch';
23
+ /**
24
+ * Minimal subset of Twilio's client we use. `client.messages.create`
25
+ * is the canonical send entry. Twilio's typed SDK is large; we don't
26
+ * pull its types in — `TwilioClientLike` describes only the shape
27
+ * we touch.
28
+ */
29
+ export type TwilioClientLike = {
30
+ messages: {
31
+ create: (params: {
32
+ to: string;
33
+ body: string;
34
+ from?: string;
35
+ messagingServiceSid?: string;
36
+ statusCallback?: string;
37
+ }) => Promise<{
38
+ sid?: string;
39
+ status?: string;
40
+ errorCode?: number | null;
41
+ errorMessage?: string | null;
42
+ }>;
43
+ };
44
+ };
45
+ export type CreateTwilioAdapterOptions = {
46
+ /** The Twilio client (`twilio(accountSid, authToken)`). */
47
+ client: TwilioClientLike;
48
+ /**
49
+ * Default origination number (E.164). Required if your messages
50
+ * don't supply `from` AND you're not using a Messaging Service.
51
+ */
52
+ defaultFrom?: string;
53
+ /**
54
+ * Twilio Messaging Service SID. When set, the adapter uses
55
+ * service-based routing instead of a single origination number.
56
+ * Pass this OR `defaultFrom`, not both — per-message `from`
57
+ * overrides this on a per-call basis.
58
+ */
59
+ messagingServiceSid?: string;
60
+ /**
61
+ * Twilio status callback URL — invoked when message status
62
+ * changes (queued → sent → delivered/failed). Wire this to your
63
+ * own webhook to record delivery in audit.
64
+ */
65
+ statusCallback?: string;
66
+ };
67
+ export declare const createTwilioAdapter: (options: CreateTwilioAdapterOptions) => SmsAdapter;
68
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EAAE,UAAU,EAAc,MAAM,sBAAsB,CAAC;AAEnE;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC9B,QAAQ,EAAE;QACT,MAAM,EAAE,CAAC,MAAM,EAAE;YAChB,EAAE,EAAE,MAAM,CAAC;YACX,IAAI,EAAE,MAAM,CAAC;YACb,IAAI,CAAC,EAAE,MAAM,CAAC;YACd,mBAAmB,CAAC,EAAE,MAAM,CAAC;YAC7B,cAAc,CAAC,EAAE,MAAM,CAAC;SACxB,KAAK,OAAO,CAAC;YACb,GAAG,CAAC,EAAE,MAAM,CAAC;YACb,MAAM,CAAC,EAAE,MAAM,CAAC;YAChB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;YAC1B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;SAC7B,CAAC,CAAC;KACH,CAAC;CACF,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACxC,2DAA2D;IAC3D,MAAM,EAAE,gBAAgB,CAAC;IACzB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAC/B,SAAS,0BAA0B,KACjC,UA+CF,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,41 @@
1
+ // @bun
2
+ // src/index.ts
3
+ var createTwilioAdapter = (options) => {
4
+ const { client } = options;
5
+ return {
6
+ name: "twilio",
7
+ send: async (message) => {
8
+ const from = message.from ?? options.defaultFrom;
9
+ if (from === undefined && options.messagingServiceSid === undefined) {
10
+ throw new Error("[dispatch-twilio] no sender configured. " + "Pass `message.from`, `createTwilioAdapter({ defaultFrom })`, or " + "`createTwilioAdapter({ messagingServiceSid })`.");
11
+ }
12
+ const params = {
13
+ body: message.body,
14
+ to: message.to
15
+ };
16
+ if (from !== undefined)
17
+ params.from = from;
18
+ else if (options.messagingServiceSid !== undefined) {
19
+ params.messagingServiceSid = options.messagingServiceSid;
20
+ }
21
+ if (options.statusCallback !== undefined) {
22
+ params.statusCallback = options.statusCallback;
23
+ }
24
+ const response = await client.messages.create(params);
25
+ if (response.errorCode !== null && response.errorCode !== undefined) {
26
+ throw new Error(`[dispatch-twilio] Twilio errorCode ${response.errorCode}: ${response.errorMessage ?? "(no message)"}`);
27
+ }
28
+ return {
29
+ at: Date.now(),
30
+ ...response.sid !== undefined ? { id: response.sid } : {},
31
+ provider: "twilio"
32
+ };
33
+ }
34
+ };
35
+ };
36
+ export {
37
+ createTwilioAdapter
38
+ };
39
+
40
+ //# debugId=92068E85E39BDEE164756E2164756E21
41
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/index.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * @absolutejs/dispatch-twilio — Twilio-backed `SmsAdapter` for\n * `@absolutejs/dispatch`.\n *\n * Takes the user's Twilio client. Maps `SmsMessage` to Twilio's\n * `messages.create` params; surfaces the Message SID as\n * `DispatchResult.id`.\n *\n * Twilio's `messages.create` throws on API errors (rate limiting,\n * invalid number, account suspended, etc), so the adapter's error\n * path is \"let Twilio's rejection propagate.\" `@absolutejs/dispatch`\n * captures it on the `dispatch.sms.send` span, bumps the failed\n * counter, and emits `dispatch.sms.failed`.\n *\n * **Two ways to identify the sender.** Twilio supports either a phone\n * number (`from`) OR a Messaging Service SID (`messagingServiceSid`).\n * Pass `messagingServiceSid` in adapter options to use service-based\n * sending (per-region routing, sender pool, content opt-out\n * management); leave it unset to require `from` per message or\n * `defaultFrom`.\n */\nimport type { SmsAdapter, SmsMessage } from '@absolutejs/dispatch';\n\n/**\n * Minimal subset of Twilio's client we use. `client.messages.create`\n * is the canonical send entry. Twilio's typed SDK is large; we don't\n * pull its types in — `TwilioClientLike` describes only the shape\n * we touch.\n */\nexport type TwilioClientLike = {\n\tmessages: {\n\t\tcreate: (params: {\n\t\t\tto: string;\n\t\t\tbody: string;\n\t\t\tfrom?: string;\n\t\t\tmessagingServiceSid?: string;\n\t\t\tstatusCallback?: string;\n\t\t}) => Promise<{\n\t\t\tsid?: string;\n\t\t\tstatus?: string;\n\t\t\terrorCode?: number | null;\n\t\t\terrorMessage?: string | null;\n\t\t}>;\n\t};\n};\n\nexport type CreateTwilioAdapterOptions = {\n\t/** The Twilio client (`twilio(accountSid, authToken)`). */\n\tclient: TwilioClientLike;\n\t/**\n\t * Default origination number (E.164). Required if your messages\n\t * don't supply `from` AND you're not using a Messaging Service.\n\t */\n\tdefaultFrom?: string;\n\t/**\n\t * Twilio Messaging Service SID. When set, the adapter uses\n\t * service-based routing instead of a single origination number.\n\t * Pass this OR `defaultFrom`, not both — per-message `from`\n\t * overrides this on a per-call basis.\n\t */\n\tmessagingServiceSid?: string;\n\t/**\n\t * Twilio status callback URL — invoked when message status\n\t * changes (queued → sent → delivered/failed). Wire this to your\n\t * own webhook to record delivery in audit.\n\t */\n\tstatusCallback?: string;\n};\n\nexport const createTwilioAdapter = (\n\toptions: CreateTwilioAdapterOptions\n): SmsAdapter => {\n\tconst { client } = options;\n\n\treturn {\n\t\tname: 'twilio',\n\t\tsend: async (message: SmsMessage) => {\n\t\t\tconst from = message.from ?? options.defaultFrom;\n\t\t\tif (\n\t\t\t\tfrom === undefined &&\n\t\t\t\toptions.messagingServiceSid === undefined\n\t\t\t) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t'[dispatch-twilio] no sender configured. ' +\n\t\t\t\t\t\t'Pass `message.from`, `createTwilioAdapter({ defaultFrom })`, or ' +\n\t\t\t\t\t\t'`createTwilioAdapter({ messagingServiceSid })`.'\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst params: Parameters<TwilioClientLike['messages']['create']>[0] = {\n\t\t\t\tbody: message.body,\n\t\t\t\tto: message.to\n\t\t\t};\n\t\t\tif (from !== undefined) params.from = from;\n\t\t\telse if (options.messagingServiceSid !== undefined) {\n\t\t\t\tparams.messagingServiceSid = options.messagingServiceSid;\n\t\t\t}\n\t\t\tif (options.statusCallback !== undefined) {\n\t\t\t\tparams.statusCallback = options.statusCallback;\n\t\t\t}\n\t\t\tconst response = await client.messages.create(params);\n\t\t\t// Twilio's SDK rejects on API errors via thrown errors, so a\n\t\t\t// returned response with `errorCode != null` is the\n\t\t\t// unusual-but-documented case (some bulk-send flows).\n\t\t\tif (\n\t\t\t\tresponse.errorCode !== null &&\n\t\t\t\tresponse.errorCode !== undefined\n\t\t\t) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`[dispatch-twilio] Twilio errorCode ${response.errorCode}: ${response.errorMessage ?? '(no message)'}`\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tat: Date.now(),\n\t\t\t\t...(response.sid !== undefined ? { id: response.sid } : {}),\n\t\t\t\tprovider: 'twilio'\n\t\t\t};\n\t\t}\n\t};\n};\n"
6
+ ],
7
+ "mappings": ";;AAqEO,IAAM,sBAAsB,CAClC,YACgB;AAAA,EAChB,QAAQ,WAAW;AAAA,EAEnB,OAAO;AAAA,IACN,MAAM;AAAA,IACN,MAAM,OAAO,YAAwB;AAAA,MACpC,MAAM,OAAO,QAAQ,QAAQ,QAAQ;AAAA,MACrC,IACC,SAAS,aACT,QAAQ,wBAAwB,WAC/B;AAAA,QACD,MAAM,IAAI,MACT,6CACC,qEACA,iDACF;AAAA,MACD;AAAA,MACA,MAAM,SAAgE;AAAA,QACrE,MAAM,QAAQ;AAAA,QACd,IAAI,QAAQ;AAAA,MACb;AAAA,MACA,IAAI,SAAS;AAAA,QAAW,OAAO,OAAO;AAAA,MACjC,SAAI,QAAQ,wBAAwB,WAAW;AAAA,QACnD,OAAO,sBAAsB,QAAQ;AAAA,MACtC;AAAA,MACA,IAAI,QAAQ,mBAAmB,WAAW;AAAA,QACzC,OAAO,iBAAiB,QAAQ;AAAA,MACjC;AAAA,MACA,MAAM,WAAW,MAAM,OAAO,SAAS,OAAO,MAAM;AAAA,MAIpD,IACC,SAAS,cAAc,QACvB,SAAS,cAAc,WACtB;AAAA,QACD,MAAM,IAAI,MACT,sCAAsC,SAAS,cAAc,SAAS,gBAAgB,gBACvF;AAAA,MACD;AAAA,MACA,OAAO;AAAA,QACN,IAAI,KAAK,IAAI;AAAA,WACT,SAAS,QAAQ,YAAY,EAAE,IAAI,SAAS,IAAI,IAAI,CAAC;AAAA,QACzD,UAAU;AAAA,MACX;AAAA;AAAA,EAEF;AAAA;",
8
+ "debugId": "92068E85E39BDEE164756E2164756E21",
9
+ "names": []
10
+ }
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@absolutejs/dispatch-twilio",
3
+ "version": "0.0.1",
4
+ "description": "Twilio-backed SmsAdapter for @absolutejs/dispatch. Maps SmsMessage to Twilio messages.create; surfaces the Message SID as DispatchResult.id.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/absolutejs/dispatch-adapters.git",
8
+ "directory": "twilio"
9
+ },
10
+ "main": "./dist/index.js",
11
+ "module": "./dist/index.js",
12
+ "types": "./dist/index.d.ts",
13
+ "type": "module",
14
+ "license": "Apache-2.0",
15
+ "author": "Alex Kahn",
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "keywords": [
20
+ "dispatch",
21
+ "sms",
22
+ "twilio",
23
+ "absolutejs",
24
+ "transactional"
25
+ ],
26
+ "scripts": {
27
+ "build": "rm -rf dist && bun build src/index.ts --outdir dist --sourcemap --target=bun --external @absolutejs/dispatch --external twilio && tsc --project tsconfig.build.json",
28
+ "test": "bun test",
29
+ "typecheck": "tsc --noEmit",
30
+ "format": "prettier --write \"./**/*.{ts,json,md}\"",
31
+ "release": "bun run format && bun run build && bun publish"
32
+ },
33
+ "peerDependencies": {
34
+ "@absolutejs/dispatch": ">= 0.0.1",
35
+ "twilio": ">= 5.0.0"
36
+ },
37
+ "devDependencies": {
38
+ "@absolutejs/dispatch": "^0.0.1",
39
+ "@types/bun": "1.3.14",
40
+ "prettier": "3.5.3",
41
+ "typescript": "5.8.3"
42
+ },
43
+ "exports": {
44
+ ".": {
45
+ "types": "./dist/index.d.ts",
46
+ "import": "./dist/index.js",
47
+ "default": "./dist/index.js"
48
+ }
49
+ },
50
+ "files": [
51
+ "dist",
52
+ "README.md"
53
+ ]
54
+ }