tsx-mailer
v0.1.0
Published
Type-safe email templating with TSX and SMTP support
Downloads
140
Maintainers
Readme
tsx-mailer
Type-safe email templating with TSX and SMTP support. No React required.
Table of Contents generated with DocToc
- Features
- Installation
- Quick Start
- Email Addresses
- API Reference
- Template Best Practices
- TypeScript Support
- License
Features
- Type-safe templates: Write email templates in TSX with full TypeScript support
- No React dependency: Uses a lightweight custom JSX runtime
- HTML + Plain text: Automatically generates both HTML and plain text versions
- SMTP support: Built on nodemailer for reliable email delivery
- Named addresses: Support for
"John Doe" <[email protected]>format - Template metadata: Define subject, from, replyTo inside templates
- Subject prefix/suffix: Automatically add app name to all subjects
Installation
npm install tsx-mailerQuick Start
1. Enable JSX in your tsconfig.json
Add these two lines to your compilerOptions (if not already present):
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "tsx-mailer"
}
}If your project also uses React, set jsxImportSource to "react" instead, and use the per-file pragma in your email templates (see below).
2. Create an email template
Add the pragma comment at the top of each template file. This tells TypeScript to use tsx-mailer's JSX runtime for that file.
You have two options for what your template returns:
Option A: Return just the JSX body
// templates/simple.tsx
/** @jsxImportSource tsx-mailer */
export function SimpleEmail({ message }: { message: string }) {
return (
<html>
<body>
<p>{message}</p>
</body>
</html>
);
}Option B: Use the email() helper to include metadata
The email() helper lets you define subject, from, and replyTo inside the template. All fields are optional except body - anything not set here can be provided (or overridden) when sending.
// templates/welcome.tsx
/** @jsxImportSource tsx-mailer */
import { email } from 'tsx-mailer';
export interface WelcomeEmailProps {
name: string;
activationLink: string;
}
// Set subject in template, use default "from" from mailer config
export function WelcomeEmail({ name, activationLink }: WelcomeEmailProps) {
return email({
subject: `Welcome, ${name}!`,
body: (
<html>
<body style={{ fontFamily: 'Arial, sans-serif' }}>
<h1>Welcome, {name}!</h1>
<p>Click below to activate your account:</p>
<a href={activationLink}>Activate Account</a>
</body>
</html>
),
});
}
// Set everything in the template
export function SupportEmail({ ticket }: { ticket: string }) {
return email({
subject: `Re: Ticket #${ticket}`,
from: { name: 'Support Team', email: '[email protected]' },
replyTo: '[email protected]',
body: <html><body>...</body></html>,
});
}3. Send the email
import { createMailer } from 'tsx-mailer';
import { WelcomeEmail } from './templates/welcome';
const mailer = createMailer({
smtp: {
host: 'smtp.example.com',
port: 587,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
},
// Named sender address
defaultFrom: { name: 'My App', email: '[email protected]' },
// Optional: add prefix/suffix to all subjects
subjectPrefix: '[MyApp] ',
});
await mailer.send(WelcomeEmail, {
// Named recipient
to: { name: 'John Carmichael', email: '[email protected]' },
// Subject is optional if template provides it
props: {
name: 'John',
activationLink: 'https://example.com/activate?token=abc',
},
});Email Addresses
All address fields (to, from, cc, bcc, replyTo) support both formats:
// Simple string
to: '[email protected]'
// Named address
to: { name: 'John Carmichael', email: '[email protected]' }
// Multiple recipients
to: [
{ name: 'Alice', email: '[email protected]' },
{ name: 'Bob', email: '[email protected]' },
]Named addresses are formatted as RFC 5322: John Carmichael <[email protected]>
API Reference
createMailer(config)
Creates a new mailer instance.
interface MailerConfig {
smtp: {
host: string;
port: number;
secure?: boolean; // true for 465, false for other ports
auth?: {
user: string;
pass: string;
};
};
defaultFrom?: EmailAddress; // Default sender
subjectPrefix?: string; // e.g., "[MyApp] "
subjectSuffix?: string; // e.g., " - MyApp"
}
type EmailAddress = string | { name: string; email: string };mailer.send(template, options)
Send an email using a TSX template.
interface EmailOptions<TProps> {
to: EmailAddress | EmailAddress[];
subject?: string; // Optional if template provides it
from?: EmailAddress; // Overrides template/default
cc?: EmailAddress | EmailAddress[];
bcc?: EmailAddress | EmailAddress[];
replyTo?: EmailAddress;
props?: TProps;
}Priority for metadata (subject, from, replyTo):
- Options passed to
send()(highest) - Template metadata (from
email()helper) - Mailer config defaults (lowest)
email(options) helper
Use inside templates to define metadata:
import { email } from 'tsx-mailer';
export function MyTemplate(props: Props) {
return email({
subject: 'My Subject',
from: { name: 'Sender', email: '[email protected]' },
replyTo: '[email protected]',
body: <html>...</html>,
});
}mailer.compile(template, props)
Compile a template to HTML and plain text without sending.
const { html, text } = mailer.compile(WelcomeEmail, { name: 'John', ... });mailer.sendRaw(options)
Send an email with pre-compiled HTML.
await mailer.sendRaw({
to: '[email protected]',
subject: 'Hello',
html: '<h1>Hello World</h1>',
text: 'Hello World', // Optional, auto-generated if omitted
});mailer.verify()
Verify SMTP connection.
const isConnected = await mailer.verify();mailer.close()
Close the SMTP connection.
Template Best Practices
Use tables for layout
Email clients have limited CSS support. Use tables for reliable layouts:
<table width="100%" cellPadding="0" cellSpacing="0">
<tr>
<td style={{ padding: '20px' }}>
Content here
</td>
</tr>
</table>Inline styles
Most email clients strip <style> tags. Use inline styles:
<p style={{ color: '#333', fontSize: '16px' }}>
Styled text
</p>Supported HTML elements
The JSX runtime supports all common HTML elements including:
- Document:
html,head,body,title,meta,style - Layout:
div,table,tr,td,th - Text:
p,h1-h6,span,a,strong,em - Lists:
ul,ol,li - Media:
img - Legacy:
center,font(for older email clients)
TypeScript Support
Templates are fully typed. Define your props interface:
interface OrderConfirmationProps {
orderNumber: string;
items: Array<{ name: string; price: number }>;
total: number;
}
function OrderConfirmation({ orderNumber, items, total }: OrderConfirmationProps) {
return (
<html>
<body>
<h1>Order #{orderNumber}</h1>
<ul>
{items.map(item => (
<li>{item.name}: ${item.price}</li>
))}
</ul>
<p><strong>Total: ${total}</strong></p>
</body>
</html>
);
}License
MIT
