@smsmode/rcs 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/LICENSE +21 -0
- package/README.md +923 -0
- package/dist/index.cjs +416 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +577 -0
- package/dist/index.d.ts +577 -0
- package/dist/index.mjs +380 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +73 -0
package/README.md
ADDED
|
@@ -0,0 +1,923 @@
|
|
|
1
|
+
# @smsmode/rcs
|
|
2
|
+
|
|
3
|
+
> SDK TypeScript officiel pour l'API REST smsmode© : Intégrez la messagerie RCS enrichie en moins de 5 minutes.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@smsmode/rcs)
|
|
6
|
+
[](https://nodejs.org)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Présentation
|
|
11
|
+
|
|
12
|
+
Ce SDK est une couche d'abstraction au-dessus de l'API REST smsmode©. Il vous évite de gérer manuellement les headers HTTP, l'encodage des paramètres, les erreurs et le timeout, et vous offre à la place une interface TypeScript typée avec autocomplétion IDE complète.
|
|
13
|
+
|
|
14
|
+
**Ce que le SDK prend en charge :**
|
|
15
|
+
- Envoi de messages RCS unitaires, en batch et programmés
|
|
16
|
+
- Contenu riche : texte, fichier, carte et carrousel avec suggestions interactives
|
|
17
|
+
- Gestion des campagnes RCS groupées (jusqu'à 1 000 destinataires)
|
|
18
|
+
- Réception et identification des webhooks (DLR et MO)
|
|
19
|
+
- Gestion des erreurs typées (400, 401, 402, 429, etc.)
|
|
20
|
+
- Timeout configurable avec annulation propre via `AbortController`
|
|
21
|
+
|
|
22
|
+
**Ce que le SDK ne prend pas en charge :**
|
|
23
|
+
- **Retry automatique** : c'est intentionnel. La stratégie de retry dépend de votre contexte métier. Un exemple est fourni dans la section [Patterns](#patterns).
|
|
24
|
+
- **Gestion des channels et organisations** : utilisez l'interface smsmode ou l'API directement.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install @smsmode/rcs
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Prérequis :** Node.js >= 20.0.0
|
|
35
|
+
|
|
36
|
+
Le SDK utilise `fetch` natif (disponible nativement depuis Node.js 18, sans flag depuis Node.js 20) et n'a **aucune dépendance runtime**. Rien d'autre à installer.
|
|
37
|
+
|
|
38
|
+
### ESM (recommandé)
|
|
39
|
+
|
|
40
|
+
Si votre projet utilise `"type": "module"` dans son `package.json` ou des fichiers `.mjs`, utilisez l'import ES module :
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { SmsmodeRcsClient } from '@smsmode/rcs';
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### CommonJS
|
|
47
|
+
|
|
48
|
+
Si votre projet utilise `require()` (fichiers `.js` sans `"type": "module"`, ou `.cjs`), utilisez :
|
|
49
|
+
|
|
50
|
+
```javascript
|
|
51
|
+
const { SmsmodeRcsClient } = require('@smsmode/rcs');
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Le package expose les deux formats : vous n'avez rien à configurer, Node.js choisit automatiquement le bon selon votre environnement.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
> ⚠️ **Server-side uniquement.** Ce SDK est conçu pour s'exécuter côté serveur (Node.js). Ne l'utilisez jamais dans du code exécuté dans un navigateur : votre clé API serait exposée publiquement dans le bundle JavaScript.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Quick Start
|
|
63
|
+
|
|
64
|
+
L'exemple minimal pour envoyer votre premier message RCS :
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { SmsmodeRcsClient } from '@smsmode/rcs';
|
|
68
|
+
|
|
69
|
+
const client = new SmsmodeRcsClient({ apiKey: 'your-api-key' });
|
|
70
|
+
|
|
71
|
+
const message = await client.send({
|
|
72
|
+
recipient: { to: '33600000001' },
|
|
73
|
+
body: { type: 'TEXT', text: 'Bonjour depuis smsmode RCS !' },
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
console.log(message.messageId); // identifiant unique du message
|
|
77
|
+
console.log(message.status.value); // "ENROUTE", "DELIVERED"...
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
> Pensez à envelopper vos appels dans un `try/catch` en production : voir la section [Gestion des erreurs](#gestion-des-erreurs).
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Configuration
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
const client = new SmsmodeRcsClient({
|
|
88
|
+
apiKey: 'your-api-key', // Obligatoire
|
|
89
|
+
timeout: 10000, // Optionnel — défaut : 10 000ms
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**`apiKey`** : Votre clé API smsmode. Voir ci-dessous comment l'obtenir et la gérer.
|
|
94
|
+
|
|
95
|
+
**`timeout`** : Durée maximale d'attente d'une réponse en millisecondes. Au-delà, la requête est annulée et une erreur est levée. Augmentez cette valeur si vous envoyez des batchs volumineux.
|
|
96
|
+
|
|
97
|
+
### Obtenir et gérer votre clé API
|
|
98
|
+
|
|
99
|
+
**Où la trouver :**
|
|
100
|
+
1. Connectez-vous à votre [espace client smsmode](https://ui.smsmode.com)
|
|
101
|
+
2. Allez dans **Settings > API Keys**
|
|
102
|
+
3. Créez une nouvelle clé ou copiez une clé existante
|
|
103
|
+
|
|
104
|
+
**Bonnes pratiques :**
|
|
105
|
+
|
|
106
|
+
Ne commitez jamais votre clé API dans votre dépôt Git. Utilisez une variable d'environnement :
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# fichier .env à la racine du projet
|
|
110
|
+
SMSMODE_API_KEY=votre_cle_api
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// Chargez la variable avec dotenv ou votre gestionnaire d'environnement
|
|
115
|
+
const client = new SmsmodeRcsClient({ apiKey: process.env.SMSMODE_API_KEY });
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
**Permissions associées à une clé :**
|
|
120
|
+
|
|
121
|
+
Chaque clé API est liée à un rôle dans votre organisation smsmode :
|
|
122
|
+
|
|
123
|
+
| Rôle | Périmètre |
|
|
124
|
+
|------|-----------|
|
|
125
|
+
| User | Envoyer dans son propre Channel uniquement |
|
|
126
|
+
| Manager | Envoyer dans tous les Channels de son organisation |
|
|
127
|
+
| Administrator | Envoyer dans les Channels des sous-organisations |
|
|
128
|
+
|
|
129
|
+
Si vous recevez une erreur `401`, vérifiez que :
|
|
130
|
+
- La clé est bien copiée en entier (sans espace avant/après)
|
|
131
|
+
- La clé n'a pas été révoquée dans l'espace client
|
|
132
|
+
- Vous utilisez bien le header `X-Api-Key` : le SDK le gère automatiquement, mais si vous testez via curl ou Postman, c'est un point d'attention courant
|
|
133
|
+
|
|
134
|
+
### Enregistrer votre agent RCS
|
|
135
|
+
|
|
136
|
+
En plus de votre clé API, l'envoi de messages RCS nécessite un **agent RCS** validé. Un agent RCS est l'identité d'entreprise vérifiée qui apparaît côté destinataire : nom de marque, logo, couleur. À la différence d'un expéditeur SMS libre, un agent RCS doit être **enregistré et validé par Google** avant tout envoi.
|
|
137
|
+
|
|
138
|
+
> **Prérequis bloquant :** aucun message ne peut être envoyé sans agent validé. Cette étape est à effectuer une seule fois, avant toute intégration.
|
|
139
|
+
|
|
140
|
+
**[Enregistrer mon agent RCS →](https://www.smsmode.com/enregistrement-agent-rcs/)**
|
|
141
|
+
|
|
142
|
+
Une fois votre agent validé par Google et les opérateurs, il est automatiquement utilisé pour tous vos envois. Si vous disposez de plusieurs agents et souhaitez en sélectionner un spécifique, renseignez le champ `from` dans votre requête avec le nom de l'agent tel qu'il apparaît dans la liste des agents de votre Channel (1-40 caractères).
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Format des numéros de téléphone
|
|
147
|
+
|
|
148
|
+
Tous les numéros doivent être au format **E.164** : indicatif pays suivi du numéro local, sans `+`, sans espaces, sans tirets, sans zéro initial.
|
|
149
|
+
|
|
150
|
+
| Pays | Numéro local | Format E.164 |
|
|
151
|
+
|------|-------------|--------------|
|
|
152
|
+
| France | 06 00 00 00 01 | `33600000001` |
|
|
153
|
+
| Belgique | 0499 00 00 01 | `32499000001` |
|
|
154
|
+
| Suisse | 079 000 00 01 | `41790000001` |
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
// Correct
|
|
158
|
+
{ to: '33600000001' }
|
|
159
|
+
|
|
160
|
+
// Correct également : le + est accepté
|
|
161
|
+
{ to: '+33600000001' }
|
|
162
|
+
|
|
163
|
+
// Incorrect : le zéro initial du numéro local ne s'inclut pas
|
|
164
|
+
{ to: '0600000001' }
|
|
165
|
+
|
|
166
|
+
// Incorrect : pas d'espaces ni de tirets
|
|
167
|
+
{ to: '33 6 00 00 00 01' }
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Un numéro mal formaté entraînera une erreur `400 Bad Request` de l'API.
|
|
171
|
+
|
|
172
|
+
> **Note RCS :** le destinataire doit disposer d'un appareil et d'un opérateur compatibles RCS. Si ce n'est pas le cas, le message ne sera pas livré : RCS ne dispose pas de fallback SMS automatique.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Messages RCS
|
|
177
|
+
|
|
178
|
+
Un **Message** est un envoi RCS unitaire ou en batch. C'est la ressource de base de l'API smsmode RCS. Chaque message est lié à un Channel (votre agent RCS) et peut être associé à une Campaign pour le suivi statistique.
|
|
179
|
+
|
|
180
|
+
### Envoyer un message
|
|
181
|
+
|
|
182
|
+
Envoi simple — le minimum requis.
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
await client.send({
|
|
186
|
+
recipient: { to: '33600000001' },
|
|
187
|
+
body: { type: 'TEXT', text: 'Bonjour depuis smsmode RCS !' },
|
|
188
|
+
});
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**Avec un agent RCS personnalisé.**
|
|
192
|
+
Le champ `from` permet de sélectionner l'agent parmi la liste de votre Channel (1-40 caractères, défaut : `"Default RCS Agent"`).
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
await client.send({
|
|
196
|
+
recipient: { to: '33600000001' },
|
|
197
|
+
body: { type: 'TEXT', text: 'Votre commande a été expédiée.' },
|
|
198
|
+
from: 'MonBrand',
|
|
199
|
+
});
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**Message programmé.**
|
|
203
|
+
Renseignez `sentDate` pour programmer l'envoi. Le message peut être modifié ou annulé avant l'envoi via `update()` ou `cancel()`.
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
await client.send({
|
|
207
|
+
recipient: { to: '33600000001' },
|
|
208
|
+
body: { type: 'TEXT', text: 'Rappel : votre rendez-vous est demain à 10h.' },
|
|
209
|
+
sentDate: '2026-06-01T10:00:00Z',
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**Avec validité personnalisée.**
|
|
214
|
+
Si le message ne peut pas être livré dans ce délai, il expire avec le statut `UNDELIVERED`.
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
await client.send({
|
|
218
|
+
recipient: { to: '33600000001' },
|
|
219
|
+
body: { type: 'TEXT', text: 'Votre code : 123456' },
|
|
220
|
+
validity: { amount: 30, timeUnit: 'SECONDS' }, // min 30s, max 48h (défaut)
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Avec référence client et callbacks.**
|
|
225
|
+
`callbackUrlStatus` reçoit les accusés de réception (DLR), `callbackUrlMo` reçoit les réponses entrantes.
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
await client.send({
|
|
229
|
+
recipient: { to: '33600000001' },
|
|
230
|
+
body: { type: 'TEXT', text: 'Votre message' },
|
|
231
|
+
refClient: 'commande-42',
|
|
232
|
+
callbackUrlStatus: 'https://votre-serveur.com/webhook/dlr',
|
|
233
|
+
callbackUrlMo: 'https://votre-serveur.com/webhook/mo',
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**Envoi en batch.**
|
|
238
|
+
Jusqu'à 1 000 messages en un seul appel. Chaque message est indépendant. Les overloads TypeScript infèrent automatiquement `RcsMessage` pour un envoi unitaire et `RcsMessage[]` pour un batch.
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
const results = await client.send([
|
|
242
|
+
{ recipient: { to: '33600000001' }, body: { type: 'TEXT', text: 'Bonjour Alice !' } },
|
|
243
|
+
{ recipient: { to: '33600000002' }, body: { type: 'TEXT', text: 'Bonjour Bob !' } },
|
|
244
|
+
]);
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
> Pour envoyer à plus de 1 000 destinataires, utilisez les Campagnes ou découpez en plusieurs batchs.
|
|
248
|
+
|
|
249
|
+
### Lister les messages
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
// Retourne la première page avec les paramètres par défaut
|
|
253
|
+
const result = await client.list();
|
|
254
|
+
|
|
255
|
+
console.log(result.totalCount); // nombre total de messages (toutes pages)
|
|
256
|
+
console.log(result.count); // nombre de messages dans cette page
|
|
257
|
+
console.log(result.items); // tableau des messages de la page courante
|
|
258
|
+
|
|
259
|
+
// Avec pagination et filtres
|
|
260
|
+
const filtered = await client.list({
|
|
261
|
+
page: 1,
|
|
262
|
+
pageSize: 20,
|
|
263
|
+
searchBy: {
|
|
264
|
+
direction: 'MT', // MT = envoyé, MO = reçu
|
|
265
|
+
to: '33600000001', // filtrer par destinataire
|
|
266
|
+
},
|
|
267
|
+
sortBy: { sentDate: 'DESC' }, // plus récents en premier
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Obtenir un message
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
const message = await client.get('67c15045-1067-4588-ba3c-737cc5051438');
|
|
275
|
+
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Modifier un message programmé
|
|
279
|
+
|
|
280
|
+
Un message programmé peut être modifié tant qu'il n'a pas encore été envoyé (et au moins 5 minutes avant l'heure d'envoi). Seuls les champs passés dans le payload sont mis à jour, les autres restent inchangés.
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
await client.update('67c15045-1067-4588-ba3c-737cc5051438', {
|
|
284
|
+
sentDate: '2026-06-02T09:00:00Z',
|
|
285
|
+
refClient: 'commande-99',
|
|
286
|
+
});
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
**Effacer un champ optionnel.**
|
|
290
|
+
Passer une chaîne vide pour retirer un paramètre.
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
await client.update('67c15045-1067-4588-ba3c-737cc5051438', {
|
|
294
|
+
refClient: '',
|
|
295
|
+
});
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Annuler un message programmé
|
|
299
|
+
|
|
300
|
+
> **Important :** `cancel()` ne fonctionne que sur les messages **programmés** (statut `SCHEDULED`). Un message déjà parti ne peut pas être annulé.
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
await client.cancel('67c15045-1067-4588-ba3c-737cc5051438');
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Utiliser un Channel ou une Campaign spécifique
|
|
307
|
+
|
|
308
|
+
Par défaut, les appels utilisent le Channel lié à votre clé API. Vous pouvez cibler un Channel ou une Campaign spécifique via le second paramètre `options`, disponible sur toutes les méthodes.
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
// Envoyer via un Channel spécifique (nécessite les permissions Manager ou Administrator)
|
|
312
|
+
await client.send(
|
|
313
|
+
{ recipient: { to: '33600000001' }, body: { type: 'TEXT', text: 'Hello !' } },
|
|
314
|
+
{ channelId: 'da0e501d-4449-40b1-b1f9-3cd1e94031bd' }
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
// Associer le message à une Campaign existante (pour les statistiques)
|
|
318
|
+
await client.send(
|
|
319
|
+
{ recipient: { to: '33600000001' }, body: { type: 'TEXT', text: 'Hello !' } },
|
|
320
|
+
{ campaignId: '4c9f9589-1ffd-48da-82d2-65aa9e5f5f70' }
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
// Combiner les deux
|
|
324
|
+
await client.send(
|
|
325
|
+
{ recipient: { to: '33600000001' }, body: { type: 'TEXT', text: 'Hello !' } },
|
|
326
|
+
{
|
|
327
|
+
channelId: 'da0e501d-4449-40b1-b1f9-3cd1e94031bd',
|
|
328
|
+
campaignId: '4c9f9589-1ffd-48da-82d2-65aa9e5f5f70',
|
|
329
|
+
}
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
// Le second paramètre options fonctionne sur toutes les méthodes
|
|
333
|
+
await client.list({ page: 1 }, { channelId: 'da0e501d-4449-40b1-b1f9-3cd1e94031bd' });
|
|
334
|
+
await client.get('message-id', { campaignId: '4c9f9589-1ffd-48da-82d2-65aa9e5f5f70' });
|
|
335
|
+
await client.update('message-id', { sentDate: '2026-06-02T09:00:00Z' }, { channelId: 'da0e501d-4449-40b1-b1f9-3cd1e94031bd' });
|
|
336
|
+
await client.cancel('message-id', { channelId: 'da0e501d-4449-40b1-b1f9-3cd1e94031bd' });
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## Campagnes RCS
|
|
342
|
+
|
|
343
|
+
Une **Campaign** est un envoi groupé à plusieurs destinataires, traité comme une unité cohérente avec des statistiques consolidées. C'est la ressource recommandée pour les communications marketing ou les notifications de masse.
|
|
344
|
+
|
|
345
|
+
**Différence avec le batch de Messages :**
|
|
346
|
+
- Le batch (`client.send([...])`) envoie des messages indépendants sans lien entre eux.
|
|
347
|
+
- La Campaign regroupe tous les envois sous un identifiant commun (`campaignId`), ce qui permet de suivre les performances globales.
|
|
348
|
+
|
|
349
|
+
**Limite :** 1 000 destinataires par campagne. Pour dépasser cette limite, créez la campagne d'abord, puis envoyez des messages supplémentaires via `client.send([...], { campaignId })`.
|
|
350
|
+
|
|
351
|
+
### Envoyer une campagne
|
|
352
|
+
|
|
353
|
+
Envoi simple — le minimum requis.
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
const campaign = await client.campaigns.send({
|
|
357
|
+
name: 'Soldes de printemps 2026',
|
|
358
|
+
recipients: [
|
|
359
|
+
{ to: '33600000001' },
|
|
360
|
+
{ to: '33600000002' },
|
|
361
|
+
],
|
|
362
|
+
body: { type: 'TEXT', text: 'Profitez de nos offres exclusives ce week-end !' },
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
console.log(campaign.campaignId); // identifiant de la campagne
|
|
366
|
+
console.log(campaign.status); // "SCHEDULED" | "ONGOING" | "ENDED" | "CANCELED"
|
|
367
|
+
console.log(campaign.quantity); // nombre de destinataires
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
**Avec contenu riche.**
|
|
371
|
+
Le champ `body` accepte tous les types RCS : BASIC, TEXT, FILE, CARD, CAROUSEL.
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
await client.campaigns.send({
|
|
375
|
+
name: 'Catalogue printemps',
|
|
376
|
+
recipients: [{ to: '33600000001' }, { to: '33600000002' }],
|
|
377
|
+
body: {
|
|
378
|
+
type: 'CAROUSEL',
|
|
379
|
+
cardWidth: 'MEDIUM',
|
|
380
|
+
contents: [
|
|
381
|
+
{
|
|
382
|
+
title: 'Nouveauté A',
|
|
383
|
+
media: { fileUrl: 'https://example.com/a.jpg' },
|
|
384
|
+
suggestions: [{ type: 'REPLY', text: 'En savoir plus', postbackData: 'plus_a' }],
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
title: 'Nouveauté B',
|
|
388
|
+
media: { fileUrl: 'https://example.com/b.jpg' },
|
|
389
|
+
suggestions: [{ type: 'REPLY', text: 'En savoir plus', postbackData: 'plus_b' }],
|
|
390
|
+
},
|
|
391
|
+
],
|
|
392
|
+
},
|
|
393
|
+
});
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
**Campagne programmée.**
|
|
397
|
+
Renseignez `sentDate` pour programmer l'envoi (au minimum 30 minutes dans le futur).
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
await client.campaigns.send({
|
|
401
|
+
name: 'Rappel rendez-vous',
|
|
402
|
+
recipients: [{ to: '33600000001' }],
|
|
403
|
+
body: { type: 'TEXT', text: 'Rappel : votre rendez-vous est demain.' },
|
|
404
|
+
sentDate: '2026-04-20T09:00:00Z',
|
|
405
|
+
});
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
**Via un Channel spécifique.**
|
|
409
|
+
Nécessite les permissions Manager ou Administrator.
|
|
410
|
+
|
|
411
|
+
```typescript
|
|
412
|
+
await client.campaigns.send(
|
|
413
|
+
{ name: 'Promo', recipients: [{ to: '33600000001' }], body: { type: 'TEXT', text: 'Hello !' } },
|
|
414
|
+
{ channelId: 'da0e501d-4449-40b1-b1f9-3cd1e94031bd' }
|
|
415
|
+
);
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### Lister les campagnes
|
|
419
|
+
|
|
420
|
+
Retourne la première page avec les paramètres par défaut.
|
|
421
|
+
|
|
422
|
+
```typescript
|
|
423
|
+
const result = await client.campaigns.list();
|
|
424
|
+
|
|
425
|
+
console.log(result.totalCount); // nombre total de campagnes
|
|
426
|
+
console.log(result.items); // campagnes de la page courante
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
**Avec filtres.**
|
|
430
|
+
|
|
431
|
+
```typescript
|
|
432
|
+
const scheduled = await client.campaigns.list({
|
|
433
|
+
searchBy: { status: 'SCHEDULED' }, // "SCHEDULED" | "ONGOING" | "ENDED" | "CANCELED"
|
|
434
|
+
sortBy: { sentDate: 'DESC' },
|
|
435
|
+
});
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### Obtenir une campagne
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
const campaign = await client.campaigns.get('67c15045-1067-4588-ba3c-737cc5051438');
|
|
442
|
+
|
|
443
|
+
console.log(campaign.status); // "SCHEDULED" | "ONGOING" | "ENDED" | "CANCELED"
|
|
444
|
+
console.log(campaign.quantity); // nombre total de destinataires
|
|
445
|
+
console.log(campaign.statuses); // répartition des statuts de livraison
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### Modifier une campagne programmée
|
|
449
|
+
|
|
450
|
+
Les modifications s'appliquent à tous les messages unitaires de la campagne.
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
await client.campaigns.update('67c15045-1067-4588-ba3c-737cc5051438', {
|
|
454
|
+
name: 'Soldes printemps v2',
|
|
455
|
+
sentDate: '2026-04-21T10:00:00Z',
|
|
456
|
+
});
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
### Annuler une campagne programmée
|
|
461
|
+
|
|
462
|
+
> **Important :** `update()` et `cancel()` ne fonctionnent que sur les campagnes **programmées** (statut `SCHEDULED`). Une campagne déjà en cours ou terminée ne peut pas être modifiée ni annulée.
|
|
463
|
+
|
|
464
|
+
```typescript
|
|
465
|
+
await client.campaigns.cancel('67c15045-1067-4588-ba3c-737cc5051438');
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
---
|
|
469
|
+
|
|
470
|
+
## Contenu riche
|
|
471
|
+
|
|
472
|
+
RCS supporte cinq types de contenu, tous typés via l'union discriminée `RcsBody`. Le champ `type` détermine la structure exacte du corps : TypeScript vous guide à chaque étape grâce à l'autocomplétion.
|
|
473
|
+
|
|
474
|
+
### BASIC : texte brut (max 160 caractères, sans suggestions)
|
|
475
|
+
|
|
476
|
+
Idéal pour les notifications transactionnelles simples.
|
|
477
|
+
|
|
478
|
+
```typescript
|
|
479
|
+
body: { type: 'BASIC', text: 'Votre colis a été livré.' }
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
### TEXT : texte enrichi avec suggestions (max 3 072 caractères, jusqu'à 11 suggestions)
|
|
483
|
+
|
|
484
|
+
Permet d'ajouter des boutons d'action sous le message.
|
|
485
|
+
|
|
486
|
+
```typescript
|
|
487
|
+
body: {
|
|
488
|
+
type: 'TEXT',
|
|
489
|
+
text: 'Choisissez un créneau pour votre livraison :',
|
|
490
|
+
suggestions: [
|
|
491
|
+
{ type: 'REPLY', text: 'Matin', postbackData: 'creneau_matin' },
|
|
492
|
+
{ type: 'REPLY', text: 'Après-midi', postbackData: 'creneau_aprem' },
|
|
493
|
+
{ type: 'OPEN_URL', text: 'Voir le suivi', postbackData: 'suivi', url: 'https://example.com/suivi' },
|
|
494
|
+
],
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
### FILE : image, vidéo, audio ou PDF
|
|
499
|
+
|
|
500
|
+
```typescript
|
|
501
|
+
// Image
|
|
502
|
+
body: {
|
|
503
|
+
type: 'FILE',
|
|
504
|
+
fileUrl: 'https://example.com/banniere.jpg',
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Vidéo avec miniature
|
|
508
|
+
body: {
|
|
509
|
+
type: 'FILE',
|
|
510
|
+
fileUrl: 'https://example.com/presentation.mp4',
|
|
511
|
+
thumbnailUrl: 'https://example.com/miniature.jpg',
|
|
512
|
+
}
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
### CARD : carte enrichie avec média, titre et suggestions
|
|
516
|
+
|
|
517
|
+
```typescript
|
|
518
|
+
body: {
|
|
519
|
+
type: 'CARD',
|
|
520
|
+
orientation: 'VERTICAL', // ou "HORIZONTAL"
|
|
521
|
+
content: {
|
|
522
|
+
title: 'Soldes de printemps',
|
|
523
|
+
description: 'Jusqu\'à -50% ce week-end seulement.',
|
|
524
|
+
media: { fileUrl: 'https://example.com/banniere.jpg', height: 'MEDIUM' },
|
|
525
|
+
suggestions: [
|
|
526
|
+
{ type: 'OPEN_URL', text: 'Voir les offres', postbackData: 'voir', url: 'https://example.com/soldes' },
|
|
527
|
+
{ type: 'REPLY', text: 'Me rappeler', postbackData: 'rappel' },
|
|
528
|
+
],
|
|
529
|
+
},
|
|
530
|
+
suggestions: [
|
|
531
|
+
{ type: 'REPLY', text: 'Non merci', postbackData: 'non' },
|
|
532
|
+
],
|
|
533
|
+
}
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
### CAROUSEL : de 2 à 11 cartes défilantes
|
|
537
|
+
|
|
538
|
+
Idéal pour présenter plusieurs produits ou offres dans un seul message.
|
|
539
|
+
|
|
540
|
+
```typescript
|
|
541
|
+
body: {
|
|
542
|
+
type: 'CAROUSEL',
|
|
543
|
+
cardWidth: 'MEDIUM', // ou "SMALL"
|
|
544
|
+
contents: [
|
|
545
|
+
{
|
|
546
|
+
title: 'Produit A',
|
|
547
|
+
description: '29,99 €',
|
|
548
|
+
media: { fileUrl: 'https://example.com/produit-a.jpg' },
|
|
549
|
+
suggestions: [
|
|
550
|
+
{ type: 'REPLY', text: 'Ajouter au panier', postbackData: 'ajout_a' },
|
|
551
|
+
],
|
|
552
|
+
},
|
|
553
|
+
{
|
|
554
|
+
title: 'Produit B',
|
|
555
|
+
description: '49,99 €',
|
|
556
|
+
media: { fileUrl: 'https://example.com/produit-b.jpg' },
|
|
557
|
+
suggestions: [
|
|
558
|
+
{ type: 'REPLY', text: 'Ajouter au panier', postbackData: 'ajout_b' },
|
|
559
|
+
],
|
|
560
|
+
},
|
|
561
|
+
],
|
|
562
|
+
}
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
### Types de suggestions
|
|
566
|
+
|
|
567
|
+
Les suggestions peuvent être placées à deux niveaux :
|
|
568
|
+
|
|
569
|
+
- **Dans le contenu d'une carte** (`content.suggestions` pour `CARD`, `contents[].suggestions` pour chaque carte d'un `CAROUSEL`) : max 4 suggestions. Ces boutons sont attachés directement à la carte.
|
|
570
|
+
- **Sous l'ensemble du message** (`body.suggestions` pour `TEXT`, `CARD` et `CAROUSEL`) : max 11 suggestions. Ces boutons apparaissent en dessous du contenu principal.
|
|
571
|
+
|
|
572
|
+
Tous les types de suggestions partagent trois champs de base : `type` (identifiant du type), `text` (texte affiché sur le bouton, max 25 caractères) et `postbackData` (payload renvoyé à l'agent quand l'utilisateur appuie, max 2048 caractères, encodé en base64). La colonne "Champs supplémentaires" liste uniquement les champs propres à chaque type.
|
|
573
|
+
|
|
574
|
+
| Type | Description | Champs supplémentaires |
|
|
575
|
+
|------|-------------|------------------------|
|
|
576
|
+
| `REPLY` | Réponse rapide prédéfinie | aucun |
|
|
577
|
+
| `OPEN_URL` | Ouvre une URL | `url`, `webviewSize?` |
|
|
578
|
+
| `DIAL_PHONE` | Lance un appel | `phoneNumber` |
|
|
579
|
+
| `SHOW_LOCATION` | Affiche une position sur la carte | `latitude`, `longitude`, `label?` |
|
|
580
|
+
| `REQUEST_LOCATION` | Demande la position du destinataire | aucun |
|
|
581
|
+
| `CREATE_CALENDAR_EVENT` | Crée un événement calendrier | `startTime`, `endTime`, `title`, `description?` |
|
|
582
|
+
|
|
583
|
+
**REPLY**
|
|
584
|
+
|
|
585
|
+
```typescript
|
|
586
|
+
{ type: 'REPLY', text: 'Confirmer', postbackData: 'confirm' }
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
**OPEN_URL**
|
|
590
|
+
|
|
591
|
+
```typescript
|
|
592
|
+
{ type: 'OPEN_URL', text: 'Voir les offres', postbackData: 'offres', url: 'https://example.com/offres' }
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
**DIAL_PHONE**
|
|
596
|
+
|
|
597
|
+
```typescript
|
|
598
|
+
{ type: 'DIAL_PHONE', text: 'Appeler le support', postbackData: 'support', phoneNumber: '33800000000' }
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
**SHOW_LOCATION**
|
|
602
|
+
|
|
603
|
+
```typescript
|
|
604
|
+
{ type: 'SHOW_LOCATION', text: 'Notre magasin', postbackData: 'magasin', latitude: 48.8566, longitude: 2.3522 }
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
**REQUEST_LOCATION**
|
|
608
|
+
|
|
609
|
+
```typescript
|
|
610
|
+
{ type: 'REQUEST_LOCATION', text: 'Partager ma position', postbackData: 'location' }
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
**CREATE_CALENDAR_EVENT**
|
|
614
|
+
|
|
615
|
+
```typescript
|
|
616
|
+
{
|
|
617
|
+
type: 'CREATE_CALENDAR_EVENT',
|
|
618
|
+
text: 'Ajouter à mon agenda',
|
|
619
|
+
postbackData: 'agenda',
|
|
620
|
+
title: 'Rendez-vous smsmode',
|
|
621
|
+
startTime: '2026-06-01T10:00:00Z',
|
|
622
|
+
endTime: '2026-06-01T11:00:00Z',
|
|
623
|
+
}
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
---
|
|
627
|
+
|
|
628
|
+
## Templates RCS
|
|
629
|
+
|
|
630
|
+
smsmode propose une bibliothèque de templates RCS prêts à l'emploi (notifications de livraison, promotions, flows de discussion...) pour vous aider à démarrer.
|
|
631
|
+
|
|
632
|
+
**[Découvrir les templates RCS smsmode →](https://www.smsmode.com/rcs-template/)**
|
|
633
|
+
|
|
634
|
+
---
|
|
635
|
+
|
|
636
|
+
## Webhooks
|
|
637
|
+
|
|
638
|
+
Les webhooks sont des requêtes de callback que smsmode envoie vers votre serveur pour vous informer d'événements liés à vos messages RCS.
|
|
639
|
+
|
|
640
|
+
Il existe deux types de notifications :
|
|
641
|
+
|
|
642
|
+
**DLR (Delivery Report)** : smsmode vous notifie chaque fois que le statut d'un message RCS change (envoyé, livré, lu, en erreur...). Configuré via `callbackUrlStatus`.
|
|
643
|
+
|
|
644
|
+
**MO (Mobile Originated)** : smsmode vous notifie quand un utilisateur répond à l'un de vos messages RCS. Le corps du message MO est systématiquement de type `TEXT`. Configuré via `callbackUrlMo`.
|
|
645
|
+
|
|
646
|
+
Les deux types partagent la même structure de base (ressource Message) et se distinguent par le champ `direction` : `MT` pour un DLR, `MO` pour un message entrant. Le champ `status` n'est présent que dans un DLR.
|
|
647
|
+
|
|
648
|
+
### Configurer l'URL de réception
|
|
649
|
+
|
|
650
|
+
1. **Global (recommandé) :** via l'interface smsmode sous **Settings > Webhooks**. C'est la façon la plus simple : vous configurez une URL une seule fois et elle s'applique à tous vos envois, sans toucher au code.
|
|
651
|
+
2. **Par channel :** via l'API Channel, si vous avez plusieurs channels et souhaitez une URL différente par channel.
|
|
652
|
+
3. **Par message :** champs `callbackUrlStatus` (DLR) et `callbackUrlMo` (MO) directement dans le `send()`, pour surcharger ponctuellement la configuration globale.
|
|
653
|
+
|
|
654
|
+
### Mécanisme de retry
|
|
655
|
+
|
|
656
|
+
Si votre endpoint ne répond pas avec un statut 2xx, smsmode retente la notification jusqu'à 6 fois :
|
|
657
|
+
|
|
658
|
+
| Tentative | Délai depuis la précédente |
|
|
659
|
+
|-----------|---------------------------|
|
|
660
|
+
| 1 | 30 secondes |
|
|
661
|
+
| 2 | 2 minutes |
|
|
662
|
+
| 3 | 10 minutes |
|
|
663
|
+
| 4 | 1 heure |
|
|
664
|
+
| 5 | 5 heures |
|
|
665
|
+
| 6 | 24 heures |
|
|
666
|
+
|
|
667
|
+
Après la 6ème tentative, la notification est abandonnée. Votre endpoint doit gérer les doublons en dédupliquant sur `messageId`.
|
|
668
|
+
|
|
669
|
+
### Recevoir et identifier un webhook
|
|
670
|
+
|
|
671
|
+
Le SDK expose trois utilitaires pour traiter les webhooks de façon typée :
|
|
672
|
+
|
|
673
|
+
- `parseWebhookPayload(body)` : valide que le payload est bien formé et retourne un type discriminé
|
|
674
|
+
- `isDeliveryReport(payload)` : type guard : retourne `true` si `direction === 'MT'`
|
|
675
|
+
- `isIncomingMessage(payload)` : type guard : retourne `true` si `direction === 'MO'`
|
|
676
|
+
|
|
677
|
+
```typescript
|
|
678
|
+
import express from 'express';
|
|
679
|
+
import {
|
|
680
|
+
parseWebhookPayload,
|
|
681
|
+
isDeliveryReport,
|
|
682
|
+
isIncomingMessage,
|
|
683
|
+
} from '@smsmode/rcs';
|
|
684
|
+
|
|
685
|
+
const app = express();
|
|
686
|
+
app.use(express.json());
|
|
687
|
+
|
|
688
|
+
app.post('/webhook/rcs', (req, res) => {
|
|
689
|
+
try {
|
|
690
|
+
const payload = parseWebhookPayload(req.body);
|
|
691
|
+
|
|
692
|
+
if (isDeliveryReport(payload)) {
|
|
693
|
+
// Rapport de livraison (DLR) : direction: 'MT'
|
|
694
|
+
// payload.status.value : "ENROUTE", "DELIVERED", "READ", "UNDELIVERABLE"...
|
|
695
|
+
console.log(`Message ${payload.messageId} : statut : ${payload.status.value}`);
|
|
696
|
+
|
|
697
|
+
} else if (isIncomingMessage(payload)) {
|
|
698
|
+
// Message entrant (MO) : direction: 'MO'
|
|
699
|
+
// payload.body est toujours de type RcsTextBody
|
|
700
|
+
console.log(`Réponse de ${payload.recipient.to} : ${payload.body.text}`);
|
|
701
|
+
|
|
702
|
+
// originMessageId identifie le message MT auquel ce MO répond
|
|
703
|
+
console.log(`En réponse au message : ${payload.originMessageId}`);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Toujours répondre avec un statut 2xx pour acquitter la notification
|
|
707
|
+
// Sans cela, smsmode considérera la notification comme échouée et retentera
|
|
708
|
+
res.sendStatus(200);
|
|
709
|
+
|
|
710
|
+
} catch {
|
|
711
|
+
// parseWebhookPayload lève une ValidationError si le payload est invalide
|
|
712
|
+
res.sendStatus(400);
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
---
|
|
718
|
+
|
|
719
|
+
## Patterns
|
|
720
|
+
|
|
721
|
+
### Pagination complète
|
|
722
|
+
|
|
723
|
+
La réponse paginée contient `totalCount` (total global) et `items` (page courante). Pour parcourir toutes les pages :
|
|
724
|
+
|
|
725
|
+
```typescript
|
|
726
|
+
let page = 1;
|
|
727
|
+
let hasMore = true;
|
|
728
|
+
const allMessages: RcsMessage[] = [];
|
|
729
|
+
|
|
730
|
+
while (hasMore) {
|
|
731
|
+
const result = await client.list({ page, pageSize: 100 });
|
|
732
|
+
|
|
733
|
+
allMessages.push(...result.items);
|
|
734
|
+
|
|
735
|
+
// S'il y a moins d'items que la taille de page, on est sur la dernière page
|
|
736
|
+
hasMore = result.items.length === result.pageSize;
|
|
737
|
+
page++;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
console.log(`${allMessages.length} messages récupérés`);
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
### Gestion du rate limit
|
|
744
|
+
|
|
745
|
+
Le SDK lève une `RateLimitError` sur les réponses HTTP 429. L'erreur expose `retryAfter`, la durée d'attente recommandée en secondes fournie par l'API. Vous pouvez implémenter un retry automatique selon votre besoin :
|
|
746
|
+
|
|
747
|
+
```typescript
|
|
748
|
+
import { RcsSendPayload, RateLimitError } from '@smsmode/rcs';
|
|
749
|
+
|
|
750
|
+
async function sendWithRetry(payload: RcsSendPayload, maxRetries = 3) {
|
|
751
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
752
|
+
try {
|
|
753
|
+
return await client.send(payload);
|
|
754
|
+
} catch (error) {
|
|
755
|
+
if (error instanceof RateLimitError && attempt < maxRetries - 1) {
|
|
756
|
+
const waitMs = (error.retryAfter ?? 60) * 1000;
|
|
757
|
+
await new Promise(resolve => setTimeout(resolve, waitMs));
|
|
758
|
+
continue;
|
|
759
|
+
}
|
|
760
|
+
throw error; // autres erreurs ou dernière tentative : on laisse remonter
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
---
|
|
767
|
+
|
|
768
|
+
## Gestion des erreurs
|
|
769
|
+
|
|
770
|
+
> **Crédit insuffisant :** `client.send()` ne lève pas d'erreur quand le crédit est insuffisant. L'API répond `200` avec le statut `ENROUTE`, puis vous envoie un DLR `UNDELIVERABLE`. Si vous ne traitez pas vos DLR, vous ne le saurez jamais.
|
|
771
|
+
|
|
772
|
+
Le SDK transforme chaque réponse d'erreur HTTP en une classe TypeScript typée. La distinction centrale est la présence ou non d'un body d'erreur structuré smsmode dans la réponse, pas le code HTTP lui-même. Cela détermine ce que vous pouvez réellement faire avec l'erreur.
|
|
773
|
+
|
|
774
|
+
Hiérarchie des classes d'erreur :
|
|
775
|
+
|
|
776
|
+
```
|
|
777
|
+
Error
|
|
778
|
+
└── RcsError
|
|
779
|
+
├── SmsModeApiError : body smsmode structuré présent
|
|
780
|
+
│ ├── AuthError : HTTP 401
|
|
781
|
+
│ └── RateLimitError : HTTP 429 (+ retryAfter)
|
|
782
|
+
└── SmsModeHttpError : pas de body structuré (5xx, gateway timeout, HTML d'un reverse proxy)
|
|
783
|
+
```
|
|
784
|
+
|
|
785
|
+
```typescript
|
|
786
|
+
import {
|
|
787
|
+
SmsmodeRcsClient,
|
|
788
|
+
SmsModeApiError,
|
|
789
|
+
SmsModeHttpError,
|
|
790
|
+
AuthError,
|
|
791
|
+
RateLimitError,
|
|
792
|
+
} from '@smsmode/rcs';
|
|
793
|
+
|
|
794
|
+
const client = new SmsmodeRcsClient({ apiKey: process.env.SMSMODE_API_KEY });
|
|
795
|
+
|
|
796
|
+
try {
|
|
797
|
+
|
|
798
|
+
await client.send({
|
|
799
|
+
recipient: { to: '33600000001' },
|
|
800
|
+
body: { type: 'TEXT', text: 'Bonjour !' },
|
|
801
|
+
});
|
|
802
|
+
console.log(message.messageId);
|
|
803
|
+
console.log(message.status.value);
|
|
804
|
+
|
|
805
|
+
} catch (error) {
|
|
806
|
+
if (error instanceof AuthError) {
|
|
807
|
+
// Clé API invalide : vérifier process.env.SMSMODE_API_KEY
|
|
808
|
+
console.error('Clé API invalide');
|
|
809
|
+
|
|
810
|
+
} else if (error instanceof RateLimitError) {
|
|
811
|
+
// Trop de requêtes : attendre retryAfter secondes
|
|
812
|
+
const wait = error.retryAfter ?? 60;
|
|
813
|
+
console.error(`Rate limit atteint, retry dans ${wait}s`);
|
|
814
|
+
|
|
815
|
+
} else if (error instanceof SmsModeApiError) {
|
|
816
|
+
console.error(" httpStatus :", error.httpStatus);
|
|
817
|
+
console.error(" errorCode :", error.errorCode);
|
|
818
|
+
console.error(" message :", error.message);
|
|
819
|
+
console.error(" detail :", error.detail);
|
|
820
|
+
console.error(" docsUrl :", error.docsUrl);
|
|
821
|
+
|
|
822
|
+
// Branchement fin possible sur error.errorCode :
|
|
823
|
+
switch (error.errorCode) {
|
|
824
|
+
case '400.029': // format E.164 invalide
|
|
825
|
+
// etc.
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
} else if (error instanceof SmsModeHttpError) {
|
|
829
|
+
// Pas de body structuré : 5xx, gateway timeout, reverse proxy
|
|
830
|
+
console.error(`HTTP ${error.httpStatus} ${error.statusText} : retry recommandé`);
|
|
831
|
+
|
|
832
|
+
} else {
|
|
833
|
+
throw error;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
```
|
|
837
|
+
|
|
838
|
+
Classes d'erreur exposées par le SDK :
|
|
839
|
+
|
|
840
|
+
| Classe | Quand ? | Propriétés |
|
|
841
|
+
|--------|---------|------------|
|
|
842
|
+
| `SmsModeApiError` | L'API smsmode a répondu avec un body d'erreur structuré (4xx principalement) | `httpStatus`, `title`, `message`, `detail`, `errorCode`, `docsUrl`, `details` |
|
|
843
|
+
| `AuthError` | HTTP 401, clé API invalide | Hérite de `SmsModeApiError` |
|
|
844
|
+
| `RateLimitError` | HTTP 429 avec body structuré | Hérite de `SmsModeApiError` + `retryAfter?: number` |
|
|
845
|
+
| `SmsModeHttpError` | Réponse HTTP en erreur sans body structuré (5xx, gateway timeout, HTML d'un reverse proxy) | `httpStatus`, `statusText` |
|
|
846
|
+
|
|
847
|
+
Propriétés de `SmsModeApiError` :
|
|
848
|
+
|
|
849
|
+
| Propriété | Type | Description |
|
|
850
|
+
|-----------|------|-------------|
|
|
851
|
+
| `httpStatus` | `number` | Code HTTP (ex : `400`) |
|
|
852
|
+
| `title` | `string` | Titre HTTP humain (ex : `"Bad Request"`) |
|
|
853
|
+
| `message` | `string` | Description courte de l'erreur |
|
|
854
|
+
| `detail` | `string` | Contrainte précise non respectée |
|
|
855
|
+
| `errorCode` | `string` | Code métier smsmode (ex : `"400.029"`) |
|
|
856
|
+
| `docsUrl` | `string` | URL vers la documentation du code d'erreur |
|
|
857
|
+
| `details` | `unknown` | Body brut complet de la réponse |
|
|
858
|
+
|
|
859
|
+
Tous les champs de `SmsModeApiError` sont garantis non-undefined. La présence du body structuré smsmode est vérifiée avant de lancer cette classe.
|
|
860
|
+
|
|
861
|
+
Pour la liste exhaustive des codes d'erreur métier smsmode, consultez la [documentation officielle des codes de statut](https://dev.smsmode.com/sms/status-codes/).
|
|
862
|
+
|
|
863
|
+
---
|
|
864
|
+
|
|
865
|
+
## Référence API
|
|
866
|
+
|
|
867
|
+
### `SmsmodeRcsClient`
|
|
868
|
+
|
|
869
|
+
| Option | Type | Défaut | Description |
|
|
870
|
+
|--------|------|--------|-------------|
|
|
871
|
+
| `apiKey` | `string` | — | **Obligatoire.** Clé API smsmode |
|
|
872
|
+
| `timeout` | `number` | `10000` | Timeout en millisecondes |
|
|
873
|
+
|
|
874
|
+
### `client` : Messages
|
|
875
|
+
|
|
876
|
+
| Méthode | Paramètres | Retour | Description |
|
|
877
|
+
|---------|------------|--------|-------------|
|
|
878
|
+
| `send(payload, options?)` | `RcsSendPayload \| RcsSendPayload[]` | `RcsMessage \| RcsMessage[]` | Envoyer un message unitaire ou batch (max 1 000) |
|
|
879
|
+
| `list(params?, options?)` | `RcsListParams` | `PaginatedResponse<RcsMessage>` | Lister les messages avec filtres et pagination |
|
|
880
|
+
| `get(messageId, options?)` | `string` | `RcsMessage` | Obtenir un message par son ID |
|
|
881
|
+
| `update(messageId, payload, options?)` | `string, RcsUpdatePayload` | `RcsMessage` | Modifier un message programmé |
|
|
882
|
+
| `cancel(messageId, options?)` | `string` | `void` | Annuler un message programmé |
|
|
883
|
+
|
|
884
|
+
### `client.campaigns` : Campagnes
|
|
885
|
+
|
|
886
|
+
| Méthode | Paramètres | Retour | Description |
|
|
887
|
+
|---------|------------|--------|-------------|
|
|
888
|
+
| `send(payload, options?)` | `RcsCampaignSendPayload` | `RcsCampaign` | Envoyer ou programmer une campagne (max 1 000 destinataires) |
|
|
889
|
+
| `list(params?, options?)` | `RcsCampaignListParams` | `PaginatedResponse<RcsCampaign>` | Lister les campagnes avec filtres et pagination |
|
|
890
|
+
| `get(campaignId, options?)` | `string` | `RcsCampaign` | Obtenir une campagne par son ID |
|
|
891
|
+
| `update(campaignId, payload, options?)` | `string, RcsCampaignUpdatePayload` | `RcsCampaign` | Modifier une campagne programmée |
|
|
892
|
+
| `cancel(campaignId, options?)` | `string` | `void` | Annuler une campagne programmée |
|
|
893
|
+
|
|
894
|
+
### Webhooks
|
|
895
|
+
|
|
896
|
+
| Fonction | Paramètres | Retour | Description |
|
|
897
|
+
|----------|------------|--------|-------------|
|
|
898
|
+
| `parseWebhookPayload(body)` | `unknown` | `RcsWebhookPayload` | Valide et type-asserte un payload entrant. Lève une `ValidationError` si invalide. |
|
|
899
|
+
| `isDeliveryReport(payload)` | `RcsWebhookPayload` | `boolean` | Type guard : `true` si DLR (`direction: 'MT'`) |
|
|
900
|
+
| `isIncomingMessage(payload)` | `RcsWebhookPayload` | `boolean` | Type guard : `true` si MO (`direction: 'MO'`) |
|
|
901
|
+
|
|
902
|
+
---
|
|
903
|
+
|
|
904
|
+
|
|
905
|
+
## Ressources
|
|
906
|
+
|
|
907
|
+
- [Documentation API smsmode](https://dev.smsmode.com) : référence complète de l'API REST
|
|
908
|
+
- [Templates RCS smsmode](https://www.smsmode.com/rcs-template/) : bibliothèque de templates et flows de discussion
|
|
909
|
+
- [Collection Postman](https://www.postman.com/smsmode/smsmode-public) : pour tester les endpoints manuellement
|
|
910
|
+
- [Espace client smsmode](https://ui.smsmode.com) : gestion des clés API, crédits, webhooks
|
|
911
|
+
- [Support](https://support.smsmode.com)
|
|
912
|
+
|
|
913
|
+
---
|
|
914
|
+
|
|
915
|
+
## Changelog
|
|
916
|
+
|
|
917
|
+
Voir [CHANGELOG.md](./CHANGELOG.md) pour l'historique des versions.
|
|
918
|
+
|
|
919
|
+
---
|
|
920
|
+
|
|
921
|
+
## Licence
|
|
922
|
+
|
|
923
|
+
MIT — voir [LICENSE](./LICENSE)
|