@groundbrick/email-service 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +410 -0
- package/package.json +68 -0
package/README.md
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
# @groundbrick/email-service
|
|
2
|
+
|
|
3
|
+
Serviço de email plugável com suporte a múltiplos provedores (GatewayAPI, SMTP, etc.) para TypeScript/Node.js.
|
|
4
|
+
|
|
5
|
+
## Características
|
|
6
|
+
|
|
7
|
+
- 📧 **Múltiplos Providers**: GatewayAPI, Sweego, Mock, Redirect
|
|
8
|
+
- 🔄 **Múltiplas Instâncias**: Diferente do sms-service, permite múltiplas configurações
|
|
9
|
+
- 🎯 **Webhooks**: Suporte completo para webhooks de entrega
|
|
10
|
+
- 📝 **Templates**: Variáveis dinâmicas em HTML/texto/subject
|
|
11
|
+
- 📊 **Tracking**: Rastreamento de aberturas e cliques (GatewayAPI)
|
|
12
|
+
- 📎 **Anexos**: Suporte a anexos com base64
|
|
13
|
+
- 🔒 **Type-Safe**: 100% TypeScript com tipos completos
|
|
14
|
+
- 🧪 **Testável**: Mock e Redirect providers para desenvolvimento
|
|
15
|
+
|
|
16
|
+
## Instalação
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @groundbrick/email-service
|
|
20
|
+
# ou
|
|
21
|
+
pnpm add @groundbrick/email-service
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Uso Básico
|
|
25
|
+
|
|
26
|
+
### GatewayAPI Provider (Produção)
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { EmailService } from '@groundbrick/email-service';
|
|
30
|
+
|
|
31
|
+
const emailService = new EmailService({
|
|
32
|
+
provider: 'gatewayapi',
|
|
33
|
+
config: {
|
|
34
|
+
token: process.env.GATEWAYAPI_EMAIL_TOKEN!,
|
|
35
|
+
defaultFrom: {
|
|
36
|
+
address: 'noreply@example.com',
|
|
37
|
+
name: 'My App'
|
|
38
|
+
},
|
|
39
|
+
webhookSecret: process.env.GATEWAYAPI_WEBHOOK_SECRET // opcional
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Enviar email simples
|
|
44
|
+
const result = await emailService.send({
|
|
45
|
+
to: { address: 'user@example.com', name: 'John Doe' },
|
|
46
|
+
subject: 'Welcome!',
|
|
47
|
+
html: '<h1>Hello!</h1><p>Welcome to our app.</p>',
|
|
48
|
+
text: 'Hello! Welcome to our app.'
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
console.log(result.messageId); // "123456"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Sweego Provider (Produção)
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { EmailService } from '@groundbrick/email-service';
|
|
58
|
+
|
|
59
|
+
const sweegoEmailService = new EmailService({
|
|
60
|
+
provider: 'sweego',
|
|
61
|
+
config: {
|
|
62
|
+
apiKey: process.env.SWEEGO_API_KEY!, // ou defina accessToken para usar Bearer
|
|
63
|
+
defaultFrom: {
|
|
64
|
+
address: 'noreply@example.com',
|
|
65
|
+
name: 'My App'
|
|
66
|
+
},
|
|
67
|
+
clientId: process.env.SWEEGO_CLIENT_ID, // opcional, repassa para o cabeçalho client-id
|
|
68
|
+
webhookSecret: process.env.SWEEGO_WEBHOOK_SECRET // opcional, Base64 para validar webhooks
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
await sweegoEmailService.send({
|
|
73
|
+
to: { address: 'user@example.com', name: 'John Doe' },
|
|
74
|
+
subject: 'Welcome!',
|
|
75
|
+
html: '<h1>Hello!</h1><p>Welcome to our app.</p>',
|
|
76
|
+
metadata: {
|
|
77
|
+
sweego: {
|
|
78
|
+
campaignId: 'welcome-2025',
|
|
79
|
+
campaignTags: ['welcome'],
|
|
80
|
+
dryRun: false
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Use a chave `metadata.sweego` para acessar recursos específicos da API da Sweego
|
|
87
|
+
(campanhas, headers customizados, list-unsubscribe, variáveis por destinatário, etc.).
|
|
88
|
+
Veja a seção [Sweego](#sweego) para a lista completa dos campos suportados.
|
|
89
|
+
|
|
90
|
+
### Mock Provider (Testes)
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
const emailService = new EmailService({
|
|
94
|
+
provider: 'mock',
|
|
95
|
+
config: {
|
|
96
|
+
defaultFrom: { address: 'test@example.com' },
|
|
97
|
+
simulateDelay: 100, // ms
|
|
98
|
+
simulateFailures: false // ou true para 10% de falhas
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const result = await emailService.send({...});
|
|
103
|
+
|
|
104
|
+
// Acessar emails enviados (apenas Mock)
|
|
105
|
+
import { MockEmailProvider } from '@groundbrick/email-service';
|
|
106
|
+
const mock = emailService['provider'] as MockEmailProvider;
|
|
107
|
+
const sentEmails = mock.getSentEmails();
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Redirect Provider (Desenvolvimento)
|
|
111
|
+
|
|
112
|
+
Redireciona TODOS os emails para um endereço específico:
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
const emailService = new EmailService({
|
|
116
|
+
provider: 'redirect',
|
|
117
|
+
config: {
|
|
118
|
+
redirectTo: 'dev@example.com',
|
|
119
|
+
defaultFrom: { address: 'noreply@example.com' },
|
|
120
|
+
addOriginalToSubject: true // adiciona destinatários originais no subject
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Este email será redirecionado para dev@example.com
|
|
125
|
+
await emailService.send({
|
|
126
|
+
to: { address: 'customer@example.com' },
|
|
127
|
+
subject: 'Order Confirmation',
|
|
128
|
+
html: '<p>Your order is confirmed!</p>'
|
|
129
|
+
});
|
|
130
|
+
// Subject real: "[REDIRECTED] Order Confirmation (Original: customer@example.com)"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Funcionalidades Avançadas
|
|
134
|
+
|
|
135
|
+
### Múltiplos Destinatários (CC, BCC)
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
await emailService.send({
|
|
139
|
+
to: [
|
|
140
|
+
{ address: 'user1@example.com', name: 'User 1' },
|
|
141
|
+
{ address: 'user2@example.com' }
|
|
142
|
+
],
|
|
143
|
+
cc: [{ address: 'manager@example.com' }],
|
|
144
|
+
bcc: [{ address: 'archive@example.com' }],
|
|
145
|
+
subject: 'Meeting Tomorrow',
|
|
146
|
+
html: '<p>Don\'t forget our meeting!</p>'
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Anexos
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
await emailService.send({
|
|
154
|
+
to: { address: 'user@example.com' },
|
|
155
|
+
subject: 'Invoice #1234',
|
|
156
|
+
html: '<p>Your invoice is attached.</p>',
|
|
157
|
+
attachments: [
|
|
158
|
+
{
|
|
159
|
+
filename: 'invoice.pdf',
|
|
160
|
+
content: pdfBuffer, // Buffer ou string
|
|
161
|
+
contentType: 'application/pdf'
|
|
162
|
+
}
|
|
163
|
+
]
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Templates com Variáveis
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
await emailService.send({
|
|
171
|
+
to: { address: 'user@example.com' },
|
|
172
|
+
subject: 'Hello %firstname!',
|
|
173
|
+
html: '<p>Hi %firstname %lastname, your code is %code.</p>',
|
|
174
|
+
template: {
|
|
175
|
+
variables: {
|
|
176
|
+
firstname: 'John',
|
|
177
|
+
lastname: 'Doe',
|
|
178
|
+
code: '123456'
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Tracking de Opens/Clicks (GatewayAPI)
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
await emailService.send({
|
|
188
|
+
to: { address: 'user@example.com' },
|
|
189
|
+
subject: 'Newsletter',
|
|
190
|
+
html: '<p>Check out our <a href="https://example.com">website</a>!</p>',
|
|
191
|
+
tracking: {
|
|
192
|
+
opens: true,
|
|
193
|
+
clicks: true
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Metadados específicos do Sweego
|
|
199
|
+
|
|
200
|
+
Quando estiver usando o provider `sweego`, utilize `metadata.sweego` para ativar
|
|
201
|
+
opções exclusivas da API:
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
await sweegoEmailService.send({
|
|
205
|
+
to: { address: 'user@example.com' },
|
|
206
|
+
subject: 'Welcome',
|
|
207
|
+
html: '<p>Hello!</p>',
|
|
208
|
+
metadata: {
|
|
209
|
+
sweego: {
|
|
210
|
+
campaignId: 'welcome-2025',
|
|
211
|
+
campaignTags: ['welcome', 'prod'],
|
|
212
|
+
campaignType: 'transac',
|
|
213
|
+
headers: { 'x-custom-header': 'value' },
|
|
214
|
+
listUnsub: { method: 'one-click', value: 'mailto:unsubscribe@example.com,https://example.com/unsub' },
|
|
215
|
+
dryRun: false,
|
|
216
|
+
expires: '2025-12-31T23:59:59Z',
|
|
217
|
+
clientId: 'my-client',
|
|
218
|
+
variables: { name: 'John' }
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Campos suportados em `metadata.sweego`:
|
|
225
|
+
|
|
226
|
+
- `campaignId`, `campaignTags` e `campaignType`
|
|
227
|
+
- `headers` (até 5 cabeçalhos customizados)
|
|
228
|
+
- `listUnsub` (`{ method?: 'mailto' | 'one-click'; value: string }`)
|
|
229
|
+
- `dryRun` e `expires`
|
|
230
|
+
- `variables` ou `perRecipientVariables` (array de objetos na mesma ordem dos destinatários)
|
|
231
|
+
- `clientId`, `channel` e `provider` (casos multi-provedor na mesma conta)
|
|
232
|
+
|
|
233
|
+
### Delivery Reports
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
const result = await emailService.send({...});
|
|
237
|
+
|
|
238
|
+
// Consultar status depois
|
|
239
|
+
const report = await emailService.getDeliveryReport(result.messageId!);
|
|
240
|
+
|
|
241
|
+
console.log(report?.status); // 'delivered', 'bounced', 'failed', etc.
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Webhooks
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
// Middleware para manter o raw body (necessário para Sweego)
|
|
248
|
+
const rawJson = express.json({
|
|
249
|
+
verify: (req, _res, buf) => {
|
|
250
|
+
req.rawBody = buf.toString('utf8');
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
app.post('/api/webhooks/email', rawJson, (req, res) => {
|
|
255
|
+
const signature = req.headers['authorization'] || '';
|
|
256
|
+
|
|
257
|
+
const validation = emailService.validateWebhook(req.body, signature, {
|
|
258
|
+
headers: req.headers as Record<string, string | string[]>,
|
|
259
|
+
rawBody: req.rawBody // Sweego precisa do corpo original para validar o HMAC
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
if (validation.isValid && validation.payload) {
|
|
263
|
+
const report = validation.payload;
|
|
264
|
+
console.log(`Email ${report.messageId} status: ${report.status}`);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
res.sendStatus(200);
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
> 💡 **Sweego:** defina `webhookSecret` (string em Base64) no config e garanta que as
|
|
272
|
+
> headers `webhook-id`, `webhook-timestamp` e `webhook-signature` sejam
|
|
273
|
+
> repassadas para `validateWebhook` através do parâmetro `headers`.
|
|
274
|
+
|
|
275
|
+
## Configuração por Ambiente
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
const emailService = new EmailService({
|
|
279
|
+
provider: process.env.NODE_ENV === 'production' ? 'gatewayapi' : 'redirect',
|
|
280
|
+
config: process.env.NODE_ENV === 'production'
|
|
281
|
+
? {
|
|
282
|
+
token: process.env.GATEWAYAPI_EMAIL_TOKEN!,
|
|
283
|
+
defaultFrom: { address: 'noreply@myapp.com', name: 'MyApp' }
|
|
284
|
+
}
|
|
285
|
+
: {
|
|
286
|
+
redirectTo: process.env.DEV_EMAIL || 'dev@myapp.com',
|
|
287
|
+
defaultFrom: { address: 'noreply@myapp.com', name: 'MyApp [DEV]' }
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## API Reference
|
|
293
|
+
|
|
294
|
+
### EmailService
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
class EmailService {
|
|
298
|
+
constructor(config: EmailConfig)
|
|
299
|
+
send(message: EmailMessage): Promise<EmailResult>
|
|
300
|
+
getDeliveryReport(messageId: string): Promise<DeliveryReport | null>
|
|
301
|
+
validateWebhook(
|
|
302
|
+
payload: any,
|
|
303
|
+
signature?: string,
|
|
304
|
+
options?: WebhookValidationOptions
|
|
305
|
+
): WebhookValidationResult
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
interface WebhookValidationOptions {
|
|
311
|
+
headers?: Record<string, string | string[] | undefined>;
|
|
312
|
+
rawBody?: string;
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### EmailMessage
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
interface EmailMessage {
|
|
320
|
+
to: EmailAddress | EmailAddress[]
|
|
321
|
+
from?: EmailAddress
|
|
322
|
+
cc?: EmailAddress[]
|
|
323
|
+
bcc?: EmailAddress[]
|
|
324
|
+
replyTo?: EmailAddress
|
|
325
|
+
subject: string
|
|
326
|
+
text?: string
|
|
327
|
+
html?: string
|
|
328
|
+
attachments?: EmailAttachment[]
|
|
329
|
+
template?: EmailTemplate
|
|
330
|
+
tracking?: EmailTracking
|
|
331
|
+
metadata?: Record<string, any>
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### EmailStatus
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
enum EmailStatus {
|
|
339
|
+
QUEUED = 'queued',
|
|
340
|
+
SENT = 'sent',
|
|
341
|
+
DELIVERED = 'delivered',
|
|
342
|
+
BOUNCED = 'bounced',
|
|
343
|
+
FAILED = 'failed',
|
|
344
|
+
DEFERRED = 'deferred',
|
|
345
|
+
BLOCKED = 'blocked'
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## GatewayAPI
|
|
350
|
+
|
|
351
|
+
Para usar o GatewayAPI:
|
|
352
|
+
|
|
353
|
+
1. Obtenha um token em [GatewayAPI](https://gatewayapi.com)
|
|
354
|
+
2. Configure webhook (opcional) para receber notificações de entrega
|
|
355
|
+
3. Use o token no config:
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
const emailService = new EmailService({
|
|
359
|
+
provider: 'gatewayapi',
|
|
360
|
+
config: {
|
|
361
|
+
token: 'your-token-here',
|
|
362
|
+
baseUrl: 'https://gatewayapi.com', // opcional
|
|
363
|
+
webhookSecret: 'your-webhook-secret', // opcional, para validação JWT
|
|
364
|
+
defaultFrom: { address: 'noreply@example.com' }
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Limites GatewayAPI
|
|
370
|
+
|
|
371
|
+
- Anexos: Máximo 5MB total
|
|
372
|
+
- Rate limits: Conforme seu plano
|
|
373
|
+
|
|
374
|
+
## Sweego
|
|
375
|
+
|
|
376
|
+
1. Crie um API Key ou OAuth Client em [app.sweego.io](https://app.sweego.io/)
|
|
377
|
+
2. Configure e verifique um subdomínio dedicado para envio
|
|
378
|
+
3. Copie o segredo do webhook (menu **Webhooks**) e salve a versão Base64 no
|
|
379
|
+
`webhookSecret`
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
const emailService = new EmailService({
|
|
383
|
+
provider: 'sweego',
|
|
384
|
+
config: {
|
|
385
|
+
apiKey: process.env.SWEEGO_API_KEY!, // ou use accessToken
|
|
386
|
+
clientId: process.env.SWEEGO_CLIENT_ID,
|
|
387
|
+
defaultFrom: { address: 'noreply@example.com', name: 'My App' },
|
|
388
|
+
webhookSecret: process.env.SWEEGO_WEBHOOK_SECRET
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### Opções suportadas
|
|
394
|
+
|
|
395
|
+
- `apiKey` **ou** `accessToken` (Bearer)
|
|
396
|
+
- `clientId` para definir o cabeçalho `client-id`
|
|
397
|
+
- `baseUrl`, `channel` e `providerSlug` (casos de sandbox)
|
|
398
|
+
- `webhookSecret` (Base64) para validar `webhook-id/webhook-timestamp/webhook-signature`
|
|
399
|
+
|
|
400
|
+
Use `metadata.sweego` para configurar campanhas, headers, dry-run, variáveis e
|
|
401
|
+
list-unsubscribe. Documentação oficial: [envio por API](https://learn.sweego.io/docs/sending/how_to_send_email_by_api) e
|
|
402
|
+
[assinatura de webhook](https://learn.sweego.io/docs/webhooks/webhook_signature).
|
|
403
|
+
|
|
404
|
+
## Migração do Service-Base
|
|
405
|
+
|
|
406
|
+
Veja [MIGRATION.md](./MIGRATION.md) para guia completo de migração do antigo `@groundbrick/service-base`.
|
|
407
|
+
|
|
408
|
+
## Licença
|
|
409
|
+
|
|
410
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@groundbrick/email-service",
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"description": "Serviço de Email plugável com suporte a múltiplos provedores (GatewayAPI, SMTP, etc)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md",
|
|
11
|
+
"MIGRATION.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"import": "./dist/index.js",
|
|
17
|
+
"require": "./dist/index.js",
|
|
18
|
+
"types": "./dist/index.d.ts"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc -p tsconfig.build.json",
|
|
23
|
+
"build:watch": "tsc -p tsconfig.build.json --watch",
|
|
24
|
+
"clean": "rm -rf dist",
|
|
25
|
+
"prepublishOnly": "npm run clean && npm run build",
|
|
26
|
+
"lint": "eslint src --ext .ts",
|
|
27
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
28
|
+
"prepare": "npm run build"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"email",
|
|
32
|
+
"gatewayapi",
|
|
33
|
+
"smtp",
|
|
34
|
+
"notifications",
|
|
35
|
+
"messaging",
|
|
36
|
+
"typescript",
|
|
37
|
+
"webhooks",
|
|
38
|
+
"templates"
|
|
39
|
+
],
|
|
40
|
+
"author": "GroundBrick",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "https://github.com/groundbrick/email-service.git"
|
|
45
|
+
},
|
|
46
|
+
"bugs": {
|
|
47
|
+
"url": "https://github.com/groundbrick/email-service/issues"
|
|
48
|
+
},
|
|
49
|
+
"homepage": "https://github.com/groundbrick/email-service#readme",
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@types/jsonwebtoken": "^9.0.10",
|
|
55
|
+
"@types/node": "^20.19.9",
|
|
56
|
+
"@typescript-eslint/eslint-plugin": "^6.17.0",
|
|
57
|
+
"@typescript-eslint/parser": "^6.17.0",
|
|
58
|
+
"eslint": "^8.56.0",
|
|
59
|
+
"prettier": "^3.1.1",
|
|
60
|
+
"typescript": "^5.3.3"
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"jsonwebtoken": "^9.0.2"
|
|
64
|
+
},
|
|
65
|
+
"engines": {
|
|
66
|
+
"node": ">=16.0.0"
|
|
67
|
+
}
|
|
68
|
+
}
|