@xenterprises/fastify-xtwilio 1.0.0
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/.dockerignore +63 -0
- package/.env.example +257 -0
- package/API.md +973 -0
- package/CHANGELOG.md +189 -0
- package/LICENSE +15 -0
- package/README.md +261 -0
- package/SECURITY.md +721 -0
- package/index.d.ts +999 -0
- package/package.json +55 -0
- package/server/app.js +88 -0
- package/src/services/conversations.js +328 -0
- package/src/services/email.js +362 -0
- package/src/services/rcs.js +284 -0
- package/src/services/sms.js +268 -0
- package/src/xTwilio.js +37 -0
- package/test/xTwilio.test.js +511 -0
package/API.md
ADDED
|
@@ -0,0 +1,973 @@
|
|
|
1
|
+
# xTwilio API Reference
|
|
2
|
+
|
|
3
|
+
Complete API documentation for xTwilio - the Fastify plugin for Twilio communications (SMS, Conversations, RCS) and SendGrid email services.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
1. [SMS Service](#sms-service)
|
|
8
|
+
2. [Conversations Service](#conversations-service)
|
|
9
|
+
3. [RCS Service](#rcs-service)
|
|
10
|
+
4. [Email Service](#email-service)
|
|
11
|
+
5. [Configuration](#configuration)
|
|
12
|
+
6. [Error Handling](#error-handling)
|
|
13
|
+
7. [Usage Examples](#usage-examples)
|
|
14
|
+
8. [Rate Limiting](#rate-limiting)
|
|
15
|
+
9. [Webhook Handling](#webhook-handling)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## SMS Service
|
|
20
|
+
|
|
21
|
+
The SMS service allows you to send text messages, multimedia messages (MMS), and manage message history via Twilio.
|
|
22
|
+
|
|
23
|
+
### Methods Overview
|
|
24
|
+
|
|
25
|
+
| Method | Description |
|
|
26
|
+
|--------|-------------|
|
|
27
|
+
| `send()` | Send SMS message |
|
|
28
|
+
| `sendMMS()` | Send multimedia message |
|
|
29
|
+
| `schedule()` | Schedule message for future delivery |
|
|
30
|
+
| `cancelScheduled()` | Cancel scheduled message |
|
|
31
|
+
| `get()` | Get message details |
|
|
32
|
+
| `getStatus()` | Get message delivery status |
|
|
33
|
+
| `list()` | List messages with filters |
|
|
34
|
+
| `delete()` | Delete message |
|
|
35
|
+
| `getMedia()` | Get media from MMS |
|
|
36
|
+
| `validatePhoneNumber()` | Validate phone number |
|
|
37
|
+
| `sendBulk()` | Send bulk SMS |
|
|
38
|
+
|
|
39
|
+
### send()
|
|
40
|
+
|
|
41
|
+
Send a text message to a phone number.
|
|
42
|
+
|
|
43
|
+
**Signature:**
|
|
44
|
+
```typescript
|
|
45
|
+
send(to: string, body: string, options?: SMSOptions): Promise<SMSResult>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Parameters:**
|
|
49
|
+
- `to` (string, required) - Recipient phone number in E.164 format (+1234567890)
|
|
50
|
+
- `body` (string, required) - Message content (max 160 characters for single SMS)
|
|
51
|
+
- `options` (object, optional) - Additional options
|
|
52
|
+
|
|
53
|
+
**Options:**
|
|
54
|
+
```typescript
|
|
55
|
+
{
|
|
56
|
+
accountSid?: string; // Override default account SID
|
|
57
|
+
mediaUrls?: string[]; // Media URLs for MMS (if needed)
|
|
58
|
+
attributes?: {}; // Custom attributes
|
|
59
|
+
attemptNumber?: number; // Retry attempt number
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Response:**
|
|
64
|
+
```typescript
|
|
65
|
+
{
|
|
66
|
+
sid: string; // Twilio message SID
|
|
67
|
+
status: string; // Message status (queued, sending, sent, failed, etc.)
|
|
68
|
+
to: string; // Recipient phone number
|
|
69
|
+
body: string; // Message text
|
|
70
|
+
numSegments: number; // Number of SMS segments
|
|
71
|
+
price?: number; // Message price
|
|
72
|
+
priceUnit?: string; // Currency (e.g., USD)
|
|
73
|
+
errorCode?: string; // Error code if failed
|
|
74
|
+
errorMessage?: string; // Error message if failed
|
|
75
|
+
dateCreated: Date; // When message was created
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Example:**
|
|
80
|
+
```javascript
|
|
81
|
+
const result = await fastify.sms.send(
|
|
82
|
+
'+1234567890',
|
|
83
|
+
'Hello! Your verification code is 123456'
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
console.log(result.sid); // SM1234567890abcdef1234567890abcdef
|
|
87
|
+
console.log(result.status); // queued
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### sendMMS()
|
|
91
|
+
|
|
92
|
+
Send a multimedia message (image, video, etc.).
|
|
93
|
+
|
|
94
|
+
**Signature:**
|
|
95
|
+
```typescript
|
|
96
|
+
sendMMS(
|
|
97
|
+
to: string,
|
|
98
|
+
body: string,
|
|
99
|
+
mediaUrl: string,
|
|
100
|
+
options?: SMSOptions
|
|
101
|
+
): Promise<SMSResult>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Parameters:**
|
|
105
|
+
- `to` (string, required) - Recipient phone number
|
|
106
|
+
- `body` (string, required) - Message text
|
|
107
|
+
- `mediaUrl` (string, required) - URL of media file (must be HTTPS)
|
|
108
|
+
- `options` (object, optional) - Additional options
|
|
109
|
+
|
|
110
|
+
**Supported Media Types:**
|
|
111
|
+
- Images: JPEG, PNG, GIF, WEBP
|
|
112
|
+
- Video: MP4, 3GP
|
|
113
|
+
- Audio: WAV, MP3, OGG
|
|
114
|
+
|
|
115
|
+
**Example:**
|
|
116
|
+
```javascript
|
|
117
|
+
const result = await fastify.sms.sendMMS(
|
|
118
|
+
'+1234567890',
|
|
119
|
+
'Check out this photo!',
|
|
120
|
+
'https://example.com/photo.jpg'
|
|
121
|
+
);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### schedule()
|
|
125
|
+
|
|
126
|
+
Schedule an SMS for future delivery.
|
|
127
|
+
|
|
128
|
+
**Signature:**
|
|
129
|
+
```typescript
|
|
130
|
+
schedule(
|
|
131
|
+
to: string,
|
|
132
|
+
body: string,
|
|
133
|
+
sendAt: Date | string,
|
|
134
|
+
options?: SMSOptions
|
|
135
|
+
): Promise<SMSResult>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Parameters:**
|
|
139
|
+
- `to` (string, required) - Recipient phone number
|
|
140
|
+
- `body` (string, required) - Message content
|
|
141
|
+
- `sendAt` (Date | string, required) - When to send (ISO 8601 format or Date object)
|
|
142
|
+
- `options` (object, optional) - Additional options
|
|
143
|
+
|
|
144
|
+
**Example:**
|
|
145
|
+
```javascript
|
|
146
|
+
// Schedule for 1 hour from now
|
|
147
|
+
const futureDate = new Date(Date.now() + 3600000);
|
|
148
|
+
|
|
149
|
+
const result = await fastify.sms.schedule(
|
|
150
|
+
'+1234567890',
|
|
151
|
+
'Reminder: Your appointment is tomorrow',
|
|
152
|
+
futureDate
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
// Or using ISO 8601 string
|
|
156
|
+
const result = await fastify.sms.schedule(
|
|
157
|
+
'+1234567890',
|
|
158
|
+
'Reminder: Your appointment is tomorrow',
|
|
159
|
+
'2024-12-31T10:00:00Z'
|
|
160
|
+
);
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### cancelScheduled()
|
|
164
|
+
|
|
165
|
+
Cancel a previously scheduled message.
|
|
166
|
+
|
|
167
|
+
**Signature:**
|
|
168
|
+
```typescript
|
|
169
|
+
cancelScheduled(messageSid: string): Promise<{ success: boolean }>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**Parameters:**
|
|
173
|
+
- `messageSid` (string, required) - SID of the scheduled message to cancel
|
|
174
|
+
|
|
175
|
+
**Example:**
|
|
176
|
+
```javascript
|
|
177
|
+
const result = await fastify.sms.cancelScheduled('SMxxxxxxxxxxxxxxxxxxxxxxxxxx');
|
|
178
|
+
console.log(result.success); // true
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### getStatus()
|
|
182
|
+
|
|
183
|
+
Get the delivery status of a message.
|
|
184
|
+
|
|
185
|
+
**Signature:**
|
|
186
|
+
```typescript
|
|
187
|
+
getStatus(messageSid: string): Promise<SMSStatus>
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Response:**
|
|
191
|
+
```typescript
|
|
192
|
+
{
|
|
193
|
+
sid: string; // Message SID
|
|
194
|
+
status: string; // Current status
|
|
195
|
+
errorCode?: number; // Error code if failed
|
|
196
|
+
errorMessage?: string; // Error message if failed
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Status Values:**
|
|
201
|
+
- `queued` - Message is queued for delivery
|
|
202
|
+
- `sending` - Message is being sent
|
|
203
|
+
- `sent` - Message was sent successfully
|
|
204
|
+
- `delivered` - Message was delivered to recipient
|
|
205
|
+
- `failed` - Message delivery failed
|
|
206
|
+
- `undelivered` - Message could not be delivered
|
|
207
|
+
|
|
208
|
+
**Example:**
|
|
209
|
+
```javascript
|
|
210
|
+
const status = await fastify.sms.getStatus('SMxxxxxxxxxxxxxxxxxxxxxxxxxx');
|
|
211
|
+
console.log(status.status); // "delivered"
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### list()
|
|
215
|
+
|
|
216
|
+
List SMS messages with optional filters.
|
|
217
|
+
|
|
218
|
+
**Signature:**
|
|
219
|
+
```typescript
|
|
220
|
+
list(filters?: {
|
|
221
|
+
to?: string;
|
|
222
|
+
from?: string;
|
|
223
|
+
status?: string;
|
|
224
|
+
limit?: number;
|
|
225
|
+
}): Promise<SMSResult[]>
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Example:**
|
|
229
|
+
```javascript
|
|
230
|
+
// Get last 10 messages to a phone number
|
|
231
|
+
const messages = await fastify.sms.list({
|
|
232
|
+
to: '+1234567890',
|
|
233
|
+
limit: 10
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Get failed messages
|
|
237
|
+
const failedMessages = await fastify.sms.list({
|
|
238
|
+
status: 'failed',
|
|
239
|
+
limit: 50
|
|
240
|
+
});
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### validatePhoneNumber()
|
|
244
|
+
|
|
245
|
+
Validate and get information about a phone number.
|
|
246
|
+
|
|
247
|
+
**Signature:**
|
|
248
|
+
```typescript
|
|
249
|
+
validatePhoneNumber(
|
|
250
|
+
phoneNumber: string,
|
|
251
|
+
options?: { countryCode?: string }
|
|
252
|
+
): Promise<PhoneValidationResult>
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**Response:**
|
|
256
|
+
```typescript
|
|
257
|
+
{
|
|
258
|
+
phoneNumber: string; // Validated phone in E.164 format
|
|
259
|
+
isValid: boolean; // Is the number valid?
|
|
260
|
+
countryCode?: string; // Country code (e.g., US)
|
|
261
|
+
numberType?: string; // mobile, fixed-line, voip, etc.
|
|
262
|
+
carrier?: string; // Carrier name
|
|
263
|
+
error?: string; // Error message if invalid
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**Example:**
|
|
268
|
+
```javascript
|
|
269
|
+
const validation = await fastify.sms.validatePhoneNumber('+14155552671');
|
|
270
|
+
|
|
271
|
+
if (validation.isValid) {
|
|
272
|
+
console.log(`${validation.carrier} - ${validation.numberType}`);
|
|
273
|
+
// Output: Verizon - mobile
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### sendBulk()
|
|
278
|
+
|
|
279
|
+
Send SMS to multiple recipients in a single operation.
|
|
280
|
+
|
|
281
|
+
**Signature:**
|
|
282
|
+
```typescript
|
|
283
|
+
sendBulk(messages: BulkSMSMessage[]): Promise<SMSResult[]>
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**Parameters:**
|
|
287
|
+
```typescript
|
|
288
|
+
messages: Array<{
|
|
289
|
+
to: string; // Recipient phone number
|
|
290
|
+
body: string; // Message body
|
|
291
|
+
mediaUrls?: string[]; // Optional media for MMS
|
|
292
|
+
options?: SMSOptions; // Optional per-message options
|
|
293
|
+
}>
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
**Example:**
|
|
297
|
+
```javascript
|
|
298
|
+
const results = await fastify.sms.sendBulk([
|
|
299
|
+
{
|
|
300
|
+
to: '+1234567890',
|
|
301
|
+
body: 'Hello Alice! Your code: 111111'
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
to: '+0987654321',
|
|
305
|
+
body: 'Hello Bob! Your code: 222222'
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
to: '+1111111111',
|
|
309
|
+
body: 'Hello Charlie! Your code: 333333'
|
|
310
|
+
}
|
|
311
|
+
]);
|
|
312
|
+
|
|
313
|
+
results.forEach(result => {
|
|
314
|
+
console.log(`${result.to}: ${result.status}`);
|
|
315
|
+
});
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## Conversations Service
|
|
321
|
+
|
|
322
|
+
Conversations provide multi-channel messaging combining SMS, Email, WhatsApp, and more.
|
|
323
|
+
|
|
324
|
+
### Methods Overview
|
|
325
|
+
|
|
326
|
+
| Method | Description |
|
|
327
|
+
|--------|-------------|
|
|
328
|
+
| `create()` | Create new conversation |
|
|
329
|
+
| `get()` | Get conversation details |
|
|
330
|
+
| `update()` | Update conversation |
|
|
331
|
+
| `list()` | List conversations |
|
|
332
|
+
| `delete()` | Delete conversation |
|
|
333
|
+
| `addParticipant()` | Add participant |
|
|
334
|
+
| `listParticipants()` | List participants |
|
|
335
|
+
| `removeParticipant()` | Remove participant |
|
|
336
|
+
| `sendMessage()` | Send message to conversation |
|
|
337
|
+
| `sendMediaMessage()` | Send media message |
|
|
338
|
+
| `getMessages()` | Get conversation messages |
|
|
339
|
+
| `getMessage()` | Get specific message |
|
|
340
|
+
| `deleteMessage()` | Delete message |
|
|
341
|
+
| `getWebhooks()` | Get webhook configuration |
|
|
342
|
+
| `updateWebhooks()` | Update webhooks |
|
|
343
|
+
|
|
344
|
+
### create()
|
|
345
|
+
|
|
346
|
+
Create a new conversation.
|
|
347
|
+
|
|
348
|
+
**Signature:**
|
|
349
|
+
```typescript
|
|
350
|
+
create(
|
|
351
|
+
friendlyName: string,
|
|
352
|
+
attributes?: Record<string, any>
|
|
353
|
+
): Promise<ConversationInfo>
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
**Example:**
|
|
357
|
+
```javascript
|
|
358
|
+
const conversation = await fastify.conversations.create(
|
|
359
|
+
'Support Chat - Order #12345',
|
|
360
|
+
{
|
|
361
|
+
order_id: '12345',
|
|
362
|
+
customer_id: 'CUST_789',
|
|
363
|
+
priority: 'high'
|
|
364
|
+
}
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
console.log(conversation.sid); // IV1234567890abcdef1234567890abcdef
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### addParticipant()
|
|
371
|
+
|
|
372
|
+
Add a participant to a conversation.
|
|
373
|
+
|
|
374
|
+
**Signature:**
|
|
375
|
+
```typescript
|
|
376
|
+
addParticipant(
|
|
377
|
+
conversationSid: string,
|
|
378
|
+
identity?: string,
|
|
379
|
+
messagingBindingAddress?: string
|
|
380
|
+
): Promise<ConversationParticipant>
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
**Parameters:**
|
|
384
|
+
- `conversationSid` (string, required) - Conversation ID
|
|
385
|
+
- `identity` (string, optional) - Participant identity (user ID, email, etc.)
|
|
386
|
+
- `messagingBindingAddress` (string, optional) - SMS number, email, WhatsApp ID
|
|
387
|
+
|
|
388
|
+
**Example:**
|
|
389
|
+
```javascript
|
|
390
|
+
// Add customer via SMS
|
|
391
|
+
const participant1 = await fastify.conversations.addParticipant(
|
|
392
|
+
'IV1234567890abcdef1234567890abcdef',
|
|
393
|
+
'customer_user_123',
|
|
394
|
+
'+1234567890' // SMS number
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
// Add agent via email
|
|
398
|
+
const participant2 = await fastify.conversations.addParticipant(
|
|
399
|
+
'IV1234567890abcdef1234567890abcdef',
|
|
400
|
+
'agent_support_456',
|
|
401
|
+
'support@example.com' // Email
|
|
402
|
+
);
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### sendMessage()
|
|
406
|
+
|
|
407
|
+
Send a message to a conversation.
|
|
408
|
+
|
|
409
|
+
**Signature:**
|
|
410
|
+
```typescript
|
|
411
|
+
sendMessage(
|
|
412
|
+
conversationSid: string,
|
|
413
|
+
body: string,
|
|
414
|
+
author: string,
|
|
415
|
+
attributes?: Record<string, any>
|
|
416
|
+
): Promise<ConversationMessage>
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
**Example:**
|
|
420
|
+
```javascript
|
|
421
|
+
const message = await fastify.conversations.sendMessage(
|
|
422
|
+
'IV1234567890abcdef1234567890abcdef',
|
|
423
|
+
'Thank you for contacting support. How can we help?',
|
|
424
|
+
'agent_support_456',
|
|
425
|
+
{ message_type: 'greeting' }
|
|
426
|
+
);
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### getMessages()
|
|
430
|
+
|
|
431
|
+
Get all messages in a conversation.
|
|
432
|
+
|
|
433
|
+
**Signature:**
|
|
434
|
+
```typescript
|
|
435
|
+
getMessages(
|
|
436
|
+
conversationSid: string,
|
|
437
|
+
options?: { limit?: number; index?: number }
|
|
438
|
+
): Promise<ConversationMessage[]>
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
**Example:**
|
|
442
|
+
```javascript
|
|
443
|
+
const messages = await fastify.conversations.getMessages(
|
|
444
|
+
'IV1234567890abcdef1234567890abcdef',
|
|
445
|
+
{ limit: 50 }
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
messages.forEach(msg => {
|
|
449
|
+
console.log(`${msg.author}: ${msg.body}`);
|
|
450
|
+
});
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
---
|
|
454
|
+
|
|
455
|
+
## RCS Service
|
|
456
|
+
|
|
457
|
+
RCS (Rich Communication Services) provides rich messaging capabilities with cards, buttons, and media.
|
|
458
|
+
|
|
459
|
+
### Methods Overview
|
|
460
|
+
|
|
461
|
+
| Method | Description |
|
|
462
|
+
|--------|-------------|
|
|
463
|
+
| `send()` | Send basic RCS message |
|
|
464
|
+
| `sendMedia()` | Send with media |
|
|
465
|
+
| `sendTemplate()` | Send from template |
|
|
466
|
+
| `sendRichCard()` | Send rich card |
|
|
467
|
+
| `sendCarousel()` | Send carousel of cards |
|
|
468
|
+
| `sendQuickReplies()` | Send with quick replies |
|
|
469
|
+
| `getStatus()` | Get message status |
|
|
470
|
+
| `listTemplates()` | List templates |
|
|
471
|
+
| `getTemplate()` | Get template details |
|
|
472
|
+
| `deleteTemplate()` | Delete template |
|
|
473
|
+
|
|
474
|
+
### sendRichCard()
|
|
475
|
+
|
|
476
|
+
Send a rich card with title, description, image, and actions.
|
|
477
|
+
|
|
478
|
+
**Signature:**
|
|
479
|
+
```typescript
|
|
480
|
+
sendRichCard(to: string, card: RCSRichCard): Promise<RCSSendResult>
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
**Card Structure:**
|
|
484
|
+
```typescript
|
|
485
|
+
{
|
|
486
|
+
title: string; // Card title
|
|
487
|
+
description: string; // Card description
|
|
488
|
+
mediaUrl?: string; // Image/video URL
|
|
489
|
+
actions?: Array<{
|
|
490
|
+
type: 'url' | 'phone' | 'open_app' | 'create_calendar_event' | 'reply';
|
|
491
|
+
text: string; // Button text
|
|
492
|
+
url?: string; // For url action
|
|
493
|
+
phoneNumber?: string; // For phone action
|
|
494
|
+
payload?: string; // For reply action
|
|
495
|
+
}>;
|
|
496
|
+
}
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
**Example:**
|
|
500
|
+
```javascript
|
|
501
|
+
const result = await fastify.rcs.sendRichCard(
|
|
502
|
+
'+1234567890',
|
|
503
|
+
{
|
|
504
|
+
title: 'Summer Sale',
|
|
505
|
+
description: '50% off all items this weekend only',
|
|
506
|
+
mediaUrl: 'https://example.com/sale-banner.jpg',
|
|
507
|
+
actions: [
|
|
508
|
+
{
|
|
509
|
+
type: 'url',
|
|
510
|
+
text: 'Shop Now',
|
|
511
|
+
url: 'https://example.com/sale'
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
type: 'phone',
|
|
515
|
+
text: 'Call Us',
|
|
516
|
+
phoneNumber: '+1234567890'
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
type: 'reply',
|
|
520
|
+
text: 'More Info',
|
|
521
|
+
payload: 'sale_info'
|
|
522
|
+
}
|
|
523
|
+
]
|
|
524
|
+
}
|
|
525
|
+
);
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
### sendCarousel()
|
|
529
|
+
|
|
530
|
+
Send multiple rich cards in a carousel.
|
|
531
|
+
|
|
532
|
+
**Signature:**
|
|
533
|
+
```typescript
|
|
534
|
+
sendCarousel(to: string, cards: RCSRichCard[]): Promise<RCSSendResult>
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
**Example:**
|
|
538
|
+
```javascript
|
|
539
|
+
const result = await fastify.rcs.sendCarousel(
|
|
540
|
+
'+1234567890',
|
|
541
|
+
[
|
|
542
|
+
{
|
|
543
|
+
title: 'Product 1',
|
|
544
|
+
description: '$29.99',
|
|
545
|
+
mediaUrl: 'https://example.com/product1.jpg',
|
|
546
|
+
actions: [{
|
|
547
|
+
type: 'url',
|
|
548
|
+
text: 'View',
|
|
549
|
+
url: 'https://example.com/product/1'
|
|
550
|
+
}]
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
title: 'Product 2',
|
|
554
|
+
description: '$39.99',
|
|
555
|
+
mediaUrl: 'https://example.com/product2.jpg',
|
|
556
|
+
actions: [{
|
|
557
|
+
type: 'url',
|
|
558
|
+
text: 'View',
|
|
559
|
+
url: 'https://example.com/product/2'
|
|
560
|
+
}]
|
|
561
|
+
}
|
|
562
|
+
]
|
|
563
|
+
);
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
### sendQuickReplies()
|
|
567
|
+
|
|
568
|
+
Send a message with quick reply options.
|
|
569
|
+
|
|
570
|
+
**Signature:**
|
|
571
|
+
```typescript
|
|
572
|
+
sendQuickReplies(
|
|
573
|
+
to: string,
|
|
574
|
+
body: string,
|
|
575
|
+
replies: RCSQuickReply[]
|
|
576
|
+
): Promise<RCSSendResult>
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
**Example:**
|
|
580
|
+
```javascript
|
|
581
|
+
const result = await fastify.rcs.sendQuickReplies(
|
|
582
|
+
'+1234567890',
|
|
583
|
+
'How can we help you today?',
|
|
584
|
+
[
|
|
585
|
+
{ text: 'Product Question', payload: 'product_q' },
|
|
586
|
+
{ text: 'Order Status', payload: 'order_status' },
|
|
587
|
+
{ text: 'Complaint', payload: 'complaint' }
|
|
588
|
+
]
|
|
589
|
+
);
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
---
|
|
593
|
+
|
|
594
|
+
## Email Service
|
|
595
|
+
|
|
596
|
+
Send emails via SendGrid with templates, attachments, and contact management.
|
|
597
|
+
|
|
598
|
+
### Methods Overview
|
|
599
|
+
|
|
600
|
+
| Method | Description |
|
|
601
|
+
|--------|-------------|
|
|
602
|
+
| `send()` | Send email |
|
|
603
|
+
| `sendTemplate()` | Send from template |
|
|
604
|
+
| `sendWithAttachments()` | Send with file attachments |
|
|
605
|
+
| `sendBulk()` | Send bulk email |
|
|
606
|
+
| `sendPersonalizedBulk()` | Send personalized bulk |
|
|
607
|
+
| `validate()` | Validate email address |
|
|
608
|
+
| `addContact()` | Add contact |
|
|
609
|
+
| `searchContact()` | Search contact |
|
|
610
|
+
| `deleteContact()` | Delete contact |
|
|
611
|
+
| `createList()` | Create contact list |
|
|
612
|
+
| `getLists()` | Get contact lists |
|
|
613
|
+
| `deleteList()` | Delete list |
|
|
614
|
+
|
|
615
|
+
### send()
|
|
616
|
+
|
|
617
|
+
Send a simple email.
|
|
618
|
+
|
|
619
|
+
**Signature:**
|
|
620
|
+
```typescript
|
|
621
|
+
send(
|
|
622
|
+
to: string,
|
|
623
|
+
subject: string,
|
|
624
|
+
html: string,
|
|
625
|
+
text?: string,
|
|
626
|
+
options?: EmailOptions
|
|
627
|
+
): Promise<EmailSendResult>
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
**Example:**
|
|
631
|
+
```javascript
|
|
632
|
+
const result = await fastify.email.send(
|
|
633
|
+
'customer@example.com',
|
|
634
|
+
'Welcome to Our Service!',
|
|
635
|
+
`<h1>Welcome</h1>
|
|
636
|
+
<p>Thank you for signing up!</p>
|
|
637
|
+
<a href="https://example.com/get-started">Get Started</a>`,
|
|
638
|
+
'Welcome to Our Service! Thank you for signing up.'
|
|
639
|
+
);
|
|
640
|
+
|
|
641
|
+
console.log(result.messageId); // Message ID from SendGrid
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
### sendTemplate()
|
|
645
|
+
|
|
646
|
+
Send email using a SendGrid template.
|
|
647
|
+
|
|
648
|
+
**Signature:**
|
|
649
|
+
```typescript
|
|
650
|
+
sendTemplate(
|
|
651
|
+
to: string,
|
|
652
|
+
subject: string,
|
|
653
|
+
templateId: string,
|
|
654
|
+
dynamicData: Record<string, any>,
|
|
655
|
+
options?: EmailOptions
|
|
656
|
+
): Promise<EmailSendResult>
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
**Example:**
|
|
660
|
+
```javascript
|
|
661
|
+
const result = await fastify.email.sendTemplate(
|
|
662
|
+
'user@example.com',
|
|
663
|
+
'Welcome Email',
|
|
664
|
+
'd-1234567890abcdef1234567890abcdef', // Template ID from SendGrid
|
|
665
|
+
{
|
|
666
|
+
first_name: 'John',
|
|
667
|
+
activation_link: 'https://example.com/activate/abc123'
|
|
668
|
+
}
|
|
669
|
+
);
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
### sendWithAttachments()
|
|
673
|
+
|
|
674
|
+
Send email with file attachments.
|
|
675
|
+
|
|
676
|
+
**Signature:**
|
|
677
|
+
```typescript
|
|
678
|
+
sendWithAttachments(
|
|
679
|
+
to: string,
|
|
680
|
+
subject: string,
|
|
681
|
+
html: string,
|
|
682
|
+
attachments: Array<{
|
|
683
|
+
content: string; // Base64 encoded
|
|
684
|
+
filename: string;
|
|
685
|
+
type: string; // MIME type
|
|
686
|
+
}>,
|
|
687
|
+
options?: EmailOptions
|
|
688
|
+
): Promise<EmailSendResult>
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
**Example:**
|
|
692
|
+
```javascript
|
|
693
|
+
const fs = require('fs');
|
|
694
|
+
|
|
695
|
+
const pdfContent = fs.readFileSync('invoice.pdf').toString('base64');
|
|
696
|
+
|
|
697
|
+
const result = await fastify.email.sendWithAttachments(
|
|
698
|
+
'customer@example.com',
|
|
699
|
+
'Your Invoice',
|
|
700
|
+
'<p>Your invoice is attached.</p>',
|
|
701
|
+
[
|
|
702
|
+
{
|
|
703
|
+
content: pdfContent,
|
|
704
|
+
filename: 'invoice_12345.pdf',
|
|
705
|
+
type: 'application/pdf'
|
|
706
|
+
}
|
|
707
|
+
]
|
|
708
|
+
);
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
### validate()
|
|
712
|
+
|
|
713
|
+
Check if an email address is valid and deliverable.
|
|
714
|
+
|
|
715
|
+
**Signature:**
|
|
716
|
+
```typescript
|
|
717
|
+
validate(email: string): Promise<{
|
|
718
|
+
isValid: boolean;
|
|
719
|
+
suggestion?: string;
|
|
720
|
+
error?: string;
|
|
721
|
+
}>
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
**Example:**
|
|
725
|
+
```javascript
|
|
726
|
+
const validation = await fastify.email.validate('user@example.com');
|
|
727
|
+
|
|
728
|
+
if (validation.isValid) {
|
|
729
|
+
console.log('Email is valid and deliverable');
|
|
730
|
+
} else {
|
|
731
|
+
console.log('Validation error:', validation.error);
|
|
732
|
+
if (validation.suggestion) {
|
|
733
|
+
console.log('Did you mean:', validation.suggestion);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
### addContact()
|
|
739
|
+
|
|
740
|
+
Add a contact to SendGrid.
|
|
741
|
+
|
|
742
|
+
**Signature:**
|
|
743
|
+
```typescript
|
|
744
|
+
addContact(
|
|
745
|
+
email: string,
|
|
746
|
+
data: Record<string, string>,
|
|
747
|
+
listIds?: string[]
|
|
748
|
+
): Promise<EmailContact>
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
**Example:**
|
|
752
|
+
```javascript
|
|
753
|
+
const contact = await fastify.email.addContact(
|
|
754
|
+
'john@example.com',
|
|
755
|
+
{
|
|
756
|
+
firstName: 'John',
|
|
757
|
+
lastName: 'Doe',
|
|
758
|
+
phone: '+1234567890',
|
|
759
|
+
company: 'Acme Inc'
|
|
760
|
+
},
|
|
761
|
+
['list_id_123'] // Add to lists
|
|
762
|
+
);
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
### sendBulk()
|
|
766
|
+
|
|
767
|
+
Send email to multiple recipients.
|
|
768
|
+
|
|
769
|
+
**Signature:**
|
|
770
|
+
```typescript
|
|
771
|
+
sendBulk(
|
|
772
|
+
to: string[],
|
|
773
|
+
subject: string,
|
|
774
|
+
html: string,
|
|
775
|
+
options?: EmailOptions
|
|
776
|
+
): Promise<EmailSendResult[]>
|
|
777
|
+
```
|
|
778
|
+
|
|
779
|
+
**Example:**
|
|
780
|
+
```javascript
|
|
781
|
+
const recipients = [
|
|
782
|
+
'user1@example.com',
|
|
783
|
+
'user2@example.com',
|
|
784
|
+
'user3@example.com'
|
|
785
|
+
];
|
|
786
|
+
|
|
787
|
+
const results = await fastify.email.sendBulk(
|
|
788
|
+
recipients,
|
|
789
|
+
'Newsletter - December',
|
|
790
|
+
'<h1>December Newsletter</h1><p>...'
|
|
791
|
+
);
|
|
792
|
+
|
|
793
|
+
results.forEach((result, idx) => {
|
|
794
|
+
console.log(`${recipients[idx]}: ${result.status}`);
|
|
795
|
+
});
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
---
|
|
799
|
+
|
|
800
|
+
## Configuration
|
|
801
|
+
|
|
802
|
+
### Plugin Registration
|
|
803
|
+
|
|
804
|
+
```javascript
|
|
805
|
+
import Fastify from 'fastify';
|
|
806
|
+
import xTwilio from '@xenterprises/fastify-xtwilio';
|
|
807
|
+
|
|
808
|
+
const fastify = Fastify();
|
|
809
|
+
|
|
810
|
+
await fastify.register(xTwilio, {
|
|
811
|
+
twilio: {
|
|
812
|
+
accountSid: process.env.TWILIO_ACCOUNT_SID,
|
|
813
|
+
authToken: process.env.TWILIO_AUTH_TOKEN,
|
|
814
|
+
phoneNumber: process.env.TWILIO_PHONE_NUMBER,
|
|
815
|
+
messagingServiceSid: process.env.TWILIO_MESSAGING_SERVICE_SID,
|
|
816
|
+
active: true, // Enable Twilio
|
|
817
|
+
},
|
|
818
|
+
sendgrid: {
|
|
819
|
+
apiKey: process.env.SENDGRID_API_KEY,
|
|
820
|
+
fromEmail: process.env.SENDGRID_FROM_EMAIL,
|
|
821
|
+
active: true, // Enable SendGrid
|
|
822
|
+
},
|
|
823
|
+
logRequests: process.env.NODE_ENV === 'development',
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
await fastify.listen({ port: 3000 });
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
---
|
|
830
|
+
|
|
831
|
+
## Error Handling
|
|
832
|
+
|
|
833
|
+
All methods throw errors with descriptive messages. Use try-catch blocks:
|
|
834
|
+
|
|
835
|
+
```javascript
|
|
836
|
+
try {
|
|
837
|
+
await fastify.sms.send('+1234567890', 'Hello');
|
|
838
|
+
} catch (error) {
|
|
839
|
+
console.error('SMS failed:', error.message);
|
|
840
|
+
// Handle specific error
|
|
841
|
+
}
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
---
|
|
845
|
+
|
|
846
|
+
## Usage Examples
|
|
847
|
+
|
|
848
|
+
### Complete SMS Flow
|
|
849
|
+
|
|
850
|
+
```javascript
|
|
851
|
+
// 1. Validate phone number
|
|
852
|
+
const validation = await fastify.sms.validatePhoneNumber('+1234567890');
|
|
853
|
+
if (!validation.isValid) {
|
|
854
|
+
throw new Error('Invalid phone number');
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// 2. Send SMS
|
|
858
|
+
const result = await fastify.sms.send(
|
|
859
|
+
'+1234567890',
|
|
860
|
+
'Your verification code is 123456'
|
|
861
|
+
);
|
|
862
|
+
|
|
863
|
+
// 3. Check status
|
|
864
|
+
const status = await fastify.sms.getStatus(result.sid);
|
|
865
|
+
console.log('Status:', status.status);
|
|
866
|
+
|
|
867
|
+
// 4. List recent messages
|
|
868
|
+
const messages = await fastify.sms.list({ limit: 10 });
|
|
869
|
+
```
|
|
870
|
+
|
|
871
|
+
### Customer Conversation
|
|
872
|
+
|
|
873
|
+
```javascript
|
|
874
|
+
// 1. Create conversation
|
|
875
|
+
const conversation = await fastify.conversations.create('Customer Support');
|
|
876
|
+
|
|
877
|
+
// 2. Add customer (SMS)
|
|
878
|
+
await fastify.conversations.addParticipant(
|
|
879
|
+
conversation.sid,
|
|
880
|
+
'customer_123',
|
|
881
|
+
'+1234567890'
|
|
882
|
+
);
|
|
883
|
+
|
|
884
|
+
// 3. Add agent (Email)
|
|
885
|
+
await fastify.conversations.addParticipant(
|
|
886
|
+
conversation.sid,
|
|
887
|
+
'agent_456',
|
|
888
|
+
'support@company.com'
|
|
889
|
+
);
|
|
890
|
+
|
|
891
|
+
// 4. Send greeting
|
|
892
|
+
await fastify.conversations.sendMessage(
|
|
893
|
+
conversation.sid,
|
|
894
|
+
'Hello! How can we help you?',
|
|
895
|
+
'agent_456'
|
|
896
|
+
);
|
|
897
|
+
|
|
898
|
+
// 5. Get conversation history
|
|
899
|
+
const messages = await fastify.conversations.getMessages(
|
|
900
|
+
conversation.sid
|
|
901
|
+
);
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
### Email Campaign with Personaliz ation
|
|
905
|
+
|
|
906
|
+
```javascript
|
|
907
|
+
const customers = [
|
|
908
|
+
{ email: 'alice@example.com', name: 'Alice', orderAmount: '$100' },
|
|
909
|
+
{ email: 'bob@example.com', name: 'Bob', orderAmount: '$250' }
|
|
910
|
+
];
|
|
911
|
+
|
|
912
|
+
const results = await fastify.email.sendPersonalizedBulk(
|
|
913
|
+
customers.map(customer => ({
|
|
914
|
+
to: customer.email,
|
|
915
|
+
subject: `Thank you for your ${customer.orderAmount} order, ${customer.name}!`,
|
|
916
|
+
html: `<p>Hi ${customer.name},</p><p>Thank you for ordering ${customer.orderAmount}!</p>`,
|
|
917
|
+
personalData: {
|
|
918
|
+
name: customer.name,
|
|
919
|
+
amount: customer.orderAmount
|
|
920
|
+
}
|
|
921
|
+
}))
|
|
922
|
+
);
|
|
923
|
+
```
|
|
924
|
+
|
|
925
|
+
---
|
|
926
|
+
|
|
927
|
+
## Rate Limiting
|
|
928
|
+
|
|
929
|
+
Implement rate limiting to prevent abuse:
|
|
930
|
+
|
|
931
|
+
```bash
|
|
932
|
+
# Environment variables
|
|
933
|
+
SMS_RATE_LIMIT_PER_MINUTE=10
|
|
934
|
+
EMAIL_RATE_LIMIT_PER_MINUTE=5
|
|
935
|
+
```
|
|
936
|
+
|
|
937
|
+
---
|
|
938
|
+
|
|
939
|
+
## Webhook Handling
|
|
940
|
+
|
|
941
|
+
Configure webhooks in your Twilio account to receive delivery status updates, inbound messages, and conversation events.
|
|
942
|
+
|
|
943
|
+
**Webhook Signature Validation:**
|
|
944
|
+
```javascript
|
|
945
|
+
import crypto from 'crypto';
|
|
946
|
+
|
|
947
|
+
fastify.post('/webhooks/twilio', async (request, reply) => {
|
|
948
|
+
const signature = request.headers['x-twilio-signature'];
|
|
949
|
+
const body = request.rawBody || JSON.stringify(request.body);
|
|
950
|
+
|
|
951
|
+
const expectedSignature = crypto
|
|
952
|
+
.createHmac('sha1', process.env.TWILIO_AUTH_TOKEN)
|
|
953
|
+
.update(`${process.env.WEBHOOK_URL}${body}`)
|
|
954
|
+
.digest('base64');
|
|
955
|
+
|
|
956
|
+
if (signature !== expectedSignature) {
|
|
957
|
+
return reply.code(403).send({ error: 'Unauthorized' });
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// Process webhook
|
|
961
|
+
console.log('Webhook received:', request.body);
|
|
962
|
+
reply.code(200).send({ status: 'received' });
|
|
963
|
+
});
|
|
964
|
+
```
|
|
965
|
+
|
|
966
|
+
---
|
|
967
|
+
|
|
968
|
+
## Related Documentation
|
|
969
|
+
|
|
970
|
+
- [Twilio API Docs](https://www.twilio.com/docs/api)
|
|
971
|
+
- [SendGrid Email API](https://docs.sendgrid.com/api-reference/mail-send)
|
|
972
|
+
- [SECURITY.md](./SECURITY.md) - Security best practices
|
|
973
|
+
- [CHANGELOG.md](./CHANGELOG.md) - Version history and changes
|