@drawbridge/drawbridge-utils 0.0.133 → 0.0.134

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/dist/twilio.cjs CHANGED
@@ -107,28 +107,37 @@ var twilio = {
107
107
  // NUMBER LIFECYCLE, same transport discipline as sms above. Endpoints and
108
108
  // parameter names are Twilio's own, from the AvailablePhoneNumbers and
109
109
  // IncomingPhoneNumbers resource docs:
110
- // GET /2010-04-01/Accounts/{AccountSid}/AvailablePhoneNumbers/{CountryCode}/Local.json
111
- // query: SmsEnabled, AreaCode (US/CA only), PageSize (1-1000)
110
+ // GET /2010-04-01/Accounts/{AccountSid}/AvailablePhoneNumbers/{CountryCode}/TollFree.json
111
+ // query: SmsEnabled, Contains, PageSize (1-1000)
112
112
  // POST /2010-04-01/Accounts/{AccountSid}/IncomingPhoneNumbers.json
113
113
  // body: PhoneNumber (E.164), SmsUrl, SmsMethod
114
114
  // DELETE /2010-04-01/Accounts/{AccountSid}/IncomingPhoneNumbers/{Sid}.json
115
115
  // Basic auth throughout, like Messages.
116
- searchNumbers: async ({ accountSid, areaCode, authToken, contains, country = "US", cursor, limit = 10, request: request2 = request }) => {
116
+ //
117
+ // TOLL-FREE, not Local, and that is a carrier-registration decision rather
118
+ // than an inventory one. A US local number may not send A2P SMS without full
119
+ // 10DLC brand-and-campaign registration; a toll-free number needs only
120
+ // Toll-Free Verification — one API submission per number (see
121
+ // submitVerification below), no TCR brand, no campaign fees. Until a
122
+ // verification is approved, Twilio blocks the number's US-bound messages
123
+ // outright — which is why a purchased number starts `pending` and the send
124
+ // path refuses anything but `verified`.
125
+ searchNumbers: async ({ accountSid, authToken, contains, country = "US", cursor, limit = 10, request: request2 = request }) => {
117
126
  if (!accountSid || !authToken) throw new Error("Twilio credentials missing \u2014 accountSid, authToken");
118
127
  const page = Number(cursor) || 0;
119
128
  const result = await request2({
120
129
  method: "GET",
121
- url: "https://api.twilio.com/2010-04-01/Accounts/" + accountSid + "/AvailablePhoneNumbers/" + country + "/Local.json",
130
+ url: "https://api.twilio.com/2010-04-01/Accounts/" + accountSid + "/AvailablePhoneNumbers/" + country + "/TollFree.json",
122
131
  headers: {
123
132
  "Authorization": "Basic " + Buffer.from(accountSid + ":" + authToken).toString("base64")
124
133
  },
125
134
  query: {
126
135
  SmsEnabled: true,
127
136
  PageSize: limit,
128
- ...areaCode && { AreaCode: areaCode },
129
137
  // `Contains` is the vendor's matching-pattern filter — digits
130
- // match anywhere in the number, so one search box serves "my
131
- // area code" and "ends in 7777" alike.
138
+ // match anywhere in the number, so one search box serves "an 888
139
+ // prefix" and "ends in 7777" alike. (AreaCode is a Local-only
140
+ // filter and went with Local.json.)
132
141
  ...contains && { Contains: contains },
133
142
  ...page && { Page: page }
134
143
  }
@@ -175,6 +184,84 @@ var twilio = {
175
184
  "Authorization": "Basic " + Buffer.from(accountSid + ":" + authToken).toString("base64")
176
185
  }
177
186
  });
187
+ },
188
+ // TOLL-FREE VERIFICATION — the carrier registration that lets a purchased
189
+ // number actually deliver. From the Tollfree Verification resource docs
190
+ // (messaging.twilio.com, docs/messaging/api/tollfree-verification-resource):
191
+ // POST /v1/Tollfree/Verifications
192
+ // required: BusinessName, BusinessWebsite, NotificationEmail,
193
+ // UseCaseCategories (array), UseCaseSummary,
194
+ // ProductionMessageSample, OptInImageUrls (array), OptInType,
195
+ // MessageVolume, TollfreePhoneNumberSid (PN…)
196
+ // GET /v1/Tollfree/Verifications/{Sid}
197
+ // status: PENDING_REVIEW | IN_REVIEW | TWILIO_APPROVED |
198
+ // TWILIO_REJECTED, with rejection_reason beside a rejection.
199
+ // Basic auth, form-encoded, like everything above. There is no status
200
+ // callback on this resource — the caller polls the fetch.
201
+ //
202
+ // THE END BUSINESS'S DETAILS, never the platform's. Twilio rejects ISV
203
+ // submissions carrying the ISV's own identity (Toll-Free Verification for
204
+ // ISVs, support.twilio.com) — so businessName, website and email here are
205
+ // the merchant's, collected at purchase.
206
+ //
207
+ // The use-case fields are PLATFORM facts and live here beside the message
208
+ // composition for the same reason it does: how Drawbridge collects opt-in
209
+ // (its own hosted entry forms) and what its messages look like are
210
+ // properties of sending through Drawbridge, not of any one merchant.
211
+ submitVerification: async ({
212
+ accountSid,
213
+ authToken,
214
+ businessName,
215
+ email,
216
+ numberSid,
217
+ optInImage,
218
+ request: request2 = request,
219
+ volume,
220
+ website
221
+ }) => {
222
+ const missing = Object.entries({ accountSid, authToken, businessName, email, numberSid, optInImage, website }).filter(([, value]) => !value).map(([name]) => name);
223
+ if (missing.length) throw new Error("Toll-free verification submission missing \u2014 " + missing.join(", "));
224
+ const body = new URLSearchParams();
225
+ body.append("BusinessName", businessName);
226
+ body.append("BusinessWebsite", website);
227
+ body.append("NotificationEmail", email);
228
+ body.append("UseCaseCategories", "MARKETING");
229
+ body.append("UseCaseSummary", "Prize-draw entry confirmations, winner notifications and campaign updates to consumers who opted in on this business's Drawbridge-hosted entry form.");
230
+ body.append("ProductionMessageSample", businessName + "\nThanks for entering! We'll text you here about your entry.\nReply STOP to opt out");
231
+ body.append("OptInImageUrls", optInImage);
232
+ body.append("OptInType", "WEB_FORM");
233
+ body.append("MessageVolume", volume || "1,000");
234
+ body.append("TollfreePhoneNumberSid", numberSid);
235
+ const result = await request2({
236
+ method: "POST",
237
+ type: "form",
238
+ url: "https://messaging.twilio.com/v1/Tollfree/Verifications",
239
+ headers: {
240
+ "Authorization": "Basic " + Buffer.from(accountSid + ":" + authToken).toString("base64")
241
+ },
242
+ body
243
+ });
244
+ return {
245
+ sid: result == null ? void 0 : result.sid,
246
+ status: result == null ? void 0 : result.status
247
+ };
248
+ },
249
+ fetchVerification: async ({ accountSid, authToken, request: request2 = request, sid }) => {
250
+ if (!accountSid || !authToken) throw new Error("Twilio credentials missing \u2014 accountSid, authToken");
251
+ if (!sid) throw new Error("Twilio verification fetch needs the verification sid");
252
+ const result = await request2({
253
+ method: "GET",
254
+ url: "https://messaging.twilio.com/v1/Tollfree/Verifications/" + sid,
255
+ headers: {
256
+ "Authorization": "Basic " + Buffer.from(accountSid + ":" + authToken).toString("base64")
257
+ }
258
+ });
259
+ return {
260
+ // TWILIO_APPROVED → verified, TWILIO_REJECTED → rejected, both
261
+ // review states → pending.
262
+ outcome: (result == null ? void 0 : result.status) === "TWILIO_APPROVED" ? "approved" : (result == null ? void 0 : result.status) === "TWILIO_REJECTED" ? "rejected" : "pending",
263
+ reason: (result == null ? void 0 : result.rejection_reason) || null
264
+ };
178
265
  }
179
266
  };
180
267
  // Annotate the CommonJS export names for ESM import in node:
package/dist/twilio.d.cts CHANGED
@@ -70,14 +70,23 @@ const twilio = {
70
70
  // NUMBER LIFECYCLE, same transport discipline as sms above. Endpoints and
71
71
  // parameter names are Twilio's own, from the AvailablePhoneNumbers and
72
72
  // IncomingPhoneNumbers resource docs:
73
- // GET /2010-04-01/Accounts/{AccountSid}/AvailablePhoneNumbers/{CountryCode}/Local.json
74
- // query: SmsEnabled, AreaCode (US/CA only), PageSize (1-1000)
73
+ // GET /2010-04-01/Accounts/{AccountSid}/AvailablePhoneNumbers/{CountryCode}/TollFree.json
74
+ // query: SmsEnabled, Contains, PageSize (1-1000)
75
75
  // POST /2010-04-01/Accounts/{AccountSid}/IncomingPhoneNumbers.json
76
76
  // body: PhoneNumber (E.164), SmsUrl, SmsMethod
77
77
  // DELETE /2010-04-01/Accounts/{AccountSid}/IncomingPhoneNumbers/{Sid}.json
78
78
  // Basic auth throughout, like Messages.
79
+ //
80
+ // TOLL-FREE, not Local, and that is a carrier-registration decision rather
81
+ // than an inventory one. A US local number may not send A2P SMS without full
82
+ // 10DLC brand-and-campaign registration; a toll-free number needs only
83
+ // Toll-Free Verification — one API submission per number (see
84
+ // submitVerification below), no TCR brand, no campaign fees. Until a
85
+ // verification is approved, Twilio blocks the number's US-bound messages
86
+ // outright — which is why a purchased number starts `pending` and the send
87
+ // path refuses anything but `verified`.
79
88
 
80
- searchNumbers : async ({ accountSid, areaCode, authToken, contains, country = 'US', cursor, limit = 10, request: request$1 = request }) => {
89
+ searchNumbers : async ({ accountSid, authToken, contains, country = 'US', cursor, limit = 10, request: request$1 = request }) => {
81
90
 
82
91
  if( ! accountSid || ! authToken ) throw new Error( 'Twilio credentials missing — accountSid, authToken' );
83
92
 
@@ -93,17 +102,17 @@ const twilio = {
93
102
 
94
103
  const result = await request$1({
95
104
  method : 'GET',
96
- url : 'https://api.twilio.com/2010-04-01/Accounts/' + accountSid + '/AvailablePhoneNumbers/' + country + '/Local.json',
105
+ url : 'https://api.twilio.com/2010-04-01/Accounts/' + accountSid + '/AvailablePhoneNumbers/' + country + '/TollFree.json',
97
106
  headers : {
98
107
  'Authorization' : 'Basic ' + Buffer.from( accountSid + ':' + authToken ).toString( 'base64' )
99
108
  },
100
109
  query : {
101
110
  SmsEnabled : true,
102
111
  PageSize : limit,
103
- ...( areaCode && { AreaCode : areaCode } ),
104
112
  // `Contains` is the vendor's matching-pattern filter — digits
105
- // match anywhere in the number, so one search box serves "my
106
- // area code" and "ends in 7777" alike.
113
+ // match anywhere in the number, so one search box serves "an 888
114
+ // prefix" and "ends in 7777" alike. (AreaCode is a Local-only
115
+ // filter and went with Local.json.)
107
116
  ...( contains && { Contains : contains } ),
108
117
  ...( page && { Page : page } )
109
118
  }
@@ -163,6 +172,111 @@ const twilio = {
163
172
  }
164
173
  });
165
174
 
175
+ },
176
+
177
+ // TOLL-FREE VERIFICATION — the carrier registration that lets a purchased
178
+ // number actually deliver. From the Tollfree Verification resource docs
179
+ // (messaging.twilio.com, docs/messaging/api/tollfree-verification-resource):
180
+ // POST /v1/Tollfree/Verifications
181
+ // required: BusinessName, BusinessWebsite, NotificationEmail,
182
+ // UseCaseCategories (array), UseCaseSummary,
183
+ // ProductionMessageSample, OptInImageUrls (array), OptInType,
184
+ // MessageVolume, TollfreePhoneNumberSid (PN…)
185
+ // GET /v1/Tollfree/Verifications/{Sid}
186
+ // status: PENDING_REVIEW | IN_REVIEW | TWILIO_APPROVED |
187
+ // TWILIO_REJECTED, with rejection_reason beside a rejection.
188
+ // Basic auth, form-encoded, like everything above. There is no status
189
+ // callback on this resource — the caller polls the fetch.
190
+ //
191
+ // THE END BUSINESS'S DETAILS, never the platform's. Twilio rejects ISV
192
+ // submissions carrying the ISV's own identity (Toll-Free Verification for
193
+ // ISVs, support.twilio.com) — so businessName, website and email here are
194
+ // the merchant's, collected at purchase.
195
+ //
196
+ // The use-case fields are PLATFORM facts and live here beside the message
197
+ // composition for the same reason it does: how Drawbridge collects opt-in
198
+ // (its own hosted entry forms) and what its messages look like are
199
+ // properties of sending through Drawbridge, not of any one merchant.
200
+ submitVerification : async ({
201
+ accountSid,
202
+ authToken,
203
+ businessName,
204
+ email,
205
+ numberSid,
206
+ optInImage,
207
+ request: request$1 = request,
208
+ volume,
209
+ website
210
+ }) => {
211
+
212
+ const missing = Object.entries({ accountSid, authToken, businessName, email, numberSid, optInImage, website })
213
+ .filter( ( [ , value ] ) => ! value )
214
+ .map( ( [ name ] ) => name );
215
+
216
+ if( missing.length ) throw new Error( 'Toll-free verification submission missing — ' + missing.join( ', ' ) );
217
+
218
+ // URLSearchParams rather than an object: UseCaseCategories and
219
+ // OptInImageUrls are array parameters, which Twilio takes as REPEATED
220
+ // form keys — an object body would comma-join them into one value.
221
+ const body = new URLSearchParams();
222
+
223
+ body.append( 'BusinessName', businessName );
224
+ body.append( 'BusinessWebsite', website );
225
+ body.append( 'NotificationEmail', email );
226
+ body.append( 'UseCaseCategories', 'MARKETING' );
227
+ body.append( 'UseCaseSummary', 'Prize-draw entry confirmations, winner notifications and campaign updates to consumers who opted in on this business\'s Drawbridge-hosted entry form.' );
228
+ // Mirrors the sms() composition above — newline-separated, brand first,
229
+ // opt-out line appended — so the reviewed sample is the sent shape.
230
+ body.append( 'ProductionMessageSample', businessName + '\nThanks for entering! We\'ll text you here about your entry.\nReply STOP to opt out' );
231
+ body.append( 'OptInImageUrls', optInImage );
232
+ // The entry form is a web form; the phone field is beside the consent
233
+ // language on the merchant's campaign page.
234
+ body.append( 'OptInType', 'WEB_FORM' );
235
+ body.append( 'MessageVolume', volume || '1,000' );
236
+ body.append( 'TollfreePhoneNumberSid', numberSid );
237
+
238
+ const result = await request$1({
239
+ method : 'POST',
240
+ type : 'form',
241
+ url : 'https://messaging.twilio.com/v1/Tollfree/Verifications',
242
+ headers : {
243
+ 'Authorization' : 'Basic ' + Buffer.from( accountSid + ':' + authToken ).toString( 'base64' )
244
+ },
245
+ body
246
+ });
247
+
248
+ return {
249
+ sid : result?.sid,
250
+ status : result?.status
251
+ };
252
+
253
+ },
254
+
255
+ fetchVerification : async ({ accountSid, authToken, request: request$1 = request, sid }) => {
256
+
257
+ if( ! accountSid || ! authToken ) throw new Error( 'Twilio credentials missing — accountSid, authToken' );
258
+ if( ! sid ) throw new Error( 'Twilio verification fetch needs the verification sid' );
259
+
260
+ const result = await request$1({
261
+ method : 'GET',
262
+ url : 'https://messaging.twilio.com/v1/Tollfree/Verifications/' + sid,
263
+ headers : {
264
+ 'Authorization' : 'Basic ' + Buffer.from( accountSid + ':' + authToken ).toString( 'base64' )
265
+ }
266
+ });
267
+
268
+ // The caller-facing shape is ours; the vendor's vocabulary stays here.
269
+ return {
270
+ // TWILIO_APPROVED → verified, TWILIO_REJECTED → rejected, both
271
+ // review states → pending.
272
+ outcome : result?.status === 'TWILIO_APPROVED'
273
+ ? 'approved'
274
+ : result?.status === 'TWILIO_REJECTED'
275
+ ? 'rejected'
276
+ : 'pending',
277
+ reason : result?.rejection_reason || null
278
+ };
279
+
166
280
  }
167
281
 
168
282
  };
package/dist/twilio.d.ts CHANGED
@@ -70,14 +70,23 @@ const twilio = {
70
70
  // NUMBER LIFECYCLE, same transport discipline as sms above. Endpoints and
71
71
  // parameter names are Twilio's own, from the AvailablePhoneNumbers and
72
72
  // IncomingPhoneNumbers resource docs:
73
- // GET /2010-04-01/Accounts/{AccountSid}/AvailablePhoneNumbers/{CountryCode}/Local.json
74
- // query: SmsEnabled, AreaCode (US/CA only), PageSize (1-1000)
73
+ // GET /2010-04-01/Accounts/{AccountSid}/AvailablePhoneNumbers/{CountryCode}/TollFree.json
74
+ // query: SmsEnabled, Contains, PageSize (1-1000)
75
75
  // POST /2010-04-01/Accounts/{AccountSid}/IncomingPhoneNumbers.json
76
76
  // body: PhoneNumber (E.164), SmsUrl, SmsMethod
77
77
  // DELETE /2010-04-01/Accounts/{AccountSid}/IncomingPhoneNumbers/{Sid}.json
78
78
  // Basic auth throughout, like Messages.
79
+ //
80
+ // TOLL-FREE, not Local, and that is a carrier-registration decision rather
81
+ // than an inventory one. A US local number may not send A2P SMS without full
82
+ // 10DLC brand-and-campaign registration; a toll-free number needs only
83
+ // Toll-Free Verification — one API submission per number (see
84
+ // submitVerification below), no TCR brand, no campaign fees. Until a
85
+ // verification is approved, Twilio blocks the number's US-bound messages
86
+ // outright — which is why a purchased number starts `pending` and the send
87
+ // path refuses anything but `verified`.
79
88
 
80
- searchNumbers : async ({ accountSid, areaCode, authToken, contains, country = 'US', cursor, limit = 10, request: request$1 = request }) => {
89
+ searchNumbers : async ({ accountSid, authToken, contains, country = 'US', cursor, limit = 10, request: request$1 = request }) => {
81
90
 
82
91
  if( ! accountSid || ! authToken ) throw new Error( 'Twilio credentials missing — accountSid, authToken' );
83
92
 
@@ -93,17 +102,17 @@ const twilio = {
93
102
 
94
103
  const result = await request$1({
95
104
  method : 'GET',
96
- url : 'https://api.twilio.com/2010-04-01/Accounts/' + accountSid + '/AvailablePhoneNumbers/' + country + '/Local.json',
105
+ url : 'https://api.twilio.com/2010-04-01/Accounts/' + accountSid + '/AvailablePhoneNumbers/' + country + '/TollFree.json',
97
106
  headers : {
98
107
  'Authorization' : 'Basic ' + Buffer.from( accountSid + ':' + authToken ).toString( 'base64' )
99
108
  },
100
109
  query : {
101
110
  SmsEnabled : true,
102
111
  PageSize : limit,
103
- ...( areaCode && { AreaCode : areaCode } ),
104
112
  // `Contains` is the vendor's matching-pattern filter — digits
105
- // match anywhere in the number, so one search box serves "my
106
- // area code" and "ends in 7777" alike.
113
+ // match anywhere in the number, so one search box serves "an 888
114
+ // prefix" and "ends in 7777" alike. (AreaCode is a Local-only
115
+ // filter and went with Local.json.)
107
116
  ...( contains && { Contains : contains } ),
108
117
  ...( page && { Page : page } )
109
118
  }
@@ -163,6 +172,111 @@ const twilio = {
163
172
  }
164
173
  });
165
174
 
175
+ },
176
+
177
+ // TOLL-FREE VERIFICATION — the carrier registration that lets a purchased
178
+ // number actually deliver. From the Tollfree Verification resource docs
179
+ // (messaging.twilio.com, docs/messaging/api/tollfree-verification-resource):
180
+ // POST /v1/Tollfree/Verifications
181
+ // required: BusinessName, BusinessWebsite, NotificationEmail,
182
+ // UseCaseCategories (array), UseCaseSummary,
183
+ // ProductionMessageSample, OptInImageUrls (array), OptInType,
184
+ // MessageVolume, TollfreePhoneNumberSid (PN…)
185
+ // GET /v1/Tollfree/Verifications/{Sid}
186
+ // status: PENDING_REVIEW | IN_REVIEW | TWILIO_APPROVED |
187
+ // TWILIO_REJECTED, with rejection_reason beside a rejection.
188
+ // Basic auth, form-encoded, like everything above. There is no status
189
+ // callback on this resource — the caller polls the fetch.
190
+ //
191
+ // THE END BUSINESS'S DETAILS, never the platform's. Twilio rejects ISV
192
+ // submissions carrying the ISV's own identity (Toll-Free Verification for
193
+ // ISVs, support.twilio.com) — so businessName, website and email here are
194
+ // the merchant's, collected at purchase.
195
+ //
196
+ // The use-case fields are PLATFORM facts and live here beside the message
197
+ // composition for the same reason it does: how Drawbridge collects opt-in
198
+ // (its own hosted entry forms) and what its messages look like are
199
+ // properties of sending through Drawbridge, not of any one merchant.
200
+ submitVerification : async ({
201
+ accountSid,
202
+ authToken,
203
+ businessName,
204
+ email,
205
+ numberSid,
206
+ optInImage,
207
+ request: request$1 = request,
208
+ volume,
209
+ website
210
+ }) => {
211
+
212
+ const missing = Object.entries({ accountSid, authToken, businessName, email, numberSid, optInImage, website })
213
+ .filter( ( [ , value ] ) => ! value )
214
+ .map( ( [ name ] ) => name );
215
+
216
+ if( missing.length ) throw new Error( 'Toll-free verification submission missing — ' + missing.join( ', ' ) );
217
+
218
+ // URLSearchParams rather than an object: UseCaseCategories and
219
+ // OptInImageUrls are array parameters, which Twilio takes as REPEATED
220
+ // form keys — an object body would comma-join them into one value.
221
+ const body = new URLSearchParams();
222
+
223
+ body.append( 'BusinessName', businessName );
224
+ body.append( 'BusinessWebsite', website );
225
+ body.append( 'NotificationEmail', email );
226
+ body.append( 'UseCaseCategories', 'MARKETING' );
227
+ body.append( 'UseCaseSummary', 'Prize-draw entry confirmations, winner notifications and campaign updates to consumers who opted in on this business\'s Drawbridge-hosted entry form.' );
228
+ // Mirrors the sms() composition above — newline-separated, brand first,
229
+ // opt-out line appended — so the reviewed sample is the sent shape.
230
+ body.append( 'ProductionMessageSample', businessName + '\nThanks for entering! We\'ll text you here about your entry.\nReply STOP to opt out' );
231
+ body.append( 'OptInImageUrls', optInImage );
232
+ // The entry form is a web form; the phone field is beside the consent
233
+ // language on the merchant's campaign page.
234
+ body.append( 'OptInType', 'WEB_FORM' );
235
+ body.append( 'MessageVolume', volume || '1,000' );
236
+ body.append( 'TollfreePhoneNumberSid', numberSid );
237
+
238
+ const result = await request$1({
239
+ method : 'POST',
240
+ type : 'form',
241
+ url : 'https://messaging.twilio.com/v1/Tollfree/Verifications',
242
+ headers : {
243
+ 'Authorization' : 'Basic ' + Buffer.from( accountSid + ':' + authToken ).toString( 'base64' )
244
+ },
245
+ body
246
+ });
247
+
248
+ return {
249
+ sid : result?.sid,
250
+ status : result?.status
251
+ };
252
+
253
+ },
254
+
255
+ fetchVerification : async ({ accountSid, authToken, request: request$1 = request, sid }) => {
256
+
257
+ if( ! accountSid || ! authToken ) throw new Error( 'Twilio credentials missing — accountSid, authToken' );
258
+ if( ! sid ) throw new Error( 'Twilio verification fetch needs the verification sid' );
259
+
260
+ const result = await request$1({
261
+ method : 'GET',
262
+ url : 'https://messaging.twilio.com/v1/Tollfree/Verifications/' + sid,
263
+ headers : {
264
+ 'Authorization' : 'Basic ' + Buffer.from( accountSid + ':' + authToken ).toString( 'base64' )
265
+ }
266
+ });
267
+
268
+ // The caller-facing shape is ours; the vendor's vocabulary stays here.
269
+ return {
270
+ // TWILIO_APPROVED → verified, TWILIO_REJECTED → rejected, both
271
+ // review states → pending.
272
+ outcome : result?.status === 'TWILIO_APPROVED'
273
+ ? 'approved'
274
+ : result?.status === 'TWILIO_REJECTED'
275
+ ? 'rejected'
276
+ : 'pending',
277
+ reason : result?.rejection_reason || null
278
+ };
279
+
166
280
  }
167
281
 
168
282
  };
package/dist/twilio.js CHANGED
@@ -82,28 +82,37 @@ var twilio = {
82
82
  // NUMBER LIFECYCLE, same transport discipline as sms above. Endpoints and
83
83
  // parameter names are Twilio's own, from the AvailablePhoneNumbers and
84
84
  // IncomingPhoneNumbers resource docs:
85
- // GET /2010-04-01/Accounts/{AccountSid}/AvailablePhoneNumbers/{CountryCode}/Local.json
86
- // query: SmsEnabled, AreaCode (US/CA only), PageSize (1-1000)
85
+ // GET /2010-04-01/Accounts/{AccountSid}/AvailablePhoneNumbers/{CountryCode}/TollFree.json
86
+ // query: SmsEnabled, Contains, PageSize (1-1000)
87
87
  // POST /2010-04-01/Accounts/{AccountSid}/IncomingPhoneNumbers.json
88
88
  // body: PhoneNumber (E.164), SmsUrl, SmsMethod
89
89
  // DELETE /2010-04-01/Accounts/{AccountSid}/IncomingPhoneNumbers/{Sid}.json
90
90
  // Basic auth throughout, like Messages.
91
- searchNumbers: async ({ accountSid, areaCode, authToken, contains, country = "US", cursor, limit = 10, request: request2 = request }) => {
91
+ //
92
+ // TOLL-FREE, not Local, and that is a carrier-registration decision rather
93
+ // than an inventory one. A US local number may not send A2P SMS without full
94
+ // 10DLC brand-and-campaign registration; a toll-free number needs only
95
+ // Toll-Free Verification — one API submission per number (see
96
+ // submitVerification below), no TCR brand, no campaign fees. Until a
97
+ // verification is approved, Twilio blocks the number's US-bound messages
98
+ // outright — which is why a purchased number starts `pending` and the send
99
+ // path refuses anything but `verified`.
100
+ searchNumbers: async ({ accountSid, authToken, contains, country = "US", cursor, limit = 10, request: request2 = request }) => {
92
101
  if (!accountSid || !authToken) throw new Error("Twilio credentials missing \u2014 accountSid, authToken");
93
102
  const page = Number(cursor) || 0;
94
103
  const result = await request2({
95
104
  method: "GET",
96
- url: "https://api.twilio.com/2010-04-01/Accounts/" + accountSid + "/AvailablePhoneNumbers/" + country + "/Local.json",
105
+ url: "https://api.twilio.com/2010-04-01/Accounts/" + accountSid + "/AvailablePhoneNumbers/" + country + "/TollFree.json",
97
106
  headers: {
98
107
  "Authorization": "Basic " + Buffer.from(accountSid + ":" + authToken).toString("base64")
99
108
  },
100
109
  query: {
101
110
  SmsEnabled: true,
102
111
  PageSize: limit,
103
- ...areaCode && { AreaCode: areaCode },
104
112
  // `Contains` is the vendor's matching-pattern filter — digits
105
- // match anywhere in the number, so one search box serves "my
106
- // area code" and "ends in 7777" alike.
113
+ // match anywhere in the number, so one search box serves "an 888
114
+ // prefix" and "ends in 7777" alike. (AreaCode is a Local-only
115
+ // filter and went with Local.json.)
107
116
  ...contains && { Contains: contains },
108
117
  ...page && { Page: page }
109
118
  }
@@ -150,6 +159,84 @@ var twilio = {
150
159
  "Authorization": "Basic " + Buffer.from(accountSid + ":" + authToken).toString("base64")
151
160
  }
152
161
  });
162
+ },
163
+ // TOLL-FREE VERIFICATION — the carrier registration that lets a purchased
164
+ // number actually deliver. From the Tollfree Verification resource docs
165
+ // (messaging.twilio.com, docs/messaging/api/tollfree-verification-resource):
166
+ // POST /v1/Tollfree/Verifications
167
+ // required: BusinessName, BusinessWebsite, NotificationEmail,
168
+ // UseCaseCategories (array), UseCaseSummary,
169
+ // ProductionMessageSample, OptInImageUrls (array), OptInType,
170
+ // MessageVolume, TollfreePhoneNumberSid (PN…)
171
+ // GET /v1/Tollfree/Verifications/{Sid}
172
+ // status: PENDING_REVIEW | IN_REVIEW | TWILIO_APPROVED |
173
+ // TWILIO_REJECTED, with rejection_reason beside a rejection.
174
+ // Basic auth, form-encoded, like everything above. There is no status
175
+ // callback on this resource — the caller polls the fetch.
176
+ //
177
+ // THE END BUSINESS'S DETAILS, never the platform's. Twilio rejects ISV
178
+ // submissions carrying the ISV's own identity (Toll-Free Verification for
179
+ // ISVs, support.twilio.com) — so businessName, website and email here are
180
+ // the merchant's, collected at purchase.
181
+ //
182
+ // The use-case fields are PLATFORM facts and live here beside the message
183
+ // composition for the same reason it does: how Drawbridge collects opt-in
184
+ // (its own hosted entry forms) and what its messages look like are
185
+ // properties of sending through Drawbridge, not of any one merchant.
186
+ submitVerification: async ({
187
+ accountSid,
188
+ authToken,
189
+ businessName,
190
+ email,
191
+ numberSid,
192
+ optInImage,
193
+ request: request2 = request,
194
+ volume,
195
+ website
196
+ }) => {
197
+ const missing = Object.entries({ accountSid, authToken, businessName, email, numberSid, optInImage, website }).filter(([, value]) => !value).map(([name]) => name);
198
+ if (missing.length) throw new Error("Toll-free verification submission missing \u2014 " + missing.join(", "));
199
+ const body = new URLSearchParams();
200
+ body.append("BusinessName", businessName);
201
+ body.append("BusinessWebsite", website);
202
+ body.append("NotificationEmail", email);
203
+ body.append("UseCaseCategories", "MARKETING");
204
+ body.append("UseCaseSummary", "Prize-draw entry confirmations, winner notifications and campaign updates to consumers who opted in on this business's Drawbridge-hosted entry form.");
205
+ body.append("ProductionMessageSample", businessName + "\nThanks for entering! We'll text you here about your entry.\nReply STOP to opt out");
206
+ body.append("OptInImageUrls", optInImage);
207
+ body.append("OptInType", "WEB_FORM");
208
+ body.append("MessageVolume", volume || "1,000");
209
+ body.append("TollfreePhoneNumberSid", numberSid);
210
+ const result = await request2({
211
+ method: "POST",
212
+ type: "form",
213
+ url: "https://messaging.twilio.com/v1/Tollfree/Verifications",
214
+ headers: {
215
+ "Authorization": "Basic " + Buffer.from(accountSid + ":" + authToken).toString("base64")
216
+ },
217
+ body
218
+ });
219
+ return {
220
+ sid: result == null ? void 0 : result.sid,
221
+ status: result == null ? void 0 : result.status
222
+ };
223
+ },
224
+ fetchVerification: async ({ accountSid, authToken, request: request2 = request, sid }) => {
225
+ if (!accountSid || !authToken) throw new Error("Twilio credentials missing \u2014 accountSid, authToken");
226
+ if (!sid) throw new Error("Twilio verification fetch needs the verification sid");
227
+ const result = await request2({
228
+ method: "GET",
229
+ url: "https://messaging.twilio.com/v1/Tollfree/Verifications/" + sid,
230
+ headers: {
231
+ "Authorization": "Basic " + Buffer.from(accountSid + ":" + authToken).toString("base64")
232
+ }
233
+ });
234
+ return {
235
+ // TWILIO_APPROVED → verified, TWILIO_REJECTED → rejected, both
236
+ // review states → pending.
237
+ outcome: (result == null ? void 0 : result.status) === "TWILIO_APPROVED" ? "approved" : (result == null ? void 0 : result.status) === "TWILIO_REJECTED" ? "rejected" : "pending",
238
+ reason: (result == null ? void 0 : result.rejection_reason) || null
239
+ };
153
240
  }
154
241
  };
155
242
  export {
package/package.json CHANGED
@@ -215,5 +215,5 @@
215
215
  "test": ". \"$HOME/.nvm/nvm.sh\" && nvm use && node --test"
216
216
  },
217
217
  "types": "dist/index.d.ts",
218
- "version": "0.0.133"
218
+ "version": "0.0.134"
219
219
  }