@kumix/email
v0.1.3
Published
Email templates and sending utilities for SaaS applications.
Maintainers
Readme
@kumix/email
A flexible email package for SaaS applications. Supports Resend and Nodemailer/SMTP, React Email templates, and all JavaScript runtimes.
Installation
# Node.js / Bun
bun add @kumix/email resend
# or
npm install @kumix/email resend
# Optional: install nodemailer for SMTP (Node.js / Bun / Deno only)
bun add nodemailerConfiguration by Runtime
Node.js / Bun
Set environment variables and call createEmail() — it auto-detects your provider.
# .env
KUMIX_EMAIL_RESEND_API_KEY=re_xxxx
KUMIX_EMAIL_FROM_NAME=My App
[email protected]
[email protected]import { createEmail } from "@kumix/email";
// Reads process.env automatically
const email = createEmail();
await email.sendEmail({
to: "[email protected]",
subject: "Welcome!",
html: "<h1>Hello World</h1>",
});Bun works identically to Node.js — process.env is natively supported.
Cloudflare Workers
Pass ctx.env as the second argument. Resend only (Nodemailer is Node-only).
# wrangler.toml
[vars]
KUMIX_EMAIL_RESEND_API_KEY = "re_xxxx"
KUMIX_EMAIL_FROM_NAME = "My App"
KUMIX_EMAIL_FROM_EMAIL = "[email protected]"// worker.ts
import { createEmail, type EnvRecord } from "@kumix/email";
interface Env extends EnvRecord {
KUMIX_EMAIL_RESEND_API_KEY: string;
KUMIX_EMAIL_FROM_NAME: string;
KUMIX_EMAIL_FROM_EMAIL: string;
}
export default {
async fetch(req: Request, env: Env) {
const email = createEmail(undefined, env);
await email.sendEmail({
to: "[email protected]",
subject: "Hello from Workers!",
html: "<p>Sent from Cloudflare Workers</p>",
});
return new Response("Email sent");
},
};With React templates in Workers:
import { createEmail, type EnvRecord } from "@kumix/email";
interface WelcomeProps {
userName: string;
loginUrl: string;
}
const WelcomeEmail: React.FC<WelcomeProps> = ({ userName, loginUrl }) => (
<div>
<h1>Welcome, {userName}!</h1>
<a href={loginUrl}>Login here</a>
</div>
);
export default {
async fetch(req: Request, env: Env) {
const email = createEmail(undefined, env);
await email.sendTemplate(
WelcomeEmail,
{ userName: "Alice", loginUrl: "https://app.example.com" },
{ to: "[email protected]", subject: "Welcome!" },
);
return new Response("Template email sent");
},
};Deno
Deno supports process.env natively (requires --allow-env). You can also pass env explicitly.
# .env
KUMIX_EMAIL_RESEND_API_KEY=re_xxxx
KUMIX_EMAIL_FROM_NAME=My App
[email protected]// main.ts
import { createEmail } from "@kumix/email";
// Option A: let the package read process.env (Deno supports it)
const email = createEmail();
// Option B: pass env explicitly
const email = createEmail(undefined, Deno.env.toObject());
await email.sendEmail({
to: "[email protected]",
subject: "Hello from Deno!",
html: "<h1>Deno works</h1>",
});deno run --allow-env --allow-net main.tsWith Nodemailer (SMTP) in Deno:
import { createNodemailer } from "@kumix/email";
const email = createNodemailer({
from: { name: "My App", email: "[email protected]" },
smtp: {
host: "smtp.gmail.com",
port: 587,
secure: false,
auth: { user: "[email protected]", pass: "app-password" },
},
});
await email.sendEmail({
to: "[email protected]",
subject: "SMTP from Deno",
html: "<p>Sent via SMTP</p>",
});
nodemaileris loaded via a dynamicimport()and is a Node-oriented package. In Deno, install it through annpm:specifier (deno run --allow-env --allow-net ...withnpm:nodemailer@^9resolvable). Bun and Node.js resolve it fromnode_modulesautomatically.
Browser
Use manual config — no env vars. Resend only, since Nodemailer requires Node.js APIs.
import { createResend } from "@kumix/email";
const email = createResend({
apiKey: "re_xxxx",
from: { name: "My App", email: "[email protected]" },
});
await email.sendEmail({
to: "[email protected]",
subject: "Hello from the browser!",
html: "<p>Sent from client-side JavaScript</p>",
});Security note: Exposing your Resend API key in browser code is insecure. Use this pattern behind an authenticated route in an admin dashboard, or proxy through your backend.
Manual Configuration (any runtime)
Skip env vars entirely — pass your config object directly. Works in every runtime.
import { createResend, createNodemailer } from "@kumix/email";
// Resend — works everywhere
const resend = createResend({
apiKey: "re_xxxx",
from: { name: "My App", email: "[email protected]" },
});
// Nodemailer — Node.js / Bun / Deno only
const nodemailer = createNodemailer({
from: { name: "My App", email: "[email protected]" },
smtp: {
host: "smtp.gmail.com",
port: 587,
secure: false,
auth: { user: "[email protected]", pass: "app-password" },
},
});EnvRecord Pattern
All config and factory functions accept an optional env parameter of type
Record<string, string | undefined>. On Node.js / Bun it defaults to
process.env. On other runtimes, pass your environment explicitly:
import {
createEmail,
createResend,
hasEmailConfig,
loadEmailConfig,
validateEmailEnvVars,
type EnvRecord,
} from "@kumix/email";
// All accept env as the last argument:
const email = createEmail(undefined, myEnv);
const config = loadEmailConfig(myEnv);
const ready = hasEmailConfig(myEnv);
const result = validateEmailEnvVars(myEnv);Sending Emails
HTML Emails
await email.sendEmail({
to: "[email protected]",
subject: "Hello!",
html: "<p>This is an HTML email</p>",
text: "This is the plain text version",
});React Templates
import { EmailTemplate } from "./templates/EmailTemplate";
await email.sendTemplate(
EmailTemplate,
{ userName: "John", resetLink: "https://..." },
{ to: "[email protected]", subject: "Password Reset" },
);Advanced Options
await email.sendEmail({
to: ["[email protected]", "[email protected]"],
cc: "[email protected]",
bcc: "[email protected]",
subject: "Important Update",
html: "<h1>Update</h1>",
attachments: [
{
filename: "document.pdf",
content: pdfBuffer,
contentType: "application/pdf",
},
],
headers: { "X-Custom-Header": "value" },
tags: { category: "notification" },
});Priority & Scheduled Delivery
Both providers support a numeric priority (1 = highest … 5 = lowest). It is forwarded to
Nodemailer as the priority field plus an X-Priority header, and to Resend as an X-Priority
header.
await email.sendEmail({
to: "[email protected]",
subject: "Incident",
html: "<p>High priority</p>",
priority: 1, // 1..5
});Resend also supports deferred delivery via scheduledAt (an ISO 8601 timestamp). It is forwarded
as Resend's scheduled_at field. Nodemailer ignores this option.
await email.sendEmail({
to: "[email protected]",
subject: "Scheduled",
html: "<p>Sent later</p>",
scheduledAt: new Date("2030-01-01T09:00:00Z"),
});Runtime Compatibility
| Feature | Node.js | Bun | CF Workers | Deno | Browser |
| ------------------- | ------- | --- | ---------- | ---- | ------- |
| Resend provider | Yes | Yes | Yes | Yes | Yes |
| Nodemailer/SMTP | Yes | Yes | No | Yes | No |
| createEmail() | Yes | Yes | Yes* | Yes | No |
| Manual config | Yes | Yes | Yes | Yes | Yes |
| React templates | Yes | Yes | Yes | Yes | Yes |
| Env auto-detection | Yes | Yes | No† | Yes | No |
| helpers subpath | Yes | Yes | Yes | Yes | Yes |
| components export | Yes | Yes | Yes | Yes | Yes |
* Pass env as second argument: createEmail(undefined, ctx.env).
† Pass env explicitly via EnvRecord pattern.
API Reference
Factory Functions
createEmail(config?, env?)— Create from config or env, auto-detects providercreateResend(config?, env?)— Create Resend email servicecreateNodemailer(config?, env?)— Create Nodemailer email serviceisEmailConfigured(env?)— Check if email is configured via envgetConfiguredProvider(env?)— Get the detected provider from env
EmailService Methods
sendEmail(options)— Send HTML/text emailsendTemplate(component, props, options)— Send React template emailgetConfig()— Get current configurationupdateConfig(config)— Update configuration at runtimevalidateConfig()— Validate current configuration
Config Helpers
loadEmailConfig(env?)— Auto-detect and load config from envloadResendConfig(env?)— Load Resend config from envloadNodemailerConfig(env?)— Load SMTP config from envvalidateEmailEnvVars(env?)— Validate any configured providervalidateResendEnvVars(env?)— Validate Resend env varsvalidateNodemailerEnvVars(env?)— Validate SMTP env varsgetEmailEnvVars(env?)— Get env vars with secrets maskedhasEmailConfig(env?)— Check if any provider is configured
Helpers (@kumix/email/helpers)
renderEmailTemplate(Component, props)— Render React component to HTML stringhtmlToText(html)— Convert HTML to plain textisValidEmail(email)— Validate common email format (permissive sanity check, not full RFC 5322)validateEmails(emails)— Validate single or multiple emailsfilterValidEmails(emails)— Filter invalid emails from a listformatEmailAddress(name, email)— Format as"Name" <email>(display name is quoted/escaped per RFC 5322; CR/LF stripped to prevent header injection)extractEmail(formatted)— Extract email from formatted addressextractDisplayName(formatted)— Extract display name from formatted addresssanitizeHtml(html)— Coarse cleanup that strips<script>/<style>/<iframe>/<object>/<embed>/<form>/<svg>blocks,on*event handlers, andjavascript:URLs. ⚠️ NOT a security sanitizer — for untrusted input use a purpose-built sanitizer (e.g. DOMPurify)truncateText(text, maxLength, ellipsis?)— Truncate with ellipsisgeneratePreviewText(html, maxLength?)— Generate email preview textgenerateUnsubscribeLink(baseUrl, email, token?)— Create unsubscribe URLgenerateTrackingPixel(baseUrl, emailId, recipientId)— Create tracking pixel URLaddUtmParams(url, params)— Add UTM tracking parametersparseEmailList(str)— Parse comma/semicolon-separated emailsdeduplicateEmails(emails)— Deduplicate (case-insensitive)chunkEmails(emails, chunkSize?)— Split into batches (default 100)getMimeType(filename)— Get MIME type from extensionformatFileSize(bytes, decimals?)— Format file size in human-readable form
Types
import type {
// Config shapes
EmailConfig,
ResendConfig,
NodemailerConfig,
BaseEmailConfig,
EmailProvider,
EnvRecord,
// Sending
SendEmailOptions,
EmailAttachment,
EmailResult,
EmailValidationResult,
IEmailProvider,
ConfigValidationResult,
EmailTemplateData,
} from "@kumix/email";Environment Variables
| Variable | Provider | Required |
| ---------------------------- | ---------- | -------- |
| KUMIX_EMAIL_RESEND_API_KEY | Resend | Yes |
| KUMIX_EMAIL_FROM_NAME | All | Yes |
| KUMIX_EMAIL_FROM_EMAIL | All | Yes |
| KUMIX_EMAIL_REPLY_TO | All | No |
| KUMIX_EMAIL_SMTP_HOST | Nodemailer | Yes |
| KUMIX_EMAIL_SMTP_PORT | Nodemailer | Yes |
| KUMIX_EMAIL_SMTP_SECURE | Nodemailer | No |
KUMIX_EMAIL_SMTP_SECURE enables TLS when set to true, 1, or yes (case-insensitive); any
other value (or unset) leaves the connection unsecured. Defaults to off — set it explicitly for
port 465 (implicit TLS).
| KUMIX_EMAIL_SMTP_USER | Nodemailer | Yes |
| KUMIX_EMAIL_SMTP_PASS | Nodemailer | Yes |
Legacy env vars (RESEND_API_KEY, SMTP_HOST, etc.) are also supported for backward compatibility.
Links
License
MIT © Kumix Labs
