vintasend-sendgrid 0.4.3

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 ADDED
@@ -0,0 +1,292 @@
1
+ # VintaSend SendGrid Adapter
2
+
3
+ A VintaSend email notification adapter using [SendGrid](https://sendgrid.com/) for reliable email delivery with attachment support.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install vintasend-sendgrid @sendgrid/mail
9
+ ```
10
+
11
+ ## Configuration
12
+
13
+ ```typescript
14
+ import { SendgridNotificationAdapterFactory } from 'vintasend-sendgrid';
15
+
16
+ const adapter = new SendgridNotificationAdapterFactory().create(
17
+ templateRenderer,
18
+ false, // enqueueNotifications
19
+ {
20
+ apiKey: process.env.SENDGRID_API_KEY,
21
+ fromEmail: 'noreply@example.com',
22
+ fromName: 'My App', // optional
23
+ }
24
+ );
25
+ ```
26
+
27
+ ### Configuration Options
28
+
29
+ ```typescript
30
+ interface SendgridConfig {
31
+ apiKey: string; // SendGrid API key
32
+ fromEmail: string; // Default sender email address
33
+ fromName?: string; // Optional sender name
34
+ }
35
+ ```
36
+
37
+ ## Usage
38
+
39
+ ### Basic Email
40
+
41
+ ```typescript
42
+ await notificationService.createNotification({
43
+ userId: '123',
44
+ notificationType: 'EMAIL',
45
+ contextName: 'welcome',
46
+ contextParameters: { firstName: 'John' },
47
+ title: 'Welcome!',
48
+ bodyTemplate: '/templates/welcome.pug',
49
+ subjectTemplate: '/templates/subjects/welcome.pug',
50
+ sendAfter: new Date(),
51
+ });
52
+ ```
53
+
54
+ ### Email with Attachments
55
+
56
+ ```typescript
57
+ import { readFile } from 'fs/promises';
58
+
59
+ await notificationService.createNotification({
60
+ userId: '123',
61
+ notificationType: 'EMAIL',
62
+ contextName: 'invoice',
63
+ contextParameters: { invoiceNumber: 'INV-001' },
64
+ title: 'Your invoice',
65
+ bodyTemplate: '/templates/invoice.pug',
66
+ subjectTemplate: '/templates/subjects/invoice.pug',
67
+ sendAfter: new Date(),
68
+ attachments: [
69
+ {
70
+ file: await readFile('./invoice.pdf'),
71
+ filename: 'invoice.pdf',
72
+ contentType: 'application/pdf',
73
+ },
74
+ ],
75
+ });
76
+ ```
77
+
78
+ ### One-Off Notifications
79
+
80
+ Send emails without a user account:
81
+
82
+ ```typescript
83
+ await notificationService.createOneOffNotification({
84
+ emailOrPhone: 'customer@example.com',
85
+ firstName: 'Jane',
86
+ lastName: 'Smith',
87
+ notificationType: 'EMAIL',
88
+ contextName: 'order-confirmation',
89
+ contextParameters: { orderNumber: '12345' },
90
+ title: 'Order Confirmation',
91
+ bodyTemplate: '/templates/order-confirmation.pug',
92
+ subjectTemplate: '/templates/subjects/order-confirmation.pug',
93
+ sendAfter: new Date(),
94
+ });
95
+ ```
96
+
97
+ ## Features
98
+
99
+ - ✅ Email delivery via SendGrid API
100
+ - ✅ File attachments (automatically base64 encoded)
101
+ - ✅ One-off notifications
102
+ - ✅ Scheduled notifications
103
+ - ✅ Custom sender name and email
104
+ - ✅ HTML email templates
105
+ - ✅ Multiple attachments per email
106
+
107
+ ## API Reference
108
+
109
+ ### SendgridNotificationAdapterFactory
110
+
111
+ ```typescript
112
+ class SendgridNotificationAdapterFactory<Config extends BaseNotificationTypeConfig>
113
+ ```
114
+
115
+ **Methods:**
116
+ - `create<TemplateRenderer>(templateRenderer, enqueueNotifications, config)` - Create adapter instance
117
+
118
+ ### SendgridNotificationAdapter
119
+
120
+ **Properties:**
121
+ - `key: string` - Returns `'sendgrid'`
122
+ - `notificationType: NotificationType` - Returns `'EMAIL'`
123
+ - `supportsAttachments: boolean` - Returns `true`
124
+
125
+ **Methods:**
126
+ - `send(notification, context)` - Send an email with optional attachments
127
+
128
+ ## Environment Variables
129
+
130
+ ```bash
131
+ SENDGRID_API_KEY=SG.your-api-key-here
132
+ FROM_EMAIL=noreply@example.com
133
+ FROM_NAME=My Application
134
+
135
+ The `TemplateAttachmentManager` provides a structure for implementing file attachment storage for notifications.
136
+
137
+ **Supported Storage Backends:**
138
+ - AWS S3 (see [`vintasend-aws-s3-attachments`](../vintasend-aws-s3-attachments))
139
+ - Azure Blob Storage
140
+ - Google Cloud Storage
141
+ - Local Filesystem (development only)
142
+ - Any S3-compatible storage (MinIO, DigitalOcean Spaces, etc.)
143
+
144
+ **Implementation Steps:**
145
+
146
+ 1. **Copy this template** to a new package:
147
+ ```bash
148
+ cp -r src/implementations/vintasend-implementation-template src/implementations/vintasend-{your-storage}-attachments
149
+ ```
150
+
151
+ 2. **Update package.json**:
152
+ - Change package name to match your implementation
153
+ - Add storage-specific dependencies (e.g., `@aws-sdk/client-s3` for S3)
154
+ - Update description and keywords
155
+
156
+ 4. **Install dependencies**:
157
+ ```bash
158
+ npm install
159
+ ```
160
+
161
+ 5. **Rename the class**:
162
+ - Rename `TemplateAttachmentManager` to your implementation name (e.g., `S3AttachmentManager`)
163
+ - Update all references in exports and tests
164
+
165
+ 4. **Implement the required methods**:
166
+
167
+ - `uploadFile(file, filename, contentType?)` - Upload file to your storage backend
168
+ - Convert file to Buffer using `this.fileToBuffer(file)`
169
+ - Calculate checksum using `this.calculateChecksum(buffer)`
170
+ - Auto-detect content type using `this.detectContentType(filename)`
171
+ - Upload to storage and return file record
172
+
173
+ - `getFile(fileId)` - Retrieve file record from database
174
+ - Query your backend's database for file metadata
175
+ - Return `AttachmentFileRecord` or `null`
176
+
177
+ - `deleteFile(fileId)` - Delete file from storage
178
+ - Remove from storage backend
179
+ - Remove file record from database
180
+ - Only called for orphaned files (not referenced by any notifications)
181
+
182
+ - `reconstructAttachmentFile(storageMetadata)` - Recreate file accessor
183
+ - Create and return an `AttachmentFile` instance
184
+ - Use storage metadata to configure access (e.g., S3 bucket/key)
185
+
186
+ - `findFileByChecksum(checksum)` _(optional)_ - Enable file deduplication
187
+ - Query database for existing file with same checksum
188
+ - Return existing `AttachmentFileRecord` or `null`
189
+ - Enables automatic deduplication when uploading identical files
190
+
191
+ 5. **Implement the AttachmentFile class**:
192
+
193
+ Create a storage-specific implementation of the `AttachmentFile` interface:
194
+
195
+ - `read()` - Load entire file into memory as Buffer
196
+ - `stream()` - Return ReadableStream for large files
197
+ - `url(expiresIn?)` - Generate presigned/temporary URL for access
198
+ - `delete()` - Delete file from storage
199
+
200
+ 6. **Write comprehensive tests**:
201
+ - Use the test template as a starting point
202
+ - Test all methods with various file types
203
+ - Test error handling
204
+ - Use mocks for unit tests
205
+ - Consider integration tests with real storage (or LocalStack/emulators)
206
+
207
+ 7. **Document configuration**:
208
+ - Document all configuration options
209
+ - Provide usage examples
210
+ - Document authentication methods
211
+ - Include troubleshooting tips
212
+
213
+ **Example Implementation:**
214
+
215
+ See [`vintasend-aws-s3-attachments`](../vintasend-aws-s3-attachments) for a complete AWS S3 implementation that follows this pattern.
216
+
217
+ **Key Design Patterns:**
218
+
219
+ - **Reusable Files**: Files are stored once in `AttachmentFile` table and referenced by multiple notifications via `NotificationAttachment` join table
220
+ - **Deduplication**: Implement `findFileByChecksum()` to prevent storing duplicate files
221
+ - **Presigned URLs**: Generate temporary URLs for secure file access without exposing credentials
222
+ - **Streaming**: Support streaming for large files to avoid memory issues
223
+ - **Type Safety**: All methods use strict TypeScript types from `vintasend/dist/types/attachment`
224
+
225
+ ## Other Components
226
+
227
+ ### Adapter
228
+
229
+ Custom notification delivery adapters (email, SMS, push notifications, etc.)
230
+
231
+ **Examples:**
232
+ - `vintasend-nodemailer` - Email via Nodemailer
233
+ - Custom SMS adapter
234
+ - Custom push notification adapter
235
+
236
+ ### Backend
237
+
238
+ Custom database persistence layers
239
+
240
+ **Examples:**
241
+ - `vintasend-prisma` - Prisma ORM backend
242
+ - Custom MongoDB backend
243
+ - Custom PostgreSQL backend
244
+
245
+ ### Template Renderer
246
+
247
+ Custom notification content rendering
248
+
249
+ **Examples:**
250
+ - `vintasend-pug` - Pug template engine
251
+ - Custom Handlebars renderer
252
+ - Custom React email renderer
253
+
254
+ ### Logger
255
+
256
+ Custom logging implementations
257
+
258
+ **Examples:**
259
+ - `vintasend-winston` - Winston logger
260
+ - Custom Pino logger
261
+ - Custom cloud logging service
262
+
263
+ ## Getting Started
264
+
265
+ 1. Choose the component type you want to implement
266
+ 2. Copy this template package to a new directory
267
+ 3. Follow the implementation steps for that component type
268
+ 4. Write comprehensive tests
269
+ 5. Document your implementation
270
+ 6. Publish as a separate npm package (optional)
271
+
272
+ ## Best Practices
273
+
274
+ - **Type Safety**: Use TypeScript strict mode and leverage VintaSend's type system
275
+ - **Testing**: Aim for high test coverage, including edge cases and error conditions
276
+ - **Documentation**: Document all configuration options and provide clear examples
277
+ - **Error Handling**: Provide clear error messages and proper error types
278
+ - **Performance**: Consider streaming for large files, connection pooling for databases, etc.
279
+ - **Security**: Never expose credentials, use presigned URLs, validate inputs
280
+
281
+ ## Contributing
282
+
283
+ When creating implementations:
284
+ - Follow the existing code style (use Biome for linting)
285
+ - Include comprehensive tests
286
+ - Document all public APIs
287
+ - Add examples in README
288
+ - Consider adding to the main VintaSend monorepo
289
+
290
+ ## License
291
+
292
+ MIT
@@ -0,0 +1,2 @@
1
+ export { SendgridNotificationAdapter, SendgridNotificationAdapterFactory } from './sendgrid-notification-adapter';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,kCAAkC,EAAE,MAAM,iCAAiC,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { SendgridNotificationAdapter, SendgridNotificationAdapterFactory } from './sendgrid-notification-adapter';
@@ -0,0 +1,24 @@
1
+ import type { AttachmentData } from '@sendgrid/helpers/classes/attachment';
2
+ import { BaseNotificationAdapter } from 'vintasend/dist/services/notification-adapters/base-notification-adapter';
3
+ import type { BaseEmailTemplateRenderer } from 'vintasend/dist/services/notification-template-renderers/base-email-template-renderer';
4
+ import type { JsonObject } from 'vintasend/dist/types/json-values';
5
+ import type { AnyDatabaseNotification } from 'vintasend/dist/types/notification';
6
+ import type { BaseNotificationTypeConfig } from 'vintasend/dist/types/notification-type-config';
7
+ import type { StoredAttachment } from 'vintasend/dist/types/attachment';
8
+ export interface SendgridConfig {
9
+ apiKey: string;
10
+ fromEmail: string;
11
+ fromName?: string;
12
+ }
13
+ export declare class SendgridNotificationAdapter<TemplateRenderer extends BaseEmailTemplateRenderer<Config>, Config extends BaseNotificationTypeConfig> extends BaseNotificationAdapter<TemplateRenderer, Config> {
14
+ key: string | null;
15
+ private config;
16
+ constructor(templateRenderer: TemplateRenderer, enqueueNotifications: boolean, config: SendgridConfig);
17
+ get supportsAttachments(): boolean;
18
+ send(notification: AnyDatabaseNotification<Config>, context: JsonObject): Promise<void>;
19
+ protected prepareAttachments(attachments: StoredAttachment[]): Promise<AttachmentData[]>;
20
+ }
21
+ export declare class SendgridNotificationAdapterFactory<Config extends BaseNotificationTypeConfig> {
22
+ create<TemplateRenderer extends BaseEmailTemplateRenderer<Config>>(templateRenderer: TemplateRenderer, enqueueNotifications: boolean, config: SendgridConfig): SendgridNotificationAdapter<TemplateRenderer, Config>;
23
+ }
24
+ //# sourceMappingURL=sendgrid-notification-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sendgrid-notification-adapter.d.ts","sourceRoot":"","sources":["../src/sendgrid-notification-adapter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAE3E,OAAO,EAAE,uBAAuB,EAAE,MAAM,yEAAyE,CAAC;AAClH,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,sFAAsF,CAAC;AACtI,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,mCAAmC,CAAC;AACjF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+CAA+C,CAAC;AAChG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAExE,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,2BAA2B,CACtC,gBAAgB,SAAS,yBAAyB,CAAC,MAAM,CAAC,EAC1D,MAAM,SAAS,0BAA0B,CACzC,SAAQ,uBAAuB,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClD,GAAG,EAAE,MAAM,GAAG,IAAI,CAAc;IACvC,OAAO,CAAC,MAAM,CAAiB;gBAG7B,gBAAgB,EAAE,gBAAgB,EAClC,oBAAoB,EAAE,OAAO,EAC7B,MAAM,EAAE,cAAc;IAOxB,IAAI,mBAAmB,IAAI,OAAO,CAEjC;IAEK,IAAI,CAAC,YAAY,EAAE,uBAAuB,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;cA+B7E,kBAAkB,CAChC,WAAW,EAAE,gBAAgB,EAAE,GAC9B,OAAO,CAAC,cAAc,EAAE,CAAC;CAa7B;AAED,qBAAa,kCAAkC,CAAC,MAAM,SAAS,0BAA0B;IACvF,MAAM,CAAC,gBAAgB,SAAS,yBAAyB,CAAC,MAAM,CAAC,EAC/D,gBAAgB,EAAE,gBAAgB,EAClC,oBAAoB,EAAE,OAAO,EAC7B,MAAM,EAAE,cAAc;CAQzB"}
@@ -0,0 +1,53 @@
1
+ import sgMail from '@sendgrid/mail';
2
+ import { BaseNotificationAdapter } from 'vintasend/dist/services/notification-adapters/base-notification-adapter';
3
+ export class SendgridNotificationAdapter extends BaseNotificationAdapter {
4
+ constructor(templateRenderer, enqueueNotifications, config) {
5
+ super(templateRenderer, 'EMAIL', enqueueNotifications);
6
+ this.key = 'sendgrid';
7
+ this.config = config;
8
+ sgMail.setApiKey(config.apiKey);
9
+ }
10
+ get supportsAttachments() {
11
+ return true;
12
+ }
13
+ async send(notification, context) {
14
+ if (!this.backend) {
15
+ throw new Error('Backend not injected');
16
+ }
17
+ const template = await this.templateRenderer.render(notification, context);
18
+ if (!notification.id) {
19
+ throw new Error('Notification ID is required');
20
+ }
21
+ // Use the helper method to get recipient email (handles both regular and one-off notifications)
22
+ const recipientEmail = await this.getRecipientEmail(notification);
23
+ const mailData = {
24
+ to: recipientEmail,
25
+ from: this.config.fromName
26
+ ? { email: this.config.fromEmail, name: this.config.fromName }
27
+ : this.config.fromEmail,
28
+ subject: template.subject,
29
+ html: template.body,
30
+ };
31
+ // Add attachments if present
32
+ if (notification.attachments && notification.attachments.length > 0) {
33
+ mailData.attachments = await this.prepareAttachments(notification.attachments);
34
+ }
35
+ await sgMail.send(mailData);
36
+ }
37
+ async prepareAttachments(attachments) {
38
+ return Promise.all(attachments.map(async (att) => {
39
+ const content = await att.file.read();
40
+ return {
41
+ filename: att.filename,
42
+ content: content.toString('base64'),
43
+ type: att.contentType,
44
+ disposition: 'attachment',
45
+ };
46
+ }));
47
+ }
48
+ }
49
+ export class SendgridNotificationAdapterFactory {
50
+ create(templateRenderer, enqueueNotifications, config) {
51
+ return new SendgridNotificationAdapter(templateRenderer, enqueueNotifications, config);
52
+ }
53
+ }
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "vintasend-sendgrid",
3
+ "version": "0.4.3",
4
+ "description": "",
5
+ "main": "dist/index.js",
6
+ "scripts": {
7
+ "build": "tsc",
8
+ "prepublishOnly": "npm run build",
9
+ "test": "jest",
10
+ "test:watch": "jest --watch",
11
+ "test:coverage": "jest --coverage"
12
+ },
13
+ "files": ["dist"],
14
+ "author": "Hugo Bessa",
15
+ "license": "MIT",
16
+ "peerDependencies": {
17
+ "vintasend": "^0.4.3",
18
+ "@sendgrid/mail": "^8.1.6"
19
+ },
20
+ "devDependencies": {
21
+ "@types/jest": "^30.0.0",
22
+ "jest": "^30.2.0",
23
+ "ts-jest": "^29.4.6",
24
+ "typescript": "^5.9.3"
25
+ }
26
+ }