Official TypeScript / Node.js SDK for SMASHSEND
Easily integrate email marketing, transactional emails, automations, and contact management into your app.
SMASHSEND is a bold, modern email platform built for business owners, creators, and startups.
- ⚡️ Drag-and-drop email builder
- 🪄 AI-powered personalization
- 🤖 Automations & event triggers
- 🚀 Scalable, high-deliverability transactional email API
- 🗂️ Lightweight CRM & contact management
- 📈 Deep analytics & link tracking
npm install @smashsend/node # or yarn add @smashsend/node / pnpm add @smashsend/node- Log in to your SMASHSEND Dashboard
- Navigate to Settings → API Keys
- Click Create API Key
- Give your key a descriptive name (e.g., "Production Server", "Development")
- Copy the key immediately — it won't be shown again!
import { SmashSend } from '@smashsend/node';
const smashsend = new SmashSend(process.env.SMASHSEND_API_KEY!);
⚠️ Security tip: Never commit API keys to version control. Use environment variables or a secrets manager.
import { SmashSend, SmashsendContactStatus, SmashsendCountryCode } from '@smashsend/node';
const smashsend = new SmashSend(process.env.SMASHSEND_API_KEY!);
const contact = await smashsend.contacts.create({
email: 'newcontact@example.com', // required
firstName: 'John',
lastName: 'Doe',
phone: '+1234567890',
status: SmashsendContactStatus.SUBSCRIBED, // defaults to SUBSCRIBED
countryCode: SmashsendCountryCode.US,
customProperties: {}, // define in dashboard first
});
console.log(contact.id); // contact UUID
console.log(contact.properties.email); // newcontact@example.comCreate multiple contacts efficiently in a single API call (takes less than 300ms to add 500 contacts. crazy fast!):
const result = await smashsend.contacts.createBatch(
[
{ email: 'john@example.com', firstName: 'John', lastName: 'Doe' },
{ email: 'jane@example.com', firstName: 'Jane', lastName: 'Smith' },
{ email: 'bob@example.com', firstName: 'Bob', lastName: 'Johnson' },
],
{
allowPartialSuccess: true, // Create valid contacts even if some fail
includeFailedContacts: true, // Include failed contacts in response
}
);
console.log(`Created: ${result.summary.created}, Failed: ${result.summary.failed}`);
// Handle failures and retry if needed
if (result.failedContacts?.length > 0) {
const retryableContacts = result.failedContacts
.filter((fc) => fc.errors.some((e) => e.retryable))
.map((fc) => fc.contact);
if (retryableContacts.length > 0) {
await smashsend.contacts.createBatch(retryableContacts);
}
}The simplest way to send a transactional email is with raw HTML:
const response = await smashsend.emails.send({
from: 'notifications@yourdomain.com',
to: 'recipient@example.com',
subject: 'Your order has shipped!',
html: `
<h1>Great news!</h1>
<p>Your order #12345 has shipped and is on its way.</p>
<a href="https://track.example.com/12345">Track your package</a>
`,
text: 'Your order #12345 has shipped. Track at: https://track.example.com/12345',
groupBy: 'order-shipped', // (Optional) Group analytics by email type
settings: {
trackClicks: true,
trackOpens: true,
},
});📊 Analytics tip: Use the
groupByparameter to group similar emails together in your analytics dashboard. This helps you track performance across all "order shipped" emails, regardless of individual recipients.
For better maintainability and design flexibility, use templates — the recommended approach for transactional emails.
Why use templates?
- 🎨 Design beautiful emails in the SMASHSEND visual editor
- 🔄 Update email content without deploying code
- 📊 Built-in analytics and tracking
- 🧪 A/B test different versions
- 👥 Non-technical team members can modify content
- 🌐 Automatic responsive design
Creating a template:
- Go to Emails => Transactional in your dashboard
- Click Create Transactional
- Design your email using the drag-and-drop editor
- Add variables (both template variables and contact variables)
- Save with a memorable template ID (e.g.,
welcome-email,order-confirmation)
Sending with a template:
const response = await smashsend.emails.sendWithTemplate({
to: 'user@example.com',
template: 'welcome-email', // Template ID from dashboard
variables: {
firstName: 'Sarah',
companyName: 'Acme Corp',
signupDate: new Date().toLocaleDateString(),
// Any variables used in your template
},
settings: {
trackClicks: true,
trackOpens: true,
},
});
console.log(response.messageId); // Unique ID for tracking
console.log(response.status); // SCHEDULED, SENT, etc.You can specify custom reply-to addresses for both raw and templated emails. This allows recipients to reply to different addresses than the sender.
Single reply-to address:
await smashsend.emails.send({
from: 'noreply@yourdomain.com',
to: 'customer@example.com',
subject: 'Support Request Received',
html: '<p>We received your support request and will respond soon.</p>',
replyTo: 'support@yourdomain.com', // Single address
});Multiple reply-to addresses (max 5):
await smashsend.emails.send({
from: 'noreply@yourdomain.com',
to: 'customer@example.com',
subject: 'Welcome to our platform',
html: '<p>Welcome! Contact us if you need help.</p>',
replyTo: ['support@yourdomain.com', 'sales@yourdomain.com'], // Multiple addresses
});With templates:
await smashsend.emails.sendWithTemplate({
template: 'welcome-email',
to: 'user@example.com',
variables: { firstName: 'John' },
replyTo: ['support@yourdomain.com', 'welcome@yourdomain.com'], // Overrides template default
});💡 Note: Dynamic reply-to addresses override any reply-to settings configured in the template. If no dynamic reply-to is provided, the template's reply-to setting is used. Duplicate addresses are automatically removed.
For developers using React, you can write emails as React components:
First, install the React email renderer:
npm install @react-email/renderCreate your email component:
// emails/OrderConfirmation.tsx
import * as React from 'react';
interface OrderConfirmationProps {
customerName: string;
orderNumber: string;
items: Array<{ name: string; price: number }>;
}
export default function OrderConfirmation({
customerName,
orderNumber,
items,
}: OrderConfirmationProps) {
const total = items.reduce((sum, item) => sum + item.price, 0);
return (
<div style={{ fontFamily: 'Arial, sans-serif', maxWidth: '600px', margin: '0 auto' }}>
<h1 style={{ color: '#333' }}>Thanks for your order, {customerName}!</h1>
<p>Order #{orderNumber} has been confirmed.</p>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<tbody>
{items.map((item, i) => (
<tr key={i}>
<td style={{ padding: '10px 0' }}>{item.name}</td>
<td style={{ textAlign: 'right' }}>${item.price.toFixed(2)}</td>
</tr>
))}
<tr style={{ borderTop: '2px solid #333', fontWeight: 'bold' }}>
<td style={{ padding: '10px 0' }}>Total</td>
<td style={{ textAlign: 'right' }}>${total.toFixed(2)}</td>
</tr>
</tbody>
</table>
</div>
);
}Send the React email:
import OrderConfirmation from './emails/OrderConfirmation';
const response = await smashsend.emails.send({
from: 'orders@yourdomain.com',
to: 'customer@example.com',
subject: 'Order Confirmation',
react: OrderConfirmation({
customerName: 'John',
orderNumber: '12345',
items: [
{ name: 'T-Shirt', price: 29.99 },
{ name: 'Shipping', price: 5.0 },
],
}),
groupBy: 'order-confirmation', // (Optional) Group analytics by email type
});Send events to trigger automations, track user behavior, or sync data with your SMASHSEND workspace.
Send a single event:
const response = await smashsend.events.send({
event: 'user.signup',
properties: {
source: 'website',
campaign: 'summer-sale',
},
identify: {
email: 'user@example.com',
traits: {
firstName: 'John',
lastName: 'Doe',
},
},
});
console.log(`Event sent with ID: ${response.messageId}`);Send multiple events in a batch:
const events = [
{
event: 'page.view',
identify: { email: 'user1@example.com' },
properties: { page: '/home' },
},
{
event: 'button.click',
identify: { email: 'user2@example.com' },
properties: { button: 'signup' },
},
];
const result = await smashsend.events.sendBatch(events);
console.log(`Accepted: ${result.accepted}, Failed: ${result.failed}`);
// Handle failed events
if (result.errors?.length > 0) {
result.errors.forEach((error) => {
console.log(`Event ${error.index} failed:`, error.errors);
});
}Event structure:
event: Event name (e.g., 'user.signup', 'purchase.completed')identify.email: User email (required)identify.traits: User attributes to sync with contact record (optional)properties: Event-specific data (optional)timestamp: Event timestamp (optional, defaults to current time)messageId: Custom ID for deduplication (optional, auto-generated if not provided)
Custom Headers
// Add multiple headers
smashsend.setHeaders({
'X-Custom-Header': 'value',
'X-Tracking-ID': 'campaign-123',
});
// Or add an individual header
smashsend.setHeader('X-Source', 'website');Debug Mode
smashsend.setDebugMode(true); // logs all requests & responsesRetry Configuration
const smashsend = new SmashSend(process.env.SMASHSEND_API_KEY!, {
maxRetries: 5, // default 3
timeout: 60000,
});
⚠️ SECURITY NOTE: This SDK contains your secret API key and must NEVER be used in client-side code. Only use it in:
- Server Components
- API Routes
- Server Actions
- Middleware
Never import this SDK in client components or expose your API key to the browser!
Helper (Server-Side Only)
// lib/smashsend.ts
// ⚠️ This file should only be imported in server-side code!
import { SmashSend } from '@smashsend/node';
let client: SmashSend;
export function getSmashSendClient(apiKey?: string) {
if (!client) {
client = new SmashSend(apiKey ?? process.env.SMASHSEND_API_KEY!);
}
return client;
}Server Component Example
// app/contacts/page.tsx
// ✅ This is a Server Component - API key is safe here
import { getSmashSendClient } from '@/lib/smashsend';
export default async function ContactsPage() {
const smashsend = getSmashSendClient();
const { contacts } = await smashsend.contacts.list();
return (
<ul>
{contacts.map((c) => (
<li key={c.id}>
{c.properties.firstName} ({c.properties.email})
</li>
))}
</ul>
);
}API Route Example
// app/api/contact/route.ts
// ✅ API routes run on the server - API key is safe here
import { getSmashSendClient, SmashsendContactStatus, SmashsendCountryCode } from '@/lib/smashsend';
import { NextResponse } from 'next/server';
export async function POST(req: Request) {
const data = await req.json();
try {
const smashsend = getSmashSendClient();
const contact = await smashsend.contacts.create({
email: data.email,
status: SmashsendContactStatus.SUBSCRIBED,
countryCode: SmashsendCountryCode.US,
customProperties: data.customFields,
});
return NextResponse.json({ success: true, contact });
} catch (err: any) {
return NextResponse.json({ success: false, error: err.message }, { status: 400 });
}
}❌ What NOT to do
// components/BadExample.tsx
'use client' // ❌ Client component
import { SmashSend } from '@smashsend/node';
export function BadExample() {
// ❌ NEVER DO THIS! This exposes your API key to the browser!
const smashsend = new SmashSend('your-api-key');
// ❌ This will leak your API key in the browser's network tab
const handleClick = async () => {
await smashsend.emails.send({ ... });
};
}✅ Correct approach for client-side interactions
// components/GoodExample.tsx
'use client';
export function GoodExample() {
const handleSubmit = async (email: string) => {
// ✅ Call your API route instead
await fetch('/api/contact', {
method: 'POST',
body: JSON.stringify({ email }),
});
};
}import { SmashSend, SmashSendError } from '@smashsend/node';
try {
await smashsend.emails.send({
/* … */
});
} catch (err) {
if (err instanceof SmashSendError) {
console.error(err.statusCode, err.requestId, err.message);
} else {
console.error('Unexpected error', err);
}
}SMASHSEND supports custom contact properties with the following types:
import { SmashsendPropertyType } from '@smashsend/node';
// Available property types:
SmashsendPropertyType.SELECT; // Single choice dropdown
SmashsendPropertyType.MULTI_SELECT; // Multiple choice selection
SmashsendPropertyType.STRING; // Text (max 255 characters)
SmashsendPropertyType.NUMBER; // Decimal numbers
SmashsendPropertyType.DATE; // Date values
SmashsendPropertyType.BOOLEAN; // True/False valuesCreating a custom property:
const property = await smashsend.contacts.createProperty({
displayName: 'Industry',
type: SmashsendPropertyType.SELECT,
description: 'The industry sector',
typeConfig: {
multiple: false,
options: ['Technology', 'Healthcare', 'Finance', 'Other'],
},
});Important: There are no separate EMAIL, URL, PHONE, TEXT, or INTEGER types. Use:
STRINGfor email addresses, URLs, phone numbers, and any textNUMBERfor both integers and decimals
For multi-select properties, you can use a simple array to replace all existing values:
// Simple array replaces all values
await smashsend.contacts.update(contactId, {
customProperties: {
tags: ['customer', 'enterprise', 'priority'], // Replaces all existing tags
interests: ['email-marketing', 'automation'], // Replaces all interests
},
});
// Empty array removes all values
await smashsend.contacts.update(contactId, {
customProperties: {
tags: [], // Removes all tags
},
});Advanced: Granular Control
For precise control without fetching current values, use the add/remove structure:
await smashsend.contacts.update(contactId, {
customProperties: {
tags: {
add: ['vip', 'enterprise'], // Add these tags
remove: ['trial', 'free'], // Remove these tags
},
},
});This is useful for webhooks or event-driven updates where you only know what changed.
- Built in TypeScript
- Complete type definitions for all resources & enums
- Works with
strictNullChecks,moduleResolution=node, etc.
GitHub Actions publishes to npm automatically.
| Branch | Release type |
|---|---|
beta |
Prereleases x.y.z-beta.n |
main |
Stable releases x.y.z |
Version bumps & Git tags (v1.2.3 / v1.2.3-beta.4) are handled for you.
Required secret
NPM_TOKEN → Settings ▸ Secrets ▸ Actions
Full API reference → https://smashsend.com/docs/api
We ❤️ PRs!
- Fork →
git checkout -b feat/awesome - Add tests & docs
- PR against
betaormain