vintasend 0.2.3 → 0.4.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/README.md +227 -12
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/next.config.d.ts +1 -1
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/next.config.js +9 -4
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/forgot-password/forgot-password-notification-context.d.ts +6 -6
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/forgot-password/forgot-password-notification-context.js +16 -17
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/forgot-password/route.d.ts +7 -5
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/forgot-password/route.js +105 -79
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/login/route.d.ts +4 -5
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/login/route.js +96 -66
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/reset-password/route.d.ts +7 -5
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/reset-password/route.js +95 -71
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/signup/email-verification-notification-context.d.ts +6 -6
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/signup/email-verification-notification-context.js +18 -18
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/signup/route.d.ts +4 -5
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/signup/route.js +124 -96
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/verify-email/route.d.ts +7 -5
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/verify-email/route.js +94 -70
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/auth/login/page.d.ts +1 -1
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/auth/login/page.js +67 -55
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/auth/reset-password/[token]/page.d.ts +1 -1
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/auth/reset-password/[token]/page.js +76 -63
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/auth/signup/page.d.ts +1 -1
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/auth/signup/page.js +87 -63
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/auth/verify-email/[token]/page.d.ts +1 -1
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/auth/verify-email/[token]/page.js +50 -35
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/auth/verify-email-sent/page.d.ts +1 -1
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/auth/verify-email-sent/page.js +12 -12
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/layout.d.ts +7 -5
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/layout.js +15 -16
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/page.d.ts +1 -1
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/page.js +65 -21
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/AuthLayout.d.ts +7 -4
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/AuthLayout.js +7 -8
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/accordion.d.ts +18 -6
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/accordion.js +86 -48
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/alert-dialog.d.ts +58 -14
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/alert-dialog.js +135 -53
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/alert.d.ts +21 -7
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/alert.js +85 -49
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/avatar.d.ts +14 -5
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/avatar.js +77 -40
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/button.d.ts +25 -9
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/button.js +80 -58
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/card.d.ts +19 -7
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/card.js +98 -48
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/checkbox.d.ts +6 -3
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/checkbox.js +66 -42
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/collapsible.d.ts +10 -4
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/collapsible.js +48 -35
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/dialog.d.ts +40 -13
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/dialog.js +116 -50
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/dropdown-menu.d.ts +83 -19
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/dropdown-menu.js +170 -68
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/form.d.ts +53 -21
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/form.js +137 -83
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/input.d.ts +8 -2
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/input.js +60 -37
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/label.d.ts +10 -4
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/label.js +61 -40
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/menubar.d.ts +77 -23
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/menubar.js +188 -64
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/select.d.ts +48 -12
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/select.js +148 -66
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/textarea.d.ts +8 -2
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/components/ui/textarea.js +59 -37
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/api-clients/auth.d.ts +10 -10
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/api-clients/auth.js +54 -55
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/api-clients/core.d.ts +14 -11
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/api-clients/core.js +1 -2
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/email.js +0 -1
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/logger.d.ts +3 -3
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/logger.js +9 -10
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/schemas/auth.d.ts +66 -32
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/schemas/auth.js +74 -54
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/services/auth.js +7 -8
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/services/notifications-with-queue.d.ts +14 -4
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/services/notifications-with-queue.js +14 -10
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/services/notifications.d.ts +11 -7
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/services/notifications.js +39 -26
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/services/temporal-queue-service.d.ts +11 -8
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/services/temporal-queue-service.js +14 -15
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/temporal.d.ts +1 -1
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/temporal.js +9 -10
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/utils.d.ts +1 -1
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/lib/utils.js +4 -5
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/workers/notifications/activities.d.ts +6 -5
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/workers/notifications/activities.js +14 -14
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/workers/notifications/config.d.ts +1 -1
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/workers/notifications/config.js +2 -3
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/workers/notifications/constants.d.ts +1 -1
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/workers/notifications/constants.js +1 -2
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/workers/notifications/worker.js +20 -22
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/workers/notifications/workflows.d.ts +3 -2
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/workers/notifications/workflows.js +10 -9
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/tailwind.config.d.ts +74 -74
- package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/tailwind.config.js +80 -81
- package/dist/implementations/vintasend-nodemailer/src/index.js +6 -4
- package/dist/implementations/vintasend-nodemailer/src/nodemailer-notification-adapter.d.ts +36 -14
- package/dist/implementations/vintasend-nodemailer/src/nodemailer-notification-adapter.js +26 -28
- package/dist/implementations/vintasend-prisma/src/index.js +6 -4
- package/dist/implementations/vintasend-prisma/src/prisma-notification-backend.d.ts +232 -138
- package/dist/implementations/vintasend-prisma/src/prisma-notification-backend.js +275 -262
- package/dist/implementations/vintasend-pug/src/index.js +6 -4
- package/dist/implementations/vintasend-pug/src/pug-email-template-renderer.d.ts +18 -7
- package/dist/implementations/vintasend-pug/src/pug-email-template-renderer.js +19 -21
- package/dist/implementations/vintasend-winston/src/index.js +6 -4
- package/dist/implementations/vintasend-winston/src/winston-logger.d.ts +6 -6
- package/dist/implementations/vintasend-winston/src/winston-logger.js +65 -50
- package/dist/index.d.ts +13 -5
- package/dist/index.js +13 -1
- package/dist/services/attachment-manager/base-attachment-manager.d.ts +42 -0
- package/dist/services/attachment-manager/base-attachment-manager.js +115 -0
- package/dist/services/attachment-manager/local-file-attachment-manager.d.ts +58 -0
- package/dist/services/attachment-manager/local-file-attachment-manager.js +192 -0
- package/dist/services/notification-adapters/base-notification-adapter.d.ts +36 -4
- package/dist/services/notification-adapters/base-notification-adapter.js +73 -4
- package/dist/services/notification-backends/base-notification-backend.d.ts +54 -11
- package/dist/services/notification-backends/base-notification-backend.js +12 -0
- package/dist/services/notification-context-generators-map.d.ts +1 -1
- package/dist/services/notification-context-registry.d.ts +14 -9
- package/dist/services/notification-context-registry.js +30 -31
- package/dist/services/notification-queue-service/base-notification-queue-service.d.ts +1 -1
- package/dist/services/notification-service.d.ts +73 -13
- package/dist/services/notification-service.js +125 -8
- package/dist/services/notification-template-renderers/base-email-template-renderer.d.ts +3 -3
- package/dist/services/notification-template-renderers/base-notification-template-renderer.d.ts +2 -2
- package/dist/types/attachment.d.ts +42 -0
- package/dist/types/attachment.js +7 -0
- package/dist/types/notification-type-config.d.ts +2 -2
- package/dist/types/notification.d.ts +21 -0
- package/dist/types/one-off-notification.d.ts +72 -0
- package/dist/types/one-off-notification.js +2 -0
- package/package.json +13 -11
package/README.md
CHANGED
|
@@ -5,9 +5,11 @@ A flexible package for implementing transactional notifications in TypeScript.
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
* **Storing notifications in a Database**: This package relies on a data store to record all the notifications that will be sent. It also keeps its state column up to date.
|
|
8
|
+
* **One-off notifications**: Send notifications directly to email addresses or phone numbers without requiring a user account. Perfect for prospects, guests, or external contacts.
|
|
9
|
+
* **File Attachments**: Attach files to notifications with flexible storage backend support (S3, Azure, GCS, local filesystem, etc.), automatic deduplication, and reusable file references.
|
|
8
10
|
* **Scheduling notifications**: Storing notifications to be sent in the future. The notification's context for rendering the template is only evaluated at the moment the notification is sent due to the lib's context generation registry.
|
|
9
11
|
* **Notification context fetched at send time**: On scheduled notifications, the package only gets the notification context (information to render the templates) at the send time, so we always get the most up-to-date information.
|
|
10
|
-
* **Flexible backend**: Your project's database is getting slow after you created the first million notifications? You can migrate to a faster
|
|
12
|
+
* **Flexible backend**: Your project's database is getting slow after you created the first million notifications? You can migrate to a faster NoSQL database in the blink of an eye without affecting how you send the notifications.
|
|
11
13
|
* **Flexible adapters**: Your project probably will need to change how it sends notifications over time. This package allows you to change the adapter without having to change how notification templates are rendered or how the notification themselves are stored.
|
|
12
14
|
* **Flexible template renderers**: Wanna start managing your templates with a third party tool (so non-technical people can help maintain them)? Or even choose a more powerful rendering engine? You can do it independently of how you send the notifications or store them in the database.
|
|
13
15
|
* **Sending notifications in background jobs**: This package supports using job queues to send notifications from separate processes. This may be helpful to free up the HTTP server of processing heavy notifications during the request time.
|
|
@@ -104,40 +106,253 @@ export function sendWelcomeEmail(userId: number) {
|
|
|
104
106
|
}
|
|
105
107
|
```
|
|
106
108
|
|
|
109
|
+
## Attachment Support
|
|
110
|
+
|
|
111
|
+
VintaSend supports file attachments for notifications with an extensible architecture that allows you to choose your preferred storage backend.
|
|
112
|
+
|
|
113
|
+
### Key Features
|
|
114
|
+
|
|
115
|
+
- ✅ **Flexible Storage** - Support for multiple backends (AWS S3, Azure Blob, Google Cloud Storage, local filesystem, etc.)
|
|
116
|
+
- ✅ **Reusable Files** - Upload once, attach to multiple notifications
|
|
117
|
+
- ✅ **Automatic Deduplication** - Files with identical content stored only once
|
|
118
|
+
- ✅ **Streaming Support** - Efficient handling of large files
|
|
119
|
+
- ✅ **Presigned URLs** - Secure, time-limited file access (backend-dependent)
|
|
120
|
+
- ✅ **Custom Backends** - Extensible interface to implement any storage service
|
|
121
|
+
|
|
122
|
+
### Quick Start
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
// Example using S3 AttachmentManager (see available implementations below)
|
|
126
|
+
import { S3AttachmentManager } from 'vintasend-aws-s3-attachments';
|
|
127
|
+
|
|
128
|
+
// Create attachment manager (configuration varies by implementation)
|
|
129
|
+
const attachmentManager = new S3AttachmentManager({
|
|
130
|
+
bucket: 'my-app-notifications',
|
|
131
|
+
region: 'us-east-1',
|
|
132
|
+
keyPrefix: 'attachments/',
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Create VintaSend with attachment support
|
|
136
|
+
const vintaSend = factory.create(
|
|
137
|
+
adapters,
|
|
138
|
+
backend,
|
|
139
|
+
templateRenderer,
|
|
140
|
+
contextGeneratorsMap,
|
|
141
|
+
logger,
|
|
142
|
+
attachmentManager, // Pass your chosen attachment manager
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// Send notification with inline file upload
|
|
146
|
+
await vintaSend.sendNotification({
|
|
147
|
+
notificationTypeId: 'order-confirmation',
|
|
148
|
+
userId: '123',
|
|
149
|
+
context: { orderNumber: 'ORD-12345' },
|
|
150
|
+
attachments: [
|
|
151
|
+
{
|
|
152
|
+
file: invoiceBuffer,
|
|
153
|
+
filename: 'invoice.pdf',
|
|
154
|
+
contentType: 'application/pdf',
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Send notification with pre-uploaded file reference
|
|
160
|
+
await vintaSend.sendNotification({
|
|
161
|
+
notificationTypeId: 'welcome-email',
|
|
162
|
+
userId: '456',
|
|
163
|
+
context: { userName: 'John' },
|
|
164
|
+
attachments: [
|
|
165
|
+
{
|
|
166
|
+
fileId: 'file-abc-123', // Reference to existing file
|
|
167
|
+
description: 'Company brochure',
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Complete Documentation
|
|
174
|
+
|
|
175
|
+
For comprehensive guides on attachment support, storage backends, security, and best practices, see [ATTACHMENTS.md](ATTACHMENTS.md).
|
|
176
|
+
|
|
177
|
+
Topics covered:
|
|
178
|
+
- Available storage backend implementations
|
|
179
|
+
- Creating custom AttachmentManagers for any storage service
|
|
180
|
+
- Security best practices and performance optimization
|
|
181
|
+
- Adapter support for sending attachments
|
|
182
|
+
- Usage examples and patterns
|
|
183
|
+
|
|
184
|
+
## One-Off Notifications
|
|
185
|
+
|
|
186
|
+
One-off notifications allow you to send notifications directly to an email address or phone number without requiring a user account in your system. This is particularly useful for:
|
|
187
|
+
|
|
188
|
+
- **Prospects**: Send welcome emails or marketing materials to potential customers
|
|
189
|
+
- **Guests**: Invite external participants to events or meetings
|
|
190
|
+
- **External Contacts**: Share information with partners or vendors
|
|
191
|
+
- **Temporary Recipients**: Send one-time notifications without creating user accounts
|
|
192
|
+
|
|
193
|
+
### Key Differences from Regular Notifications
|
|
194
|
+
|
|
195
|
+
| Feature | Regular Notification | One-Off Notification |
|
|
196
|
+
|---------|---------------------|----------------------|
|
|
197
|
+
| **Recipient** | User ID (requires account) | Email/phone directly |
|
|
198
|
+
| **User Data** | Fetched from user table | Provided inline (firstName, lastName) |
|
|
199
|
+
| **Use Case** | Registered users | Prospects, guests, external contacts |
|
|
200
|
+
| **Storage** | Same table with `userId` | Same table with `emailOrPhone` |
|
|
201
|
+
|
|
202
|
+
### Creating One-Off Notifications
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
// Send an immediate one-off notification
|
|
206
|
+
const notification = await vintaSend.createOneOffNotification({
|
|
207
|
+
emailOrPhone: 'prospect@example.com',
|
|
208
|
+
firstName: 'John',
|
|
209
|
+
lastName: 'Doe',
|
|
210
|
+
notificationType: 'EMAIL',
|
|
211
|
+
title: 'Welcome!',
|
|
212
|
+
bodyTemplate: './templates/welcome.html',
|
|
213
|
+
subjectTemplate: 'Welcome to {{companyName}}!',
|
|
214
|
+
contextName: 'welcomeContext',
|
|
215
|
+
contextParameters: { companyName: 'Acme Corp' },
|
|
216
|
+
sendAfter: null, // Send immediately
|
|
217
|
+
extraParams: null,
|
|
218
|
+
});
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
#### Using with Phone Numbers (SMS)
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
// Send SMS to a phone number (requires SMS adapter)
|
|
225
|
+
const smsNotification = await vintaSend.createOneOffNotification({
|
|
226
|
+
emailOrPhone: '+15551234567', // E.164 format recommended
|
|
227
|
+
firstName: 'John',
|
|
228
|
+
lastName: 'Doe',
|
|
229
|
+
notificationType: 'SMS',
|
|
230
|
+
title: 'Welcome SMS',
|
|
231
|
+
bodyTemplate: './templates/welcome-sms.txt',
|
|
232
|
+
subjectTemplate: null, // SMS doesn't use subjects
|
|
233
|
+
contextName: 'welcomeContext',
|
|
234
|
+
contextParameters: { companyName: 'Acme Corp' },
|
|
235
|
+
sendAfter: null,
|
|
236
|
+
extraParams: null,
|
|
237
|
+
});
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Database Schema Considerations
|
|
241
|
+
|
|
242
|
+
One-off notifications are stored in the same table as regular notifications using a unified approach:
|
|
243
|
+
|
|
244
|
+
- **Regular notifications**: Have `userId` set, `emailOrPhone` is null
|
|
245
|
+
- **One-off notifications**: Have `emailOrPhone` set, `userId` is null
|
|
246
|
+
|
|
247
|
+
### Migration Guides
|
|
248
|
+
|
|
249
|
+
#### Migrating to v0.4.0 (Attachment Support)
|
|
250
|
+
|
|
251
|
+
Version 0.4.0 introduces file attachment support with a **breaking change** to the `VintaSendFactory.create()` method signature.
|
|
252
|
+
|
|
253
|
+
**Breaking Change**: The `attachmentManager` parameter now comes **before** the `options` parameter.
|
|
254
|
+
|
|
255
|
+
- **Old signature (v0.3.x)**: `create(adapters, backend, logger, contextGeneratorsMap, queueService?, options?)`
|
|
256
|
+
- **New signature (v0.4.0)**: `create(adapters, backend, logger, contextGeneratorsMap, queueService?, attachmentManager?, options?)`
|
|
257
|
+
|
|
258
|
+
**Migration Steps**:
|
|
259
|
+
|
|
260
|
+
1. **If you're NOT passing `options`** - No changes needed:
|
|
261
|
+
```typescript
|
|
262
|
+
// This still works in v0.4.0
|
|
263
|
+
factory.create(adapters, backend, logger, contextGeneratorsMap);
|
|
264
|
+
factory.create(adapters, backend, logger, contextGeneratorsMap, queueService);
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
2. **If you ARE passing `options` as the 6th argument** - Add `undefined` for `attachmentManager`:
|
|
268
|
+
```typescript
|
|
269
|
+
// Before (v0.3.x) - THIS WILL BREAK
|
|
270
|
+
factory.create(adapters, backend, logger, contextGeneratorsMap, queueService, {
|
|
271
|
+
raiseErrorOnFailedSend: true
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// After (v0.4.0) - CORRECT
|
|
275
|
+
factory.create(adapters, backend, logger, contextGeneratorsMap, queueService, undefined, {
|
|
276
|
+
raiseErrorOnFailedSend: true
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
3. **If you want to use attachments** - Pass the `attachmentManager`:
|
|
281
|
+
```typescript
|
|
282
|
+
import { LocalFileAttachmentManager } from 'vintasend';
|
|
283
|
+
|
|
284
|
+
const attachmentManager = new LocalFileAttachmentManager({ uploadDir: './uploads' });
|
|
285
|
+
|
|
286
|
+
factory.create(adapters, backend, logger, contextGeneratorsMap, queueService, attachmentManager, {
|
|
287
|
+
raiseErrorOnFailedSend: true
|
|
288
|
+
});
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
4. **For Prisma users**: Add attachment models to your schema (optional, only if you want attachment support):
|
|
292
|
+
```bash
|
|
293
|
+
# Add AttachmentFile and NotificationAttachment models to schema.prisma
|
|
294
|
+
# See ATTACHMENTS.md for the complete schema
|
|
295
|
+
prisma migrate dev --name add-attachment-support
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
**Note**: Attachment methods in `BaseNotificationBackend` are now **optional**. Existing backend implementations continue to work without changes. See [ATTACHMENTS.md](ATTACHMENTS.md) for full documentation.
|
|
299
|
+
|
|
300
|
+
#### Migrating to v0.3.0 (One-off Notifications)
|
|
301
|
+
|
|
302
|
+
If you're adding one-off notification support to an existing installation:
|
|
303
|
+
|
|
304
|
+
1. **Update your Prisma schema** to make `userId` optional and add one-off fields:
|
|
305
|
+
```bash
|
|
306
|
+
# Add the new fields to your schema.prisma
|
|
307
|
+
# Then run:
|
|
308
|
+
prisma migrate dev --name add-one-off-notification-support
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
2. **No code changes required** for existing functionality - all existing notifications continue to work as before.
|
|
312
|
+
|
|
313
|
+
3. **Existing notifications are preserved** - they have `userId` set and `emailOrPhone` as null.
|
|
314
|
+
|
|
107
315
|
## Glossary
|
|
108
316
|
|
|
109
|
-
* **Notification Backend**: It is a class that implements the methods necessary for VintaSend services to create, update, and retrieve Notifications from
|
|
317
|
+
* **Notification Backend**: It is a class that implements the methods necessary for VintaSend services to create, update, and retrieve Notifications from the database.
|
|
110
318
|
* **Notification Adapter**: It is a class that implements the methods necessary for VintaSend services to send Notifications through email, SMS or even push/in-app notifications.
|
|
111
319
|
* **Template Renderer**: It is a class that implements the methods necessary for VintaSend adapter to render the notification body.
|
|
112
320
|
* **Notification Context**: It's the data passed to the templates to render the notification correctly. It's generated when the notification is sent, not on creation time
|
|
113
321
|
* **Context generator**: It's a class defined by the user context generator map with a context name. That class has a `generate` method that, when called, generates the data necessary to render its respective notification.
|
|
114
322
|
* **Context name**: The registered name of a context generator. It's stored in the notification so the context generator is called at the moment the notification will be sent.
|
|
115
323
|
* **Context generators map**: It's an object defined by the user that maps context names to their respective context generators.
|
|
116
|
-
* **Queue service**: Service for enqueueing notifications so they are
|
|
117
|
-
* **Logger**: A class that allows the `NotificationService` to create logs following a format defined by its users.
|
|
324
|
+
* **Queue service**: Service for enqueueing notifications so they are sent by an external service.
|
|
325
|
+
* **Logger**: A class that allows the `NotificationService` to create logs following a format defined by its users.
|
|
326
|
+
* **One-off Notification**: A notification sent directly to an email address or phone number without requiring a user account. Used for prospects, guests, or external contacts.
|
|
327
|
+
* **Regular Notification**: A notification associated with a user account (via userId). Used for registered users in your system.
|
|
328
|
+
* **AttachmentManager**: A class that handles file storage operations (upload, download, delete) for notification attachments. Supports S3, Azure, GCS, and custom storage backends.
|
|
329
|
+
* **Attachment**: A file attached to a notification, either uploaded inline or referenced from previously uploaded files. Supports automatic deduplication and reuse across multiple notifications.
|
|
118
330
|
|
|
119
331
|
|
|
120
332
|
## Implementations
|
|
121
333
|
|
|
122
|
-
|
|
123
|
-
## Community
|
|
334
|
+
### Community
|
|
124
335
|
|
|
125
336
|
VintaSend has many backend, adapter, and template renderer implementations. If you can't find something that fulfills your needs, the package has very clear interfaces you can implement and achieve the exact behavior you expect without loosing VintaSend's friendly API.
|
|
126
337
|
|
|
127
|
-
|
|
338
|
+
#### Officially supported packages
|
|
128
339
|
|
|
129
|
-
|
|
340
|
+
##### Backends
|
|
130
341
|
|
|
131
342
|
* **[vintasend-prisma](https://github.com/vintasoftware/vintasend-prisma/)**: Uses Prisma Client to manage the notifications in the database.
|
|
132
343
|
|
|
133
|
-
|
|
344
|
+
##### Adapters
|
|
345
|
+
|
|
346
|
+
* **[vintasend-nodemailer](https://github.com/vintasoftware/vintasend-nodemailer/)**: Uses nodemailer to send transactional emails to users.
|
|
347
|
+
|
|
348
|
+
##### Attachment Managers
|
|
134
349
|
|
|
135
|
-
* **[vintasend-
|
|
350
|
+
* **[vintasend-aws-s3-attachments](https://github.com/vintasoftware/vintasend-aws-s3-attachments/)**: AWS S3 storage backend with presigned URLs and streaming support. Also works with S3-compatible services (MinIO, DigitalOcean Spaces, Cloudflare R2, etc.).
|
|
136
351
|
|
|
137
|
-
|
|
352
|
+
##### Template Renderers
|
|
138
353
|
* **[vintasend-pug](https://github.com/vintasoftware/vintasend-pug/)**: Renders emails using Pug.
|
|
139
354
|
|
|
140
|
-
|
|
355
|
+
##### Loggers
|
|
141
356
|
* **[vintasend-winston](https://github.com/vintasoftware/vintasend-winston/)**: Uses Winston to allow `NotificationService` to create log entries.
|
|
142
357
|
|
|
143
358
|
## Examples
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
3
2
|
const nextConfig = {
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
/* config options here */
|
|
4
|
+
transpilePackages: [
|
|
5
|
+
'vintasend',
|
|
6
|
+
'vintasend-prisma',
|
|
7
|
+
'vintasend-pug',
|
|
8
|
+
'vintasend-nodemailer',
|
|
9
|
+
'vintasend-winston',
|
|
10
|
+
],
|
|
6
11
|
};
|
|
7
12
|
exports.default = nextConfig;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { ContextGenerator } from 'vintasend/src/services/notification-context-registry';
|
|
2
2
|
export declare class ForgotPasswordContextGenerator implements ContextGenerator {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
generate(params: {
|
|
4
|
+
token: string;
|
|
5
|
+
}): Promise<{
|
|
6
|
+
firstName: string | null;
|
|
7
|
+
resetPasswordLink: string;
|
|
8
|
+
}>;
|
|
9
9
|
}
|
|
@@ -1,22 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
3
2
|
exports.ForgotPasswordContextGenerator = void 0;
|
|
4
|
-
const client_1 = require(
|
|
3
|
+
const client_1 = require('@prisma/client');
|
|
5
4
|
class ForgotPasswordContextGenerator {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
return {
|
|
17
|
-
firstName: token.user.firstName,
|
|
18
|
-
resetPasswordLink: `${APP_DOMAIN}/auth/reset-password/${params.token}/`,
|
|
19
|
-
};
|
|
5
|
+
async generate(params) {
|
|
6
|
+
const prisma = new client_1.PrismaClient();
|
|
7
|
+
const APP_DOMAIN = process.env.APP_DOMAIN;
|
|
8
|
+
const token = await prisma.token.findUnique({
|
|
9
|
+
where: { token: params.token },
|
|
10
|
+
select: { user: true },
|
|
11
|
+
});
|
|
12
|
+
if (!token || !token.user) {
|
|
13
|
+
throw new Error('Token not found');
|
|
20
14
|
}
|
|
15
|
+
return {
|
|
16
|
+
firstName: token.user.firstName,
|
|
17
|
+
resetPasswordLink: `${APP_DOMAIN}/auth/reset-password/${params.token}/`,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
21
20
|
}
|
|
22
21
|
exports.ForgotPasswordContextGenerator = ForgotPasswordContextGenerator;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { NextResponse } from 'next/server';
|
|
2
|
+
import type * as z from 'zod';
|
|
2
3
|
import type { WriteApiResponse } from '../../../../lib/api-clients/core';
|
|
3
|
-
import {
|
|
4
|
-
import * as z from 'zod';
|
|
4
|
+
import type { ForgotPasswordValues } from '../../../../lib/schemas/auth';
|
|
5
5
|
type ForgotPasswordSuccess = null;
|
|
6
6
|
type ForgotPasswordValidationError = z.typeToFlattenedError<ForgotPasswordValues>;
|
|
7
|
-
export type ForgotPasswordApiResponse = WriteApiResponse<
|
|
7
|
+
export type ForgotPasswordApiResponse = WriteApiResponse<
|
|
8
|
+
ForgotPasswordSuccess,
|
|
9
|
+
ForgotPasswordValidationError
|
|
10
|
+
>;
|
|
8
11
|
type ForgotPasswordNextResponse = NextResponse<ForgotPasswordApiResponse>;
|
|
9
12
|
export declare function POST(req: Request): Promise<ForgotPasswordNextResponse>;
|
|
10
|
-
export {};
|
|
@@ -1,86 +1,112 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
1
|
+
var __createBinding =
|
|
2
|
+
(this && this.__createBinding) ||
|
|
3
|
+
(Object.create
|
|
4
|
+
? (o, m, k, k2) => {
|
|
5
|
+
if (k2 === undefined) k2 = k;
|
|
6
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
7
|
+
if (!desc || ('get' in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
8
|
+
desc = { enumerable: true, get: () => m[k] };
|
|
9
|
+
}
|
|
10
|
+
Object.defineProperty(o, k2, desc);
|
|
11
|
+
}
|
|
12
|
+
: (o, m, k, k2) => {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
});
|
|
16
|
+
var __setModuleDefault =
|
|
17
|
+
(this && this.__setModuleDefault) ||
|
|
18
|
+
(Object.create
|
|
19
|
+
? (o, v) => {
|
|
20
|
+
Object.defineProperty(o, 'default', { enumerable: true, value: v });
|
|
21
|
+
}
|
|
22
|
+
: (o, v) => {
|
|
23
|
+
o['default'] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar =
|
|
26
|
+
(this && this.__importStar) ||
|
|
27
|
+
(() => {
|
|
28
|
+
var ownKeys = (o) => {
|
|
29
|
+
ownKeys =
|
|
30
|
+
Object.getOwnPropertyNames ||
|
|
31
|
+
((o) => {
|
|
32
|
+
var ar = [];
|
|
33
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
34
|
+
return ar;
|
|
35
|
+
});
|
|
36
|
+
return ownKeys(o);
|
|
26
37
|
};
|
|
27
|
-
return
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
38
|
+
return (mod) => {
|
|
39
|
+
if (mod && mod.__esModule) return mod;
|
|
40
|
+
var result = {};
|
|
41
|
+
if (mod != null)
|
|
42
|
+
for (var k = ownKeys(mod), i = 0; i < k.length; i++)
|
|
43
|
+
if (k[i] !== 'default') __createBinding(result, mod, k[i]);
|
|
44
|
+
__setModuleDefault(result, mod);
|
|
45
|
+
return result;
|
|
33
46
|
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports,
|
|
47
|
+
})();
|
|
48
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
36
49
|
exports.POST = POST;
|
|
37
|
-
const auth_1 = require(
|
|
38
|
-
const client_1 = require(
|
|
39
|
-
const auth_2 = require(
|
|
40
|
-
const server_1 = require(
|
|
41
|
-
const z = __importStar(require(
|
|
42
|
-
const library_1 = require(
|
|
43
|
-
const notifications_with_queue_1 = require(
|
|
50
|
+
const auth_1 = require('../../../../lib/schemas/auth');
|
|
51
|
+
const client_1 = require('@prisma/client');
|
|
52
|
+
const auth_2 = require('../../../../lib/services/auth');
|
|
53
|
+
const server_1 = require('next/server');
|
|
54
|
+
const z = __importStar(require('zod'));
|
|
55
|
+
const library_1 = require('@prisma/client/runtime/library');
|
|
56
|
+
const notifications_with_queue_1 = require('../../../../lib/services/notifications-with-queue');
|
|
44
57
|
async function POST(req) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
|
|
58
|
+
try {
|
|
59
|
+
const body = await req.json();
|
|
60
|
+
const { email } = auth_1.forgotPasswordSchema.parse(body);
|
|
61
|
+
const prisma = new client_1.PrismaClient();
|
|
62
|
+
const user = await prisma.user.findUnique({ where: { email } });
|
|
63
|
+
if (user) {
|
|
64
|
+
const resetToken = (0, auth_2.generateToken)({ userId: user.id }, '1h');
|
|
65
|
+
await prisma.token.create({
|
|
66
|
+
data: {
|
|
67
|
+
token: resetToken,
|
|
68
|
+
type: 'PASSWORD_RESET',
|
|
69
|
+
userId: user.id,
|
|
70
|
+
expiresAt: new Date(Date.now() + 60 * 60 * 1000),
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
const notificationService = await (0,
|
|
74
|
+
notifications_with_queue_1.getNotificationServiceWithQueue)();
|
|
75
|
+
await notificationService.createNotification({
|
|
76
|
+
userId: user.id,
|
|
77
|
+
notificationType: 'EMAIL',
|
|
78
|
+
title: 'Password Reset',
|
|
79
|
+
contextName: 'forgotPassword',
|
|
80
|
+
contextParameters: { token: resetToken },
|
|
81
|
+
sendAfter: new Date(),
|
|
82
|
+
bodyTemplate: './src/email-templates/auth/forgot-password/forgot-password-body.html.pug',
|
|
83
|
+
subjectTemplate:
|
|
84
|
+
'./src/email-templates/auth/forgot-password/forgot-password-subject.txt.pug',
|
|
85
|
+
extraParams: {},
|
|
86
|
+
});
|
|
74
87
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
88
|
+
return server_1.NextResponse.json({
|
|
89
|
+
success: true,
|
|
90
|
+
message: 'If an account exists, a password reset email has been sent',
|
|
91
|
+
});
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (error instanceof z.ZodError) {
|
|
94
|
+
const validationError = error;
|
|
95
|
+
return server_1.NextResponse.json(
|
|
96
|
+
{ success: false, error: 'Validation error', details: validationError.flatten() },
|
|
97
|
+
{ status: 400 },
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
if (error instanceof library_1.PrismaClientKnownRequestError) {
|
|
101
|
+
return server_1.NextResponse.json(
|
|
102
|
+
{ success: false, error: 'Database error occurred' },
|
|
103
|
+
{ status: 500 },
|
|
104
|
+
);
|
|
85
105
|
}
|
|
106
|
+
console.error('Password reset error:', error);
|
|
107
|
+
return server_1.NextResponse.json(
|
|
108
|
+
{ success: false, error: 'Internal server error' },
|
|
109
|
+
{ status: 500 },
|
|
110
|
+
);
|
|
111
|
+
}
|
|
86
112
|
}
|
package/dist/examples/nextjs-prisma-nodemailer-pug-temporal/src/app/api/auth/login/route.d.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import type { NextResponse } from 'next/server';
|
|
2
|
+
import type * as z from 'zod';
|
|
3
3
|
import type { WriteApiResponse } from '../../../../lib/api-clients/core';
|
|
4
|
-
import
|
|
4
|
+
import type { LoginValues } from '../../../../lib/schemas/auth';
|
|
5
5
|
type LoginSuccess = {
|
|
6
|
-
|
|
6
|
+
token: string;
|
|
7
7
|
};
|
|
8
8
|
type LoginValidationError = z.typeToFlattenedError<LoginValues>;
|
|
9
9
|
export type LoginApiResponse = WriteApiResponse<LoginSuccess, LoginValidationError>;
|
|
10
10
|
type LoginNextResponse = NextResponse<LoginApiResponse>;
|
|
11
11
|
export declare function POST(req: Request): Promise<LoginNextResponse>;
|
|
12
|
-
export {};
|