@bloomneo/appkit 1.2.9 → 1.5.2
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/AGENTS.md +195 -0
- package/CHANGELOG.md +253 -0
- package/README.md +147 -799
- package/bin/commands/generate.js +7 -7
- package/cookbook/README.md +26 -0
- package/cookbook/api-key-service.ts +106 -0
- package/cookbook/auth-protected-crud.ts +112 -0
- package/cookbook/file-upload-pipeline.ts +113 -0
- package/cookbook/multi-tenant-saas.ts +87 -0
- package/cookbook/real-time-chat.ts +121 -0
- package/dist/auth/auth.d.ts +21 -4
- package/dist/auth/auth.d.ts.map +1 -1
- package/dist/auth/auth.js +56 -44
- package/dist/auth/auth.js.map +1 -1
- package/dist/auth/defaults.d.ts +1 -1
- package/dist/auth/defaults.js +35 -35
- package/dist/cache/cache.d.ts +29 -6
- package/dist/cache/cache.d.ts.map +1 -1
- package/dist/cache/cache.js +72 -44
- package/dist/cache/cache.js.map +1 -1
- package/dist/cache/defaults.js +29 -29
- package/dist/cache/index.d.ts +19 -10
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +21 -18
- package/dist/cache/index.js.map +1 -1
- package/dist/config/defaults.d.ts +1 -1
- package/dist/config/defaults.js +11 -11
- package/dist/config/index.d.ts +3 -3
- package/dist/config/index.js +4 -4
- package/dist/database/adapters/mongoose.d.ts +4 -4
- package/dist/database/adapters/mongoose.js +7 -7
- package/dist/database/adapters/prisma.d.ts +4 -4
- package/dist/database/adapters/prisma.js +7 -7
- package/dist/database/defaults.d.ts +1 -1
- package/dist/database/defaults.js +4 -4
- package/dist/database/index.js +2 -2
- package/dist/database/index.js.map +1 -1
- package/dist/email/defaults.js +26 -26
- package/dist/email/index.js +7 -7
- package/dist/email/strategies/resend.js +1 -1
- package/dist/error/defaults.d.ts +1 -1
- package/dist/error/defaults.js +13 -13
- package/dist/error/error.d.ts +12 -0
- package/dist/error/error.d.ts.map +1 -1
- package/dist/error/error.js +19 -0
- package/dist/error/error.js.map +1 -1
- package/dist/error/index.d.ts +14 -3
- package/dist/error/index.d.ts.map +1 -1
- package/dist/error/index.js +14 -3
- package/dist/error/index.js.map +1 -1
- package/dist/event/defaults.js +35 -35
- package/dist/event/index.js +7 -7
- package/dist/logger/defaults.d.ts +1 -1
- package/dist/logger/defaults.js +40 -40
- package/dist/logger/index.d.ts +1 -0
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/logger/index.js.map +1 -1
- package/dist/logger/logger.d.ts +8 -0
- package/dist/logger/logger.d.ts.map +1 -1
- package/dist/logger/logger.js +13 -3
- package/dist/logger/logger.js.map +1 -1
- package/dist/logger/transports/console.js +2 -2
- package/dist/logger/transports/http.d.ts +1 -1
- package/dist/logger/transports/http.js +2 -2
- package/dist/logger/transports/webhook.d.ts +1 -1
- package/dist/logger/transports/webhook.js +3 -3
- package/dist/queue/defaults.d.ts +2 -2
- package/dist/queue/defaults.js +38 -38
- package/dist/security/defaults.d.ts +1 -1
- package/dist/security/defaults.js +30 -30
- package/dist/security/index.d.ts +1 -1
- package/dist/security/index.js +3 -3
- package/dist/security/security.d.ts +1 -1
- package/dist/security/security.js +4 -4
- package/dist/storage/defaults.js +26 -26
- package/dist/storage/index.js +3 -3
- package/dist/util/defaults.d.ts +1 -1
- package/dist/util/defaults.js +41 -41
- package/dist/util/env.d.ts +35 -0
- package/dist/util/env.d.ts.map +1 -0
- package/dist/util/env.js +50 -0
- package/dist/util/env.js.map +1 -0
- package/dist/util/errors.d.ts +52 -0
- package/dist/util/errors.d.ts.map +1 -0
- package/dist/util/errors.js +82 -0
- package/dist/util/errors.js.map +1 -0
- package/dist/util/util.js +1 -1
- package/examples/.env.example +80 -0
- package/examples/README.md +16 -0
- package/examples/auth.ts +228 -0
- package/examples/cache.ts +36 -0
- package/examples/config.ts +45 -0
- package/examples/database.ts +69 -0
- package/examples/email.ts +53 -0
- package/examples/error.ts +50 -0
- package/examples/event.ts +42 -0
- package/examples/logger.ts +41 -0
- package/examples/queue.ts +58 -0
- package/examples/security.ts +46 -0
- package/examples/storage.ts +44 -0
- package/examples/util.ts +47 -0
- package/llms.txt +591 -0
- package/package.json +19 -10
- package/src/auth/README.md +850 -0
- package/src/cache/README.md +756 -0
- package/src/config/README.md +604 -0
- package/src/database/README.md +818 -0
- package/src/email/README.md +759 -0
- package/src/error/README.md +660 -0
- package/src/event/README.md +729 -0
- package/src/logger/README.md +435 -0
- package/src/queue/README.md +851 -0
- package/src/security/README.md +612 -0
- package/src/storage/README.md +1008 -0
- package/src/util/README.md +955 -0
- package/bin/templates/backend/docs/APPKIT_CLI.md +0 -507
- package/bin/templates/backend/docs/APPKIT_COMMENTS_GUIDELINES.md +0 -61
- package/bin/templates/backend/docs/APPKIT_LLM_GUIDE.md +0 -2539
|
@@ -0,0 +1,759 @@
|
|
|
1
|
+
# @bloomneo/appkit - Email Module 📧
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@bloomneo/appkit)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
> Ultra-simple email sending that just works - One function, automatic provider
|
|
7
|
+
> detection, zero configuration
|
|
8
|
+
|
|
9
|
+
**One function** returns an email object with automatic strategy selection. Zero
|
|
10
|
+
configuration needed, production-ready sending by default, with built-in
|
|
11
|
+
template system and development preview.
|
|
12
|
+
|
|
13
|
+
## 🚀 Why Choose This?
|
|
14
|
+
|
|
15
|
+
- **⚡ One Function** - Just `emailClass.get()`, everything else is automatic
|
|
16
|
+
- **🎯 Auto-Strategy** - RESEND_API_KEY = Resend, SMTP_HOST = SMTP, default =
|
|
17
|
+
Console
|
|
18
|
+
- **🔧 Zero Configuration** - Smart defaults for everything
|
|
19
|
+
- **📄 Built-in Templates** - Welcome, reset password templates included
|
|
20
|
+
- **🎨 Development Preview** - See emails in console with beautiful formatting
|
|
21
|
+
- **🛡️ Production Ready** - Retry logic, error handling, graceful shutdown
|
|
22
|
+
- **🤖 AI-Ready** - Optimized for LLM code generation
|
|
23
|
+
|
|
24
|
+
## 📦 Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install @bloomneo/appkit
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 🏃♂️ Quick Start (30 seconds)
|
|
31
|
+
|
|
32
|
+
### 1. Basic Setup (Console Preview)
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { emailClass } from '@bloomneo/appkit/email';
|
|
36
|
+
|
|
37
|
+
const email = emailClass.get();
|
|
38
|
+
|
|
39
|
+
// Send email (shows in console during development)
|
|
40
|
+
await email.send({
|
|
41
|
+
to: 'user@example.com',
|
|
42
|
+
subject: 'Welcome!',
|
|
43
|
+
text: 'Hello world!',
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Even simpler
|
|
47
|
+
await emailClass.sendText('user@example.com', 'Hi', 'Hello!');
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 2. Production Setup (Resend)
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Just set API key - automatic Resend strategy
|
|
54
|
+
export RESEND_API_KEY=re_your_api_key_here
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { emailClass } from '@bloomneo/appkit/email';
|
|
59
|
+
|
|
60
|
+
// Same code, now sends real emails!
|
|
61
|
+
await emailClass.send({
|
|
62
|
+
to: 'user@example.com',
|
|
63
|
+
subject: 'Welcome!',
|
|
64
|
+
html: '<h1>Hello!</h1><p>Welcome to our app!</p>',
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**That's it!** No configuration, no setup, just works everywhere.
|
|
69
|
+
|
|
70
|
+
## 🧠 Mental Model
|
|
71
|
+
|
|
72
|
+
### **Automatic Strategy Selection**
|
|
73
|
+
|
|
74
|
+
The email module **automatically detects** what you need:
|
|
75
|
+
|
|
76
|
+
| Environment Variable | Strategy | Use Case |
|
|
77
|
+
| -------------------------- | -------- | ---------------------------------- |
|
|
78
|
+
| `RESEND_API_KEY=re_...` | Resend | Modern production (recommended) |
|
|
79
|
+
| `SMTP_HOST=smtp.gmail.com` | SMTP | Universal (Gmail, Outlook, custom) |
|
|
80
|
+
| _No email env vars_ | Console | Development (shows in terminal) |
|
|
81
|
+
|
|
82
|
+
### **Development → Production**
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// Same code works everywhere
|
|
86
|
+
await emailClass.send({
|
|
87
|
+
to: 'user@example.com',
|
|
88
|
+
subject: 'Welcome!',
|
|
89
|
+
text: 'Hello!',
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Development: Beautiful console preview
|
|
93
|
+
// Production: Real email via Resend/SMTP
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## 📖 Complete API
|
|
97
|
+
|
|
98
|
+
### Core Function
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
const email = emailClass.get(); // One function, everything you need
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Email Operations
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// Full email
|
|
108
|
+
await email.send({
|
|
109
|
+
to: 'user@example.com',
|
|
110
|
+
subject: 'Subject',
|
|
111
|
+
text: 'Plain text',
|
|
112
|
+
html: '<h1>HTML content</h1>',
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Simple text
|
|
116
|
+
await email.sendText('user@example.com', 'Subject', 'Text');
|
|
117
|
+
|
|
118
|
+
// HTML email
|
|
119
|
+
await email.sendHtml('user@example.com', 'Subject', '<h1>HTML</h1>');
|
|
120
|
+
|
|
121
|
+
// Template email
|
|
122
|
+
await email.sendTemplate('welcome', {
|
|
123
|
+
to: 'user@example.com',
|
|
124
|
+
name: 'John',
|
|
125
|
+
appName: 'MyApp',
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Batch emails
|
|
129
|
+
await email.sendBatch([email1, email2, email3]);
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Utility Methods
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// Debugging
|
|
136
|
+
email.getStrategy(); // 'resend', 'smtp', or 'console'
|
|
137
|
+
emailClass.hasResend(); // true if RESEND_API_KEY set
|
|
138
|
+
emailClass.hasSmtp(); // true if SMTP_HOST set
|
|
139
|
+
|
|
140
|
+
// Convenience
|
|
141
|
+
await emailClass.send(emailData); // Direct send without get()
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## 💡 Simple Examples
|
|
145
|
+
|
|
146
|
+
### **User Registration Email**
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
import { emailClass } from '@bloomneo/appkit/email';
|
|
150
|
+
|
|
151
|
+
async function sendWelcomeEmail(user) {
|
|
152
|
+
await emailClass.send({
|
|
153
|
+
to: user.email,
|
|
154
|
+
subject: `Welcome to ${process.env.APP_NAME}!`,
|
|
155
|
+
html: `
|
|
156
|
+
<h1>Welcome ${user.name}!</h1>
|
|
157
|
+
<p>Thanks for joining us. We're excited to have you!</p>
|
|
158
|
+
<a href="${process.env.APP_URL}/dashboard">Get Started</a>
|
|
159
|
+
`,
|
|
160
|
+
text: `Welcome ${user.name}! Thanks for joining us. Visit ${process.env.APP_URL}/dashboard to get started.`,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### **Password Reset**
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
import { emailClass } from '@bloomneo/appkit/email';
|
|
169
|
+
|
|
170
|
+
async function sendPasswordReset(user, resetToken) {
|
|
171
|
+
const resetUrl = `${process.env.APP_URL}/reset?token=${resetToken}`;
|
|
172
|
+
|
|
173
|
+
await emailClass.send({
|
|
174
|
+
to: user.email,
|
|
175
|
+
subject: 'Reset your password',
|
|
176
|
+
html: `
|
|
177
|
+
<h2>Reset your password</h2>
|
|
178
|
+
<p>Hi ${user.name},</p>
|
|
179
|
+
<p>Click the link below to reset your password:</p>
|
|
180
|
+
<a href="${resetUrl}">Reset Password</a>
|
|
181
|
+
<p>This link expires in 1 hour.</p>
|
|
182
|
+
<p>If you didn't request this, please ignore this email.</p>
|
|
183
|
+
`,
|
|
184
|
+
text: `Reset your password: ${resetUrl} (expires in 1 hour)`,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### **Order Confirmation**
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
import { emailClass } from '@bloomneo/appkit/email';
|
|
193
|
+
|
|
194
|
+
async function sendOrderConfirmation(order) {
|
|
195
|
+
await emailClass.send({
|
|
196
|
+
to: order.customerEmail,
|
|
197
|
+
subject: `Order Confirmation #${order.id}`,
|
|
198
|
+
html: `
|
|
199
|
+
<h1>Order Confirmed!</h1>
|
|
200
|
+
<p>Thanks for your order, ${order.customerName}!</p>
|
|
201
|
+
<h3>Order Details:</h3>
|
|
202
|
+
<ul>
|
|
203
|
+
${order.items.map((item) => `<li>${item.name} x${item.quantity} - $${item.price}</li>`).join('')}
|
|
204
|
+
</ul>
|
|
205
|
+
<p><strong>Total: $${order.total}</strong></p>
|
|
206
|
+
<p>We'll send you tracking information when your order ships.</p>
|
|
207
|
+
`,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### **Built-in Templates**
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
import { emailClass } from '@bloomneo/appkit/email';
|
|
216
|
+
|
|
217
|
+
// Welcome template
|
|
218
|
+
await emailClass.get().sendTemplate('welcome', {
|
|
219
|
+
to: 'user@example.com',
|
|
220
|
+
name: 'John',
|
|
221
|
+
appName: 'MyApp',
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// Password reset template
|
|
225
|
+
await emailClass.get().sendTemplate('reset', {
|
|
226
|
+
to: 'user@example.com',
|
|
227
|
+
name: 'John',
|
|
228
|
+
resetUrl: 'https://myapp.com/reset?token=abc123',
|
|
229
|
+
appName: 'MyApp',
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## 🧪 Testing
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
import { emailClass } from '@bloomneo/appkit/email';
|
|
237
|
+
|
|
238
|
+
describe('Email Tests', () => {
|
|
239
|
+
afterEach(async () => {
|
|
240
|
+
await emailClass.clear(); // Clean up between tests
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test('should send email', async () => {
|
|
244
|
+
// Force console strategy for tests
|
|
245
|
+
await emailClass.reset({
|
|
246
|
+
strategy: 'console',
|
|
247
|
+
from: { name: 'Test App', email: 'test@example.com' },
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
const result = await emailClass.send({
|
|
251
|
+
to: 'user@example.com',
|
|
252
|
+
subject: 'Test',
|
|
253
|
+
text: 'Test message',
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
expect(result.success).toBe(true);
|
|
257
|
+
expect(result.messageId).toBeDefined();
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## ⚠️ Common Mistakes
|
|
263
|
+
|
|
264
|
+
### **1. Missing Required Fields**
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
// ❌ DON'T forget subject or content
|
|
268
|
+
await emailClass.send({
|
|
269
|
+
to: 'user@example.com',
|
|
270
|
+
// Missing subject and content!
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// ✅ DO include all required fields
|
|
274
|
+
await emailClass.send({
|
|
275
|
+
to: 'user@example.com',
|
|
276
|
+
subject: 'Welcome!',
|
|
277
|
+
text: 'Welcome to our app!',
|
|
278
|
+
});
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### **2. Console Strategy in Production**
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
// ❌ DON'T rely on console strategy in production
|
|
285
|
+
// This only logs emails, doesn't send them!
|
|
286
|
+
process.env.NODE_ENV = 'production';
|
|
287
|
+
// No RESEND_API_KEY or SMTP_HOST set
|
|
288
|
+
await emailClass.send(emailData); // Only logs to console!
|
|
289
|
+
|
|
290
|
+
// ✅ DO set up a real email provider
|
|
291
|
+
process.env.RESEND_API_KEY = 're_your_api_key';
|
|
292
|
+
await emailClass.send(emailData); // Actually sends emails
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### **3. Ignoring Send Results**
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
// ❌ DON'T ignore send results
|
|
299
|
+
await emailClass.send(emailData); // What if it failed?
|
|
300
|
+
|
|
301
|
+
// ✅ DO check for success/failure
|
|
302
|
+
const result = await emailClass.send(emailData);
|
|
303
|
+
if (!result.success) {
|
|
304
|
+
console.error('Email failed:', result.error);
|
|
305
|
+
// Handle failure appropriately
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### **4. Invalid Email Addresses**
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
// ❌ DON'T use invalid email formats
|
|
313
|
+
await emailClass.send({
|
|
314
|
+
to: 'not-an-email', // Invalid format
|
|
315
|
+
subject: 'Test',
|
|
316
|
+
text: 'Hello',
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// ✅ DO validate email addresses
|
|
320
|
+
const isValidEmail = (email) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
|
321
|
+
if (!isValidEmail(user.email)) {
|
|
322
|
+
throw new Error('Invalid email address');
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### **5. Unverified FROM Addresses**
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
// ❌ DON'T use unverified FROM addresses with Resend
|
|
330
|
+
await emailClass.send({
|
|
331
|
+
from: 'random@example.com', // Unverified domain
|
|
332
|
+
to: 'user@example.com',
|
|
333
|
+
subject: 'Test',
|
|
334
|
+
text: 'Hello',
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// ✅ DO configure verified FROM address
|
|
338
|
+
process.env.BLOOM_EMAIL_FROM_EMAIL = 'noreply@yourdomain.com'; // Verified
|
|
339
|
+
// FROM automatically set from config
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
## 🚨 Error Handling
|
|
343
|
+
|
|
344
|
+
### **Basic Error Handling**
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
async function sendWelcomeEmail(user) {
|
|
348
|
+
const result = await emailClass.send({
|
|
349
|
+
to: user.email,
|
|
350
|
+
subject: 'Welcome!',
|
|
351
|
+
text: 'Welcome to our app!',
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
if (!result.success) {
|
|
355
|
+
console.error('Email failed:', result.error);
|
|
356
|
+
// Don't throw - email failure shouldn't break user registration
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
console.log('Welcome email sent:', result.messageId);
|
|
361
|
+
return true;
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### **Production Error Handling with Retries**
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
async function sendCriticalEmail(emailData, maxRetries = 3) {
|
|
369
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
370
|
+
const result = await emailClass.send(emailData);
|
|
371
|
+
|
|
372
|
+
if (result.success) {
|
|
373
|
+
return result;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// If it's a client error (bad email, invalid data), don't retry
|
|
377
|
+
if (
|
|
378
|
+
result.error?.includes('Invalid email') ||
|
|
379
|
+
result.error?.includes('Bad Request')
|
|
380
|
+
) {
|
|
381
|
+
throw new Error(`Email validation failed: ${result.error}`);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Server errors - retry with exponential backoff
|
|
385
|
+
if (attempt < maxRetries) {
|
|
386
|
+
const delay = 1000 * Math.pow(2, attempt - 1);
|
|
387
|
+
console.warn(
|
|
388
|
+
`Email attempt ${attempt} failed, retrying in ${delay}ms:`,
|
|
389
|
+
result.error
|
|
390
|
+
);
|
|
391
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
throw new Error(`Email failed after ${maxRetries} attempts`);
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### **Strategy-Specific Error Handling**
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
async function sendEmailWithContext(emailData) {
|
|
403
|
+
const strategy = emailClass.getStrategy();
|
|
404
|
+
const result = await emailClass.send(emailData);
|
|
405
|
+
|
|
406
|
+
if (!result.success) {
|
|
407
|
+
// Handle strategy-specific errors
|
|
408
|
+
switch (strategy) {
|
|
409
|
+
case 'resend':
|
|
410
|
+
if (result.error?.includes('API key')) {
|
|
411
|
+
throw new Error('Resend API key invalid - check RESEND_API_KEY');
|
|
412
|
+
}
|
|
413
|
+
if (result.error?.includes('domain')) {
|
|
414
|
+
throw new Error('Email domain not verified in Resend');
|
|
415
|
+
}
|
|
416
|
+
break;
|
|
417
|
+
|
|
418
|
+
case 'smtp':
|
|
419
|
+
if (result.error?.includes('authentication')) {
|
|
420
|
+
throw new Error(
|
|
421
|
+
'SMTP authentication failed - check SMTP_USER/SMTP_PASS'
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
if (result.error?.includes('connection')) {
|
|
425
|
+
throw new Error('SMTP connection failed - check SMTP_HOST/SMTP_PORT');
|
|
426
|
+
}
|
|
427
|
+
break;
|
|
428
|
+
|
|
429
|
+
case 'console':
|
|
430
|
+
if (process.env.NODE_ENV === 'production') {
|
|
431
|
+
console.warn(
|
|
432
|
+
'Using console strategy in production - emails not actually sent'
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
break;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
throw new Error(`Email send failed: ${result.error}`);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return result;
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
## 🔧 Startup Validation
|
|
446
|
+
|
|
447
|
+
### **Basic App Startup Validation**
|
|
448
|
+
|
|
449
|
+
```typescript
|
|
450
|
+
import { emailClass } from '@bloomneo/appkit/email';
|
|
451
|
+
|
|
452
|
+
async function startApp() {
|
|
453
|
+
try {
|
|
454
|
+
// Validate email configuration at startup
|
|
455
|
+
emailClass.validateConfig();
|
|
456
|
+
|
|
457
|
+
const strategy = emailClass.getStrategy();
|
|
458
|
+
console.log(`📧 Email configured with ${strategy} strategy`);
|
|
459
|
+
|
|
460
|
+
// Start your app
|
|
461
|
+
app.listen(3000, () => {
|
|
462
|
+
console.log('🚀 Server started on port 3000');
|
|
463
|
+
});
|
|
464
|
+
} catch (error) {
|
|
465
|
+
console.error('❌ Startup validation failed:', error.message);
|
|
466
|
+
process.exit(1);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
### **Production Startup Validation**
|
|
472
|
+
|
|
473
|
+
```typescript
|
|
474
|
+
async function validateProductionSetup() {
|
|
475
|
+
if (process.env.NODE_ENV !== 'production') return;
|
|
476
|
+
|
|
477
|
+
// Check if real email provider is configured
|
|
478
|
+
if (!emailClass.hasProvider()) {
|
|
479
|
+
throw new Error(
|
|
480
|
+
'No email provider configured in production. ' +
|
|
481
|
+
'Set RESEND_API_KEY or SMTP_HOST environment variable.'
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Validate FROM address is set
|
|
486
|
+
if (!process.env.BLOOM_EMAIL_FROM_EMAIL) {
|
|
487
|
+
console.warn(
|
|
488
|
+
'⚠️ No FROM email configured. Using default. ' +
|
|
489
|
+
'Set BLOOM_EMAIL_FROM_EMAIL for professional emails.'
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
console.log('✅ Email system validated successfully');
|
|
494
|
+
}
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
### **Health Check Endpoint**
|
|
498
|
+
|
|
499
|
+
```typescript
|
|
500
|
+
// Express middleware for email health check
|
|
501
|
+
function emailHealthCheck(req, res) {
|
|
502
|
+
try {
|
|
503
|
+
const config = emailClass.getConfig();
|
|
504
|
+
const hasProvider = emailClass.hasProvider();
|
|
505
|
+
|
|
506
|
+
res.json({
|
|
507
|
+
status: 'ok',
|
|
508
|
+
strategy: config.strategy,
|
|
509
|
+
hasProvider,
|
|
510
|
+
fromEmail: config.fromEmail,
|
|
511
|
+
ready: hasProvider || process.env.NODE_ENV === 'development',
|
|
512
|
+
});
|
|
513
|
+
} catch (error) {
|
|
514
|
+
res.status(500).json({
|
|
515
|
+
status: 'error',
|
|
516
|
+
error: error.message,
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
app.get('/health/email', emailHealthCheck);
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
## 🌍 Environment Variables
|
|
525
|
+
|
|
526
|
+
### **Resend (Recommended)**
|
|
527
|
+
|
|
528
|
+
```bash
|
|
529
|
+
# Modern email service with great deliverability
|
|
530
|
+
RESEND_API_KEY=re_your_api_key_here
|
|
531
|
+
|
|
532
|
+
# Optional: Custom FROM address
|
|
533
|
+
BLOOM_EMAIL_FROM_EMAIL=noreply@yourdomain.com
|
|
534
|
+
BLOOM_EMAIL_FROM_NAME="Your App Name"
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
### **SMTP (Universal)**
|
|
538
|
+
|
|
539
|
+
```bash
|
|
540
|
+
# Works with Gmail, Outlook, custom servers
|
|
541
|
+
SMTP_HOST=smtp.gmail.com
|
|
542
|
+
SMTP_PORT=587
|
|
543
|
+
SMTP_USER=your-email@gmail.com
|
|
544
|
+
SMTP_PASS=your-app-password
|
|
545
|
+
|
|
546
|
+
# Optional: Security settings
|
|
547
|
+
SMTP_SECURE=false # true for port 465, false for 587
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
### **Console (Development)**
|
|
551
|
+
|
|
552
|
+
```bash
|
|
553
|
+
# No configuration needed!
|
|
554
|
+
# Automatically used when no email provider is set
|
|
555
|
+
|
|
556
|
+
# Optional: Customize console output
|
|
557
|
+
BLOOM_EMAIL_CONSOLE_FORMAT=detailed # or 'simple'
|
|
558
|
+
BLOOM_EMAIL_CONSOLE_PREVIEW=true # Show email content
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
## 🔧 Platform Setup
|
|
562
|
+
|
|
563
|
+
### **Local Development**
|
|
564
|
+
|
|
565
|
+
```bash
|
|
566
|
+
# No setup needed - beautiful console preview
|
|
567
|
+
npm start
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
### **Resend (Recommended)**
|
|
571
|
+
|
|
572
|
+
1. Sign up at [resend.com](https://resend.com)
|
|
573
|
+
2. Get your API key
|
|
574
|
+
3. Set `RESEND_API_KEY=re_your_key`
|
|
575
|
+
4. Done! ✅
|
|
576
|
+
|
|
577
|
+
### **Gmail SMTP**
|
|
578
|
+
|
|
579
|
+
```bash
|
|
580
|
+
# Enable 2FA and create App Password
|
|
581
|
+
SMTP_HOST=smtp.gmail.com
|
|
582
|
+
SMTP_PORT=587
|
|
583
|
+
SMTP_USER=your-email@gmail.com
|
|
584
|
+
SMTP_PASS=your-16-char-app-password
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
### **Outlook/Hotmail SMTP**
|
|
588
|
+
|
|
589
|
+
```bash
|
|
590
|
+
SMTP_HOST=smtp-mail.outlook.com
|
|
591
|
+
SMTP_PORT=587
|
|
592
|
+
SMTP_USER=your-email@outlook.com
|
|
593
|
+
SMTP_PASS=your-password
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
### **Custom SMTP Server**
|
|
597
|
+
|
|
598
|
+
```bash
|
|
599
|
+
SMTP_HOST=mail.yourdomain.com
|
|
600
|
+
SMTP_PORT=587
|
|
601
|
+
SMTP_USER=noreply@yourdomain.com
|
|
602
|
+
SMTP_PASS=your-password
|
|
603
|
+
SMTP_SECURE=false
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
## 🚀 Production Deployment
|
|
607
|
+
|
|
608
|
+
### **Vercel**
|
|
609
|
+
|
|
610
|
+
```bash
|
|
611
|
+
# Add to Vercel environment variables
|
|
612
|
+
RESEND_API_KEY=re_your_api_key
|
|
613
|
+
BLOOM_EMAIL_FROM_EMAIL=noreply@yourdomain.com
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
### **Railway/Heroku**
|
|
617
|
+
|
|
618
|
+
```bash
|
|
619
|
+
# Add to platform environment variables
|
|
620
|
+
RESEND_API_KEY=re_your_api_key
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
### **Docker**
|
|
624
|
+
|
|
625
|
+
```yaml
|
|
626
|
+
# docker-compose.yml
|
|
627
|
+
services:
|
|
628
|
+
app:
|
|
629
|
+
image: my-app
|
|
630
|
+
environment:
|
|
631
|
+
RESEND_API_KEY: re_your_api_key
|
|
632
|
+
BLOOM_EMAIL_FROM_EMAIL: noreply@yourdomain.com
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
### **AWS/VPS**
|
|
636
|
+
|
|
637
|
+
```bash
|
|
638
|
+
# Add to your deployment script
|
|
639
|
+
export RESEND_API_KEY=re_your_api_key
|
|
640
|
+
export BLOOM_EMAIL_FROM_EMAIL=noreply@yourdomain.com
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
## 🤖 LLM Guidelines
|
|
644
|
+
|
|
645
|
+
### **Essential Patterns**
|
|
646
|
+
|
|
647
|
+
```typescript
|
|
648
|
+
// ✅ ALWAYS use these patterns
|
|
649
|
+
import { emailClass } from '@bloomneo/appkit/email';
|
|
650
|
+
const email = emailClass.get();
|
|
651
|
+
|
|
652
|
+
// ✅ Basic email sending
|
|
653
|
+
await email.send({
|
|
654
|
+
to: 'user@example.com',
|
|
655
|
+
subject: 'Subject',
|
|
656
|
+
text: 'Plain text content',
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
// ✅ HTML email with fallback
|
|
660
|
+
await email.send({
|
|
661
|
+
to: 'user@example.com',
|
|
662
|
+
subject: 'Subject',
|
|
663
|
+
html: '<h1>HTML content</h1>',
|
|
664
|
+
text: 'Plain text fallback',
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
// ✅ Convenience methods
|
|
668
|
+
await emailClass.sendText('user@example.com', 'Subject', 'Message');
|
|
669
|
+
|
|
670
|
+
// ✅ Template usage
|
|
671
|
+
await email.sendTemplate('welcome', {
|
|
672
|
+
to: 'user@example.com',
|
|
673
|
+
name: 'John',
|
|
674
|
+
appName: 'MyApp',
|
|
675
|
+
});
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
### **Anti-Patterns to Avoid**
|
|
679
|
+
|
|
680
|
+
```typescript
|
|
681
|
+
// ❌ DON'T create email strategies directly
|
|
682
|
+
const resend = new ResendStrategy(); // Use emailClass.get() instead
|
|
683
|
+
|
|
684
|
+
// ❌ DON'T forget error handling
|
|
685
|
+
await email.send(data); // Check result.success
|
|
686
|
+
|
|
687
|
+
// ❌ DON'T send without subject
|
|
688
|
+
await email.send({ to: 'user@example.com', text: 'Hi' }); // Missing subject
|
|
689
|
+
|
|
690
|
+
// ❌ DON'T send without content
|
|
691
|
+
await email.send({ to: 'user@example.com', subject: 'Hi' }); // Missing text/html
|
|
692
|
+
|
|
693
|
+
// ❌ DON'T ignore email validation
|
|
694
|
+
await email.send({ to: 'invalid-email', subject: 'Hi', text: 'Hello' });
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
### **Common Patterns**
|
|
698
|
+
|
|
699
|
+
```typescript
|
|
700
|
+
// Email with error handling
|
|
701
|
+
const result = await emailClass.send({
|
|
702
|
+
to: user.email,
|
|
703
|
+
subject: 'Welcome!',
|
|
704
|
+
text: 'Welcome to our app!',
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
if (!result.success) {
|
|
708
|
+
console.error('Email failed:', result.error);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// Conditional email sending
|
|
712
|
+
if (emailClass.hasProvider()) {
|
|
713
|
+
await emailClass.send(emailData);
|
|
714
|
+
} else {
|
|
715
|
+
console.log('No email provider configured');
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// Batch email sending
|
|
719
|
+
const emails = users.map((user) => ({
|
|
720
|
+
to: user.email,
|
|
721
|
+
subject: 'Newsletter',
|
|
722
|
+
html: newsletterHtml,
|
|
723
|
+
}));
|
|
724
|
+
|
|
725
|
+
await emailClass.get().sendBatch(emails);
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
## 📈 Performance
|
|
729
|
+
|
|
730
|
+
- **Resend Strategy**: ~100-500ms per email
|
|
731
|
+
- **SMTP Strategy**: ~200-1000ms per email (depends on server)
|
|
732
|
+
- **Console Strategy**: ~1-5ms (instant logging)
|
|
733
|
+
- **Batch Sending**: Processes 10 emails concurrently by default
|
|
734
|
+
- **Memory Usage**: <2MB baseline usage
|
|
735
|
+
|
|
736
|
+
## 🔍 TypeScript Support
|
|
737
|
+
|
|
738
|
+
```typescript
|
|
739
|
+
import type { EmailData, EmailResult } from '@bloomneo/appkit/email';
|
|
740
|
+
|
|
741
|
+
// Strongly typed email operations
|
|
742
|
+
const emailData: EmailData = {
|
|
743
|
+
to: 'user@example.com',
|
|
744
|
+
subject: 'Hello',
|
|
745
|
+
text: 'Hello world!',
|
|
746
|
+
};
|
|
747
|
+
|
|
748
|
+
const result: EmailResult = await emailClass.send(emailData);
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
## 📄 License
|
|
752
|
+
|
|
753
|
+
MIT © [Bloomneo](https://github.com/bloomneo)
|
|
754
|
+
|
|
755
|
+
---
|
|
756
|
+
|
|
757
|
+
<p align="center">
|
|
758
|
+
Built with ❤️ in India by the <a href="https://github.com/orgs/bloomneo/people">Bloomneo Team</a>
|
|
759
|
+
</p>
|