@simplens/resend 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/README.md +87 -0
- package/dist/index.d.ts +179 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +230 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# @simplens/resend
|
|
2
|
+
|
|
3
|
+
Resend email provider plugin for [SimpleNS](https://github.com/SimpleNotificationSystem/SimpleNotificationSystem).
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 📧 Send HTML emails via [Resend](https://resend.com)
|
|
8
|
+
- 🖼️ Automatic base64 image extraction to CID attachments
|
|
9
|
+
- 🔄 Template variable replacement (`{{var}}`, `${var}`, `{var}`, `$var`)
|
|
10
|
+
- ⚡ Built-in rate limiting (configurable)
|
|
11
|
+
- ✅ Full TypeScript support
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @simplens/resend
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Configuration
|
|
20
|
+
|
|
21
|
+
Add to your `simplens.config.yaml` or generate using `npx @simplens/config-gen gen @simplens/resend`:
|
|
22
|
+
|
|
23
|
+
```yaml
|
|
24
|
+
providers:
|
|
25
|
+
- package: '@simplens/resend'
|
|
26
|
+
id: email-resend
|
|
27
|
+
credentials:
|
|
28
|
+
RESEND_API_KEY: ${RESEND_API_KEY}
|
|
29
|
+
FROM_EMAIL: ${FROM_EMAIL}
|
|
30
|
+
options:
|
|
31
|
+
rateLimit:
|
|
32
|
+
maxTokens: 100 # Max emails in bucket
|
|
33
|
+
refillRate: 100 # Tokens refilled per interval
|
|
34
|
+
refillInterval: day # second | minute | hour | day
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Required Credentials
|
|
38
|
+
|
|
39
|
+
| Credential | Description |
|
|
40
|
+
|------------|-------------|
|
|
41
|
+
| `RESEND_API_KEY` | Your Resend API key ([get one here](https://resend.com/api-keys)) |
|
|
42
|
+
| `FROM_EMAIL` | Verified sender email address ([verify domain](https://resend.com/domains)) |
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
## Notification Schema
|
|
46
|
+
|
|
47
|
+
| Field | Type | Required | Description |
|
|
48
|
+
|-------|------|----------|-------------|
|
|
49
|
+
| `channel` | `'email'` | ✅ | Must be `'email'` |
|
|
50
|
+
| `recipient.user_id` | `string` | ✅ | User identifier |
|
|
51
|
+
| `recipient.email` | `string` | ✅ | Valid email address |
|
|
52
|
+
| `content.subject` | `string` | ✅ | Email subject line |
|
|
53
|
+
| `content.html` | `string` | ✅ | HTML content (must contain at least one HTML tag) |
|
|
54
|
+
| `variables` | `Record<string, unknown>` | ❌ | Template variables |
|
|
55
|
+
|
|
56
|
+
## Rate Limiting
|
|
57
|
+
|
|
58
|
+
Resend free tier allows 100 emails/day. Configure based on your plan:
|
|
59
|
+
|
|
60
|
+
| Plan | Suggested Config |
|
|
61
|
+
|------|------------------|
|
|
62
|
+
| Free | `maxTokens: 100, refillRate: 100, refillInterval: day` |
|
|
63
|
+
| Pro | `maxTokens: 5000, refillRate: 5000, refillInterval: day` |
|
|
64
|
+
|
|
65
|
+
## Development
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# Install dependencies
|
|
69
|
+
npm install
|
|
70
|
+
|
|
71
|
+
# Build
|
|
72
|
+
npm run build
|
|
73
|
+
|
|
74
|
+
# Run tests
|
|
75
|
+
npm test
|
|
76
|
+
|
|
77
|
+
# Manual test (requires .env with RESEND_API_KEY, FROM_EMAIL, TO_EMAIL)
|
|
78
|
+
npm run test:send
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
MIT
|
|
84
|
+
|
|
85
|
+
## Author
|
|
86
|
+
|
|
87
|
+
SimpleNS Team
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @simplens/resend
|
|
3
|
+
*
|
|
4
|
+
* Resend email provider plugin for SimpleNS.
|
|
5
|
+
* Uses Resend API for email delivery.
|
|
6
|
+
*/
|
|
7
|
+
import { z, type SimpleNSProvider, type ProviderManifest, type ProviderConfig, type DeliveryResult, type RateLimitConfig } from '@simplens/sdk';
|
|
8
|
+
declare const notificationSchema: z.ZodObject<{
|
|
9
|
+
notification_id: z.ZodString;
|
|
10
|
+
request_id: z.ZodString;
|
|
11
|
+
client_id: z.ZodString;
|
|
12
|
+
provider: z.ZodOptional<z.ZodString>;
|
|
13
|
+
variables: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
14
|
+
webhook_url: z.ZodString;
|
|
15
|
+
retry_count: z.ZodNumber;
|
|
16
|
+
} & {
|
|
17
|
+
channel: z.ZodLiteral<"email">;
|
|
18
|
+
recipient: z.ZodObject<{
|
|
19
|
+
user_id: z.ZodString;
|
|
20
|
+
email: z.ZodString;
|
|
21
|
+
}, "strip", z.ZodTypeAny, {
|
|
22
|
+
user_id: string;
|
|
23
|
+
email: string;
|
|
24
|
+
}, {
|
|
25
|
+
user_id: string;
|
|
26
|
+
email: string;
|
|
27
|
+
}>;
|
|
28
|
+
content: z.ZodObject<{
|
|
29
|
+
subject: z.ZodString;
|
|
30
|
+
html: z.ZodEffects<z.ZodString, string, string>;
|
|
31
|
+
}, "strip", z.ZodTypeAny, {
|
|
32
|
+
subject: string;
|
|
33
|
+
html: string;
|
|
34
|
+
}, {
|
|
35
|
+
subject: string;
|
|
36
|
+
html: string;
|
|
37
|
+
}>;
|
|
38
|
+
created_at: z.ZodDate;
|
|
39
|
+
}, "strip", z.ZodTypeAny, {
|
|
40
|
+
channel: "email";
|
|
41
|
+
recipient: {
|
|
42
|
+
user_id: string;
|
|
43
|
+
email: string;
|
|
44
|
+
};
|
|
45
|
+
content: {
|
|
46
|
+
subject: string;
|
|
47
|
+
html: string;
|
|
48
|
+
};
|
|
49
|
+
created_at: Date;
|
|
50
|
+
notification_id: string;
|
|
51
|
+
request_id: string;
|
|
52
|
+
client_id: string;
|
|
53
|
+
webhook_url: string;
|
|
54
|
+
retry_count: number;
|
|
55
|
+
provider?: string | undefined;
|
|
56
|
+
variables?: Record<string, string> | undefined;
|
|
57
|
+
}, {
|
|
58
|
+
channel: "email";
|
|
59
|
+
recipient: {
|
|
60
|
+
user_id: string;
|
|
61
|
+
email: string;
|
|
62
|
+
};
|
|
63
|
+
content: {
|
|
64
|
+
subject: string;
|
|
65
|
+
html: string;
|
|
66
|
+
};
|
|
67
|
+
created_at: Date;
|
|
68
|
+
notification_id: string;
|
|
69
|
+
request_id: string;
|
|
70
|
+
client_id: string;
|
|
71
|
+
webhook_url: string;
|
|
72
|
+
retry_count: number;
|
|
73
|
+
provider?: string | undefined;
|
|
74
|
+
variables?: Record<string, string> | undefined;
|
|
75
|
+
}>;
|
|
76
|
+
type Notification = z.infer<typeof notificationSchema>;
|
|
77
|
+
declare class ResendProvider implements SimpleNSProvider<Notification> {
|
|
78
|
+
private config;
|
|
79
|
+
private from_email;
|
|
80
|
+
private resend;
|
|
81
|
+
readonly manifest: ProviderManifest;
|
|
82
|
+
getNotificationSchema(): z.ZodObject<{
|
|
83
|
+
notification_id: z.ZodString;
|
|
84
|
+
request_id: z.ZodString;
|
|
85
|
+
client_id: z.ZodString;
|
|
86
|
+
provider: z.ZodOptional<z.ZodString>;
|
|
87
|
+
variables: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
88
|
+
webhook_url: z.ZodString;
|
|
89
|
+
retry_count: z.ZodNumber;
|
|
90
|
+
} & {
|
|
91
|
+
channel: z.ZodLiteral<"email">;
|
|
92
|
+
recipient: z.ZodObject<{
|
|
93
|
+
user_id: z.ZodString;
|
|
94
|
+
email: z.ZodString;
|
|
95
|
+
}, "strip", z.ZodTypeAny, {
|
|
96
|
+
user_id: string;
|
|
97
|
+
email: string;
|
|
98
|
+
}, {
|
|
99
|
+
user_id: string;
|
|
100
|
+
email: string;
|
|
101
|
+
}>;
|
|
102
|
+
content: z.ZodObject<{
|
|
103
|
+
subject: z.ZodString;
|
|
104
|
+
html: z.ZodEffects<z.ZodString, string, string>;
|
|
105
|
+
}, "strip", z.ZodTypeAny, {
|
|
106
|
+
subject: string;
|
|
107
|
+
html: string;
|
|
108
|
+
}, {
|
|
109
|
+
subject: string;
|
|
110
|
+
html: string;
|
|
111
|
+
}>;
|
|
112
|
+
created_at: z.ZodDate;
|
|
113
|
+
}, "strip", z.ZodTypeAny, {
|
|
114
|
+
channel: "email";
|
|
115
|
+
recipient: {
|
|
116
|
+
user_id: string;
|
|
117
|
+
email: string;
|
|
118
|
+
};
|
|
119
|
+
content: {
|
|
120
|
+
subject: string;
|
|
121
|
+
html: string;
|
|
122
|
+
};
|
|
123
|
+
created_at: Date;
|
|
124
|
+
notification_id: string;
|
|
125
|
+
request_id: string;
|
|
126
|
+
client_id: string;
|
|
127
|
+
webhook_url: string;
|
|
128
|
+
retry_count: number;
|
|
129
|
+
provider?: string | undefined;
|
|
130
|
+
variables?: Record<string, string> | undefined;
|
|
131
|
+
}, {
|
|
132
|
+
channel: "email";
|
|
133
|
+
recipient: {
|
|
134
|
+
user_id: string;
|
|
135
|
+
email: string;
|
|
136
|
+
};
|
|
137
|
+
content: {
|
|
138
|
+
subject: string;
|
|
139
|
+
html: string;
|
|
140
|
+
};
|
|
141
|
+
created_at: Date;
|
|
142
|
+
notification_id: string;
|
|
143
|
+
request_id: string;
|
|
144
|
+
client_id: string;
|
|
145
|
+
webhook_url: string;
|
|
146
|
+
retry_count: number;
|
|
147
|
+
provider?: string | undefined;
|
|
148
|
+
variables?: Record<string, string> | undefined;
|
|
149
|
+
}>;
|
|
150
|
+
getRecipientSchema(): z.ZodObject<{
|
|
151
|
+
user_id: z.ZodString;
|
|
152
|
+
email: z.ZodString;
|
|
153
|
+
}, "strip", z.ZodTypeAny, {
|
|
154
|
+
user_id: string;
|
|
155
|
+
email: string;
|
|
156
|
+
}, {
|
|
157
|
+
user_id: string;
|
|
158
|
+
email: string;
|
|
159
|
+
}>;
|
|
160
|
+
getContentSchema(): z.ZodObject<{
|
|
161
|
+
subject: z.ZodString;
|
|
162
|
+
html: z.ZodEffects<z.ZodString, string, string>;
|
|
163
|
+
}, "strip", z.ZodTypeAny, {
|
|
164
|
+
subject: string;
|
|
165
|
+
html: string;
|
|
166
|
+
}, {
|
|
167
|
+
subject: string;
|
|
168
|
+
html: string;
|
|
169
|
+
}>;
|
|
170
|
+
getRateLimitConfig(): RateLimitConfig;
|
|
171
|
+
initialize(config: ProviderConfig): Promise<void>;
|
|
172
|
+
healthCheck(): Promise<boolean>;
|
|
173
|
+
send(notification: Notification): Promise<DeliveryResult>;
|
|
174
|
+
shutdown(): Promise<void>;
|
|
175
|
+
}
|
|
176
|
+
export default ResendProvider;
|
|
177
|
+
export type { Notification as ResendNotification };
|
|
178
|
+
export declare function createProvider(): ResendProvider;
|
|
179
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACH,CAAC,EACD,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,eAAe,EAEvB,MAAM,eAAe,CAAC;AAoHvB,QAAA,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAKtB,CAAC;AAEH,KAAK,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEvD,cAAM,cAAe,YAAW,gBAAgB,CAAC,YAAY,CAAC;IAC1D,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,UAAU,CAAc;IAChC,OAAO,CAAC,MAAM,CAAuB;IAErC,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CASjC;IAEF,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAIrB,kBAAkB;;;;;;;;;;IAIlB,gBAAgB;;;;;;;;;;IAIhB,kBAAkB,IAAI,eAAe;IAW/B,UAAU,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAYjD,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAI/B,IAAI,CAAC,YAAY,EAAE,YAAY,GAAG,OAAO,CAAC,cAAc,CAAC;IAsGzD,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAKlC;AAGD,eAAe,cAAc,CAAC;AAG9B,YAAY,EAAE,YAAY,IAAI,kBAAkB,EAAE,CAAC;AAGnD,wBAAgB,cAAc,IAAI,cAAc,CAE/C"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @simplens/resend
|
|
3
|
+
*
|
|
4
|
+
* Resend email provider plugin for SimpleNS.
|
|
5
|
+
* Uses Resend API for email delivery.
|
|
6
|
+
*/
|
|
7
|
+
import { z, baseNotificationSchema, } from '@simplens/sdk';
|
|
8
|
+
import { Resend } from 'resend';
|
|
9
|
+
/**
|
|
10
|
+
* Extracts base64 encoded images from HTML and converts them to CID attachments.
|
|
11
|
+
* This is necessary because most email clients block inline base64 images.
|
|
12
|
+
*
|
|
13
|
+
* @param html - The HTML content containing base64 images
|
|
14
|
+
* @returns Object with processed HTML (cid: references) and attachments array
|
|
15
|
+
*/
|
|
16
|
+
function extractBase64Images(html) {
|
|
17
|
+
const attachments = [];
|
|
18
|
+
// Regex to match base64 image data URIs in img src attributes
|
|
19
|
+
// Matches: <img ... src="data:image/TYPE;base64,DATA" ...>
|
|
20
|
+
const base64ImageRegex = /<img([^>]*)\ssrc=["']data:(image\/(\w+));base64,([^"']+)["']([^>]*)>/gi;
|
|
21
|
+
let imageIndex = 0;
|
|
22
|
+
const timestamp = Date.now();
|
|
23
|
+
const processedHtml = html.replace(base64ImageRegex, (match, before, mimeType, extension, base64Data, after) => {
|
|
24
|
+
const contentId = `embedded-image-${imageIndex}-${timestamp}`;
|
|
25
|
+
const filename = `image-${imageIndex}.${extension}`;
|
|
26
|
+
attachments.push({
|
|
27
|
+
filename,
|
|
28
|
+
content: base64Data, // Keep as base64 string for Resend
|
|
29
|
+
contentId,
|
|
30
|
+
contentType: mimeType,
|
|
31
|
+
});
|
|
32
|
+
imageIndex++;
|
|
33
|
+
return `<img${before} src="cid:${contentId}"${after}>`;
|
|
34
|
+
});
|
|
35
|
+
return { html: processedHtml, attachments };
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Replaces template variables in a string using multiple common patterns.
|
|
39
|
+
*
|
|
40
|
+
* Supported patterns:
|
|
41
|
+
* - {{variable}} - Handlebars/Mustache style
|
|
42
|
+
* - ${variable} - ES6 template literal style
|
|
43
|
+
* - {variable} - Simple brace style
|
|
44
|
+
* - $variable - Shell/PHP style (word characters only)
|
|
45
|
+
*
|
|
46
|
+
* @param template - The template string containing variables
|
|
47
|
+
* @param variables - Record of variable names to values
|
|
48
|
+
* @returns The template with all variables replaced
|
|
49
|
+
*/
|
|
50
|
+
function replaceTemplateVariables(template, variables) {
|
|
51
|
+
let result = template;
|
|
52
|
+
// Define all supported patterns with their regex
|
|
53
|
+
// Order matters: more specific patterns first to avoid partial matches
|
|
54
|
+
const patterns = [
|
|
55
|
+
/\{\{(\w+)\}\}/g, // {{variable}}
|
|
56
|
+
/\$\{(\w+)\}/g, // ${variable}
|
|
57
|
+
/\{(\w+)\}/g, // {variable}
|
|
58
|
+
/\$(\w+)/g, // $variable
|
|
59
|
+
];
|
|
60
|
+
for (const pattern of patterns) {
|
|
61
|
+
result = result.replace(pattern, (match, varName) => {
|
|
62
|
+
if (varName in variables) {
|
|
63
|
+
return String(variables[varName]);
|
|
64
|
+
}
|
|
65
|
+
return match; // Leave unmatched patterns as-is
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
// Define your schemas
|
|
71
|
+
const recipientSchema = z.object({
|
|
72
|
+
user_id: z.string(),
|
|
73
|
+
email: z.string().email()
|
|
74
|
+
});
|
|
75
|
+
const htmlSchema = z.string().refine((value) => {
|
|
76
|
+
// Check if string contains at least one HTML tag
|
|
77
|
+
const htmlTagPattern = /<[a-z][\s\S]*>/i;
|
|
78
|
+
return htmlTagPattern.test(value);
|
|
79
|
+
}, { message: 'Invalid HTML: must contain at least one HTML tag' });
|
|
80
|
+
const contentSchema = z.object({
|
|
81
|
+
subject: z.string(),
|
|
82
|
+
html: htmlSchema,
|
|
83
|
+
});
|
|
84
|
+
const notificationSchema = baseNotificationSchema.extend({
|
|
85
|
+
channel: z.literal('email'),
|
|
86
|
+
recipient: recipientSchema,
|
|
87
|
+
content: contentSchema,
|
|
88
|
+
created_at: z.coerce.date(),
|
|
89
|
+
});
|
|
90
|
+
class ResendProvider {
|
|
91
|
+
config = null;
|
|
92
|
+
from_email = '';
|
|
93
|
+
resend = null;
|
|
94
|
+
manifest = {
|
|
95
|
+
name: '@simplens/resend',
|
|
96
|
+
version: '1.0.0',
|
|
97
|
+
channel: 'email',
|
|
98
|
+
displayName: 'Resend',
|
|
99
|
+
description: 'Send notifications via Resend',
|
|
100
|
+
author: 'Adhish Krishna S',
|
|
101
|
+
homepage: '',
|
|
102
|
+
requiredCredentials: ["RESEND_API_KEY", "FROM_EMAIL"],
|
|
103
|
+
};
|
|
104
|
+
getNotificationSchema() {
|
|
105
|
+
return notificationSchema;
|
|
106
|
+
}
|
|
107
|
+
getRecipientSchema() {
|
|
108
|
+
return recipientSchema;
|
|
109
|
+
}
|
|
110
|
+
getContentSchema() {
|
|
111
|
+
return contentSchema;
|
|
112
|
+
}
|
|
113
|
+
getRateLimitConfig() {
|
|
114
|
+
const options = this.config?.options;
|
|
115
|
+
const rateLimit = options?.rateLimit;
|
|
116
|
+
return {
|
|
117
|
+
maxTokens: rateLimit?.maxTokens || 100, //default set to 100 emails / day for free tier
|
|
118
|
+
refillRate: rateLimit?.refillRate || 100,
|
|
119
|
+
refillInterval: rateLimit?.refillInterval || 'day'
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
async initialize(config) {
|
|
123
|
+
this.config = config;
|
|
124
|
+
const api_key = config.credentials['RESEND_API_KEY'];
|
|
125
|
+
const from_email = config.credentials['FROM_EMAIL'];
|
|
126
|
+
if (!api_key || !from_email) {
|
|
127
|
+
throw new Error('RESEND_API_KEY and FROM_EMAIL are required');
|
|
128
|
+
}
|
|
129
|
+
this.from_email = from_email;
|
|
130
|
+
this.resend = new Resend(api_key);
|
|
131
|
+
console.log(`[ResendProvider] Initialized with from_email: ${from_email}`);
|
|
132
|
+
}
|
|
133
|
+
async healthCheck() {
|
|
134
|
+
return this.config !== null && this.resend !== null;
|
|
135
|
+
}
|
|
136
|
+
async send(notification) {
|
|
137
|
+
if (!this.resend) {
|
|
138
|
+
return {
|
|
139
|
+
success: false,
|
|
140
|
+
error: { code: 'NOT_INITIALIZED', message: 'Resend client not initialized', retryable: false },
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
let htmlContent = notification.content.html;
|
|
145
|
+
let subject = notification.content.subject;
|
|
146
|
+
// Replace template variables if provided
|
|
147
|
+
if (notification.variables) {
|
|
148
|
+
htmlContent = replaceTemplateVariables(htmlContent, notification.variables);
|
|
149
|
+
subject = replaceTemplateVariables(subject, notification.variables);
|
|
150
|
+
}
|
|
151
|
+
// Extract base64 images and convert to attachments
|
|
152
|
+
const { html, attachments } = extractBase64Images(htmlContent);
|
|
153
|
+
// Build the email payload
|
|
154
|
+
const emailPayload = {
|
|
155
|
+
from: this.from_email,
|
|
156
|
+
to: notification.recipient.email,
|
|
157
|
+
subject: subject,
|
|
158
|
+
html: html,
|
|
159
|
+
};
|
|
160
|
+
// Add attachments if any base64 images were found
|
|
161
|
+
if (attachments.length > 0) {
|
|
162
|
+
emailPayload.attachments = attachments.map(att => ({
|
|
163
|
+
filename: att.filename,
|
|
164
|
+
content: att.content,
|
|
165
|
+
contentId: att.contentId, // Required for CID embedding
|
|
166
|
+
}));
|
|
167
|
+
}
|
|
168
|
+
const response = await this.resend.emails.send(emailPayload);
|
|
169
|
+
if (response.error) {
|
|
170
|
+
console.error(`[ResendProvider] Send failed:`, response.error);
|
|
171
|
+
// Determine if retryable based on error type
|
|
172
|
+
const nonRetryablePatterns = [
|
|
173
|
+
'validation',
|
|
174
|
+
'invalid',
|
|
175
|
+
'unauthorized',
|
|
176
|
+
'forbidden',
|
|
177
|
+
];
|
|
178
|
+
const errorMessage = response.error.message || 'Unknown error';
|
|
179
|
+
const retryable = !nonRetryablePatterns.some(p => errorMessage.toLowerCase().includes(p.toLowerCase()));
|
|
180
|
+
return {
|
|
181
|
+
success: false,
|
|
182
|
+
error: {
|
|
183
|
+
code: response.error.name || 'SEND_FAILED',
|
|
184
|
+
message: errorMessage,
|
|
185
|
+
retryable,
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
console.log(`[ResendProvider] Email sent: ${response.data?.id} to ${notification.recipient.email}`);
|
|
190
|
+
return {
|
|
191
|
+
success: true,
|
|
192
|
+
messageId: response.data?.id,
|
|
193
|
+
providerResponse: response.data,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
|
|
198
|
+
// Determine if retryable
|
|
199
|
+
const nonRetryablePatterns = [
|
|
200
|
+
'validation',
|
|
201
|
+
'invalid',
|
|
202
|
+
'unauthorized',
|
|
203
|
+
'forbidden',
|
|
204
|
+
'api key',
|
|
205
|
+
];
|
|
206
|
+
const retryable = !nonRetryablePatterns.some(p => errorMessage.toLowerCase().includes(p.toLowerCase()));
|
|
207
|
+
console.error(`[ResendProvider] Send failed:`, err);
|
|
208
|
+
return {
|
|
209
|
+
success: false,
|
|
210
|
+
error: {
|
|
211
|
+
code: 'SEND_FAILED',
|
|
212
|
+
message: errorMessage,
|
|
213
|
+
retryable,
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
async shutdown() {
|
|
219
|
+
this.config = null;
|
|
220
|
+
this.resend = null;
|
|
221
|
+
console.log('[ResendProvider] Shutdown complete');
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// Export the provider class as default
|
|
225
|
+
export default ResendProvider;
|
|
226
|
+
// Export a factory function for convenience
|
|
227
|
+
export function createProvider() {
|
|
228
|
+
return new ResendProvider();
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@simplens/resend",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Send notifications via Resend",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"type": "module",
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"test:watch": "vitest",
|
|
19
|
+
"test:send": "npx tsx scripts/test-send.ts",
|
|
20
|
+
"prepublishOnly": "npm run build"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"simplens",
|
|
24
|
+
"plugin",
|
|
25
|
+
"email",
|
|
26
|
+
"notification"
|
|
27
|
+
],
|
|
28
|
+
"author": "Adhish Krishna S <adhishthesak@gmail.com>",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@simplens/sdk": "^1.0.3",
|
|
32
|
+
"resend": "^6.7.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^22.0.0",
|
|
36
|
+
"dotenv": "^17.2.3",
|
|
37
|
+
"typescript": "^5.0.0",
|
|
38
|
+
"vitest": "^2.0.0"
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"dist",
|
|
42
|
+
"README.md"
|
|
43
|
+
],
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "https://github.com/SimpleNotificationSystem/plugin-resend"
|
|
47
|
+
}
|
|
48
|
+
}
|