@kumix/email 0.1.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 ADDED
@@ -0,0 +1,395 @@
1
+ # @kumix/email
2
+
3
+ [![Version](https://img.shields.io/npm/v/@kumix/email.svg)](https://www.npmjs.com/package/@kumix/email)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ A flexible email package for SaaS applications. Supports Resend and Nodemailer/SMTP, React
7
+ Email templates, and all JavaScript runtimes.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ # Node.js / Bun
13
+ bun add @kumix/email resend
14
+ # or
15
+ npm install @kumix/email resend
16
+
17
+ # Optional: install nodemailer for SMTP (Node.js / Bun / Deno only)
18
+ bun add nodemailer
19
+ ```
20
+
21
+ ## Configuration by Runtime
22
+
23
+ ### Node.js / Bun
24
+
25
+ Set environment variables and call `createEmail()` — it auto-detects your provider.
26
+
27
+ ```bash
28
+ # .env
29
+ KUMIX_EMAIL_RESEND_API_KEY=re_xxxx
30
+ KUMIX_EMAIL_FROM_NAME=My App
31
+ KUMIX_EMAIL_FROM_EMAIL=noreply@myapp.com
32
+ KUMIX_EMAIL_REPLY_TO=support@myapp.com
33
+ ```
34
+
35
+ ```typescript
36
+ import { createEmail } from "@kumix/email";
37
+
38
+ // Reads process.env automatically
39
+ const email = createEmail();
40
+
41
+ await email.sendEmail({
42
+ to: "user@example.com",
43
+ subject: "Welcome!",
44
+ html: "<h1>Hello World</h1>",
45
+ });
46
+ ```
47
+
48
+ Bun works identically to Node.js — `process.env` is natively supported.
49
+
50
+ ### Cloudflare Workers
51
+
52
+ Pass `ctx.env` as the second argument. Resend only (Nodemailer is Node-only).
53
+
54
+ ```toml
55
+ # wrangler.toml
56
+ [vars]
57
+ KUMIX_EMAIL_RESEND_API_KEY = "re_xxxx"
58
+ KUMIX_EMAIL_FROM_NAME = "My App"
59
+ KUMIX_EMAIL_FROM_EMAIL = "noreply@myapp.com"
60
+ ```
61
+
62
+ ```typescript
63
+ // worker.ts
64
+ import { createEmail, type EnvRecord } from "@kumix/email";
65
+
66
+ interface Env extends EnvRecord {
67
+ KUMIX_EMAIL_RESEND_API_KEY: string;
68
+ KUMIX_EMAIL_FROM_NAME: string;
69
+ KUMIX_EMAIL_FROM_EMAIL: string;
70
+ }
71
+
72
+ export default {
73
+ async fetch(req: Request, env: Env) {
74
+ const email = createEmail(undefined, env);
75
+
76
+ await email.sendEmail({
77
+ to: "user@example.com",
78
+ subject: "Hello from Workers!",
79
+ html: "<p>Sent from Cloudflare Workers</p>",
80
+ });
81
+
82
+ return new Response("Email sent");
83
+ },
84
+ };
85
+ ```
86
+
87
+ With React templates in Workers:
88
+
89
+ ```typescript
90
+ import { createEmail, type EnvRecord } from "@kumix/email";
91
+
92
+ interface WelcomeProps {
93
+ userName: string;
94
+ loginUrl: string;
95
+ }
96
+
97
+ const WelcomeEmail: React.FC<WelcomeProps> = ({ userName, loginUrl }) => (
98
+ <div>
99
+ <h1>Welcome, {userName}!</h1>
100
+ <a href={loginUrl}>Login here</a>
101
+ </div>
102
+ );
103
+
104
+ export default {
105
+ async fetch(req: Request, env: Env) {
106
+ const email = createEmail(undefined, env);
107
+
108
+ await email.sendTemplate(
109
+ WelcomeEmail,
110
+ { userName: "Alice", loginUrl: "https://app.example.com" },
111
+ { to: "alice@example.com", subject: "Welcome!" },
112
+ );
113
+
114
+ return new Response("Template email sent");
115
+ },
116
+ };
117
+ ```
118
+
119
+ ### Deno
120
+
121
+ Deno supports `process.env` natively (requires `--allow-env`). You can also pass env explicitly.
122
+
123
+ ```bash
124
+ # .env
125
+ KUMIX_EMAIL_RESEND_API_KEY=re_xxxx
126
+ KUMIX_EMAIL_FROM_NAME=My App
127
+ KUMIX_EMAIL_FROM_EMAIL=noreply@myapp.com
128
+ ```
129
+
130
+ ```typescript
131
+ // main.ts
132
+ import { createEmail } from "@kumix/email";
133
+
134
+ // Option A: let the package read process.env (Deno supports it)
135
+ const email = createEmail();
136
+
137
+ // Option B: pass env explicitly
138
+ const email = createEmail(undefined, Deno.env.toObject());
139
+
140
+ await email.sendEmail({
141
+ to: "user@example.com",
142
+ subject: "Hello from Deno!",
143
+ html: "<h1>Deno works</h1>",
144
+ });
145
+ ```
146
+
147
+ ```bash
148
+ deno run --allow-env --allow-net main.ts
149
+ ```
150
+
151
+ With Nodemailer (SMTP) in Deno:
152
+
153
+ ```typescript
154
+ import { createNodemailer } from "@kumix/email";
155
+
156
+ const email = createNodemailer({
157
+ from: { name: "My App", email: "noreply@myapp.com" },
158
+ smtp: {
159
+ host: "smtp.gmail.com",
160
+ port: 587,
161
+ secure: false,
162
+ auth: { user: "you@gmail.com", pass: "app-password" },
163
+ },
164
+ });
165
+
166
+ await email.sendEmail({
167
+ to: "user@example.com",
168
+ subject: "SMTP from Deno",
169
+ html: "<p>Sent via SMTP</p>",
170
+ });
171
+ ```
172
+
173
+ ### Browser
174
+
175
+ Use manual config — no env vars. Resend only, since Nodemailer requires Node.js APIs.
176
+
177
+ ```typescript
178
+ import { createResend } from "@kumix/email";
179
+
180
+ const email = createResend({
181
+ apiKey: "re_xxxx",
182
+ from: { name: "My App", email: "noreply@myapp.com" },
183
+ });
184
+
185
+ await email.sendEmail({
186
+ to: "user@example.com",
187
+ subject: "Hello from the browser!",
188
+ html: "<p>Sent from client-side JavaScript</p>",
189
+ });
190
+ ```
191
+
192
+ > **Security note**: Exposing your Resend API key in browser code is insecure. Use this
193
+ > pattern behind an authenticated route in an admin dashboard, or proxy through your backend.
194
+
195
+ ### Manual Configuration (any runtime)
196
+
197
+ Skip env vars entirely — pass your config object directly. Works in every runtime.
198
+
199
+ ```typescript
200
+ import { createResend, createNodemailer } from "@kumix/email";
201
+
202
+ // Resend — works everywhere
203
+ const resend = createResend({
204
+ apiKey: "re_xxxx",
205
+ from: { name: "My App", email: "noreply@myapp.com" },
206
+ });
207
+
208
+ // Nodemailer — Node.js / Bun / Deno only
209
+ const nodemailer = createNodemailer({
210
+ from: { name: "My App", email: "noreply@myapp.com" },
211
+ smtp: {
212
+ host: "smtp.gmail.com",
213
+ port: 587,
214
+ secure: false,
215
+ auth: { user: "you@gmail.com", pass: "app-password" },
216
+ },
217
+ });
218
+ ```
219
+
220
+ ## `EnvRecord` Pattern
221
+
222
+ All config and factory functions accept an optional `env` parameter of type
223
+ `Record<string, string | undefined>`. On Node.js / Bun it defaults to
224
+ `process.env`. On other runtimes, pass your environment explicitly:
225
+
226
+ ```typescript
227
+ import {
228
+ createEmail,
229
+ createResend,
230
+ hasEmailConfig,
231
+ loadEmailConfig,
232
+ validateEmailEnvVars,
233
+ type EnvRecord,
234
+ } from "@kumix/email";
235
+
236
+ // All accept env as the last argument:
237
+ const email = createEmail(undefined, myEnv);
238
+ const config = loadEmailConfig(myEnv);
239
+ const ready = hasEmailConfig(myEnv);
240
+ const result = validateEmailEnvVars(myEnv);
241
+ ```
242
+
243
+ ## Sending Emails
244
+
245
+ ### HTML Emails
246
+
247
+ ```typescript
248
+ await email.sendEmail({
249
+ to: "user@example.com",
250
+ subject: "Hello!",
251
+ html: "<p>This is an HTML email</p>",
252
+ text: "This is the plain text version",
253
+ });
254
+ ```
255
+
256
+ ### React Templates
257
+
258
+ ```typescript
259
+ import { EmailTemplate } from "./templates/EmailTemplate";
260
+
261
+ await email.sendTemplate(
262
+ EmailTemplate,
263
+ { userName: "John", resetLink: "https://..." },
264
+ { to: "user@example.com", subject: "Password Reset" },
265
+ );
266
+ ```
267
+
268
+ ### Advanced Options
269
+
270
+ ```typescript
271
+ await email.sendEmail({
272
+ to: ["user1@example.com", "user2@example.com"],
273
+ cc: "manager@example.com",
274
+ bcc: "admin@example.com",
275
+ subject: "Important Update",
276
+ html: "<h1>Update</h1>",
277
+ attachments: [
278
+ {
279
+ filename: "document.pdf",
280
+ content: pdfBuffer,
281
+ contentType: "application/pdf",
282
+ },
283
+ ],
284
+ headers: { "X-Custom-Header": "value" },
285
+ tags: { category: "notification" },
286
+ });
287
+ ```
288
+
289
+ ## Runtime Compatibility
290
+
291
+ | Feature | Node.js | Bun | CF Workers | Deno | Browser |
292
+ | ------------------- | ------- | --- | ---------- | ---- | ------- |
293
+ | Resend provider | Yes | Yes | Yes | Yes | Yes |
294
+ | Nodemailer/SMTP | Yes | Yes | No | Yes | No |
295
+ | `createEmail()` | Yes | Yes | Yes\* | Yes | No |
296
+ | Manual config | Yes | Yes | Yes | Yes | Yes |
297
+ | React templates | Yes | Yes | Yes | Yes | Yes |
298
+ | Env auto-detection | Yes | Yes | No† | Yes | No |
299
+ | `helpers` subpath | Yes | Yes | Yes | Yes | Yes |
300
+ | `components` export | Yes | Yes | Yes | Yes | Yes |
301
+
302
+ \* Pass env as second argument: `createEmail(undefined, ctx.env)`.
303
+ † Pass `env` explicitly via `EnvRecord` pattern.
304
+
305
+ ## API Reference
306
+
307
+ ### Factory Functions
308
+
309
+ - `createEmail(config?, env?)` — Create from config or env, auto-detects provider
310
+ - `createResend(config?, env?)` — Create Resend email service
311
+ - `createNodemailer(config?, env?)` — Create Nodemailer email service
312
+ - `isEmailConfigured(env?)` — Check if email is configured via env
313
+ - `getConfiguredProvider(env?)` — Get the detected provider from env
314
+
315
+ ### EmailService Methods
316
+
317
+ - `sendEmail(options)` — Send HTML/text email
318
+ - `sendTemplate(component, props, options)` — Send React template email
319
+ - `getConfig()` — Get current configuration
320
+ - `updateConfig(config)` — Update configuration at runtime
321
+ - `validateConfig()` — Validate current configuration
322
+
323
+ ### Config Helpers
324
+
325
+ - `loadEmailConfig(env?)` — Auto-detect and load config from env
326
+ - `loadResendConfig(env?)` — Load Resend config from env
327
+ - `loadNodemailerConfig(env?)` — Load SMTP config from env
328
+ - `validateEmailEnvVars(env?)` — Validate any configured provider
329
+ - `validateResendEnvVars(env?)` — Validate Resend env vars
330
+ - `validateNodemailerEnvVars(env?)` — Validate SMTP env vars
331
+ - `getEmailEnvVars(env?)` — Get env vars with secrets masked
332
+ - `hasEmailConfig(env?)` — Check if any provider is configured
333
+
334
+ ### Helpers (`@kumix/email/helpers`)
335
+
336
+ - `renderEmailTemplate(Component, props)` — Render React component to HTML string
337
+ - `htmlToText(html)` — Convert HTML to plain text
338
+ - `isValidEmail(email)` — Validate email format (RFC 5322)
339
+ - `validateEmails(emails)` — Validate single or multiple emails
340
+ - `filterValidEmails(emails)` — Filter invalid emails from a list
341
+ - `formatEmailAddress(name, email)` — Format as `Name <email>`
342
+ - `extractEmail(formatted)` — Extract email from formatted address
343
+ - `extractDisplayName(formatted)` — Extract display name from formatted address
344
+ - `sanitizeHtml(html)` — Strip dangerous HTML (scripts, event handlers)
345
+ - `truncateText(text, maxLength, ellipsis?)` — Truncate with ellipsis
346
+ - `generatePreviewText(html, maxLength?)` — Generate email preview text
347
+ - `generateUnsubscribeLink(baseUrl, email, token?)` — Create unsubscribe URL
348
+ - `generateTrackingPixel(baseUrl, emailId, recipientId)` — Create tracking pixel URL
349
+ - `addUtmParams(url, params)` — Add UTM tracking parameters
350
+ - `parseEmailList(str)` — Parse comma/semicolon-separated emails
351
+ - `deduplicateEmails(emails)` — Deduplicate (case-insensitive)
352
+ - `chunkEmails(emails, chunkSize?)` — Split into batches (default 100)
353
+ - `getMimeType(filename)` — Get MIME type from extension
354
+ - `formatFileSize(bytes, decimals?)` — Format file size in human-readable form
355
+
356
+ ### Types
357
+
358
+ ```typescript
359
+ import type {
360
+ EmailConfig,
361
+ ResendConfig,
362
+ NodemailerConfig,
363
+ SendEmailOptions,
364
+ EmailResult,
365
+ EmailProvider,
366
+ EnvRecord,
367
+ // ... and more
368
+ } from "@kumix/email";
369
+ ```
370
+
371
+ ## Environment Variables
372
+
373
+ | Variable | Provider | Required |
374
+ | ---------------------------- | ---------- | -------- |
375
+ | `KUMIX_EMAIL_RESEND_API_KEY` | Resend | Yes |
376
+ | `KUMIX_EMAIL_FROM_NAME` | All | Yes |
377
+ | `KUMIX_EMAIL_FROM_EMAIL` | All | Yes |
378
+ | `KUMIX_EMAIL_REPLY_TO` | All | No |
379
+ | `KUMIX_EMAIL_SMTP_HOST` | Nodemailer | Yes |
380
+ | `KUMIX_EMAIL_SMTP_PORT` | Nodemailer | Yes |
381
+ | `KUMIX_EMAIL_SMTP_SECURE` | Nodemailer | No |
382
+ | `KUMIX_EMAIL_SMTP_USER` | Nodemailer | Yes |
383
+ | `KUMIX_EMAIL_SMTP_PASS` | Nodemailer | Yes |
384
+
385
+ Legacy env vars (`RESEND_API_KEY`, `SMTP_HOST`, etc.) are also supported for backward compatibility.
386
+
387
+ ## Links
388
+
389
+ - [npm Package](https://www.npmjs.com/package/@kumix/email)
390
+ - [Contributing Guide](../../CONTRIBUTING.md)
391
+ - [License](../../LICENSE)
392
+
393
+ ## License
394
+
395
+ MIT © [Kumix Labs](../../LICENSE)
@@ -0,0 +1,3 @@
1
+ import * as ReactEmailComponents from "react-email";
2
+ export * from "react-email";
3
+ export { ReactEmailComponents };
@@ -0,0 +1,11 @@
1
+ import * as ReactEmailComponents from "react-email";
2
+ export * from "react-email";
3
+ //#region src/components.ts
4
+ /**
5
+ * React Email components
6
+ * Re-exports all React Email components for building email templates
7
+ */
8
+ //#endregion
9
+ export { ReactEmailComponents };
10
+
11
+ //# sourceMappingURL=components.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"components.js","names":[],"sources":["../src/components.ts"],"sourcesContent":["/**\n * React Email components\n * Re-exports all React Email components for building email templates\n */\n\n// Import all components as a namespace for convenience\nimport * as ReactEmailComponents from \"react-email\";\n\n/**\n * Re-export all React Email components for easy access\n * Available components include Html, Head, Body, Container, Section, Row, Column, Text, Heading, Link, Button, Img, Hr, Font, Preview and more\n * @public\n *\n * @see https://react.email/docs/components/html\n */\nexport * from \"react-email\";\n\n/**\n * Export the namespace as well for alternative import style\n * @public\n *\n * @example\n * ```typescript\n * import { ReactEmailComponents } from '@kumix/email/components';\n *\n * const MyEmail = () => (\n * <ReactEmailComponents.Html>\n * <ReactEmailComponents.Body>\n * <ReactEmailComponents.Text>Hello World</ReactEmailComponents.Text>\n * </ReactEmailComponents.Body>\n * </ReactEmailComponents.Html>\n * );\n * ```\n */\nexport { ReactEmailComponents };\n"],"mappings":""}