Quickstart
This guide shows how to send your first email using the official
@mailzeno/client SDK.
Mailzeno is SMTP-first. Before sending emails, you must:
- Create an SMTP account in the dashboard
- Use the exact SMTP username as the
fromaddress - Generate an API key (
mz_api_xxxxxx)
Prerequisites
- Node.js 18 or later
- A Mailzeno API key (
mz_api_xxxxxx) - At least one configured SMTP account
- A sender email that matches your SMTP username
Install the SDK
Install the Mailzeno SDK:
npm install @mailzeno/clientOr:
# yarn
yarn add @mailzeno/client
# pnpm
pnpm add @mailzeno/clientInitialize the client
The constructor requires your API key as a string.
import { MailZeno } from "@mailzeno/client"
export const mz = new Mailzeno(process.env.MAILZENO_API_KEY!)Never commit your API key to source control. Store it in environment variables.
Example .env file:
MAILZENO_API_KEY=mz_api_your_key_hereSend your first email
You must provide:
smtpIdfrom(must match SMTP username)to- Either:
subject+html- OR a template (
templateIdortemplateKey)
Example using raw HTML:
import { MailZeno } from "@mailzeno/client"
const mz = new Mailzeno(process.env.MAILZENO_API_KEY!)
async function main() {
const result = await mz.emails.send({
smtpId: "your-smtp-id",
from: "hello@yourdomain.com",
to: "user@example.com",
subject: "Hello from Mailzeno",
html: "<h1>Hello World</h1>"
})
console.log("Message ID:", result.messageId)
console.log("Accepted:", result.accepted)
console.log("Rejected:", result.rejected)
}
main().catch(console.error)Passing dynamic variables in raw HTML
await mz.emails.send({
smtpId: "your-smtp-id",
from: "hello@yourdomain.com",
to: "user@example.com",
subject: "Hello {{name}}",
html: "<h1>Hello {{name}}</h1>",
variables: {
name: "Harsh"
}
}){{variable}} placeholders are interpolated at send time.
Important
- The
fromaddress must match the SMTP account username. - The
messageIdis returned from your SMTP provider. - Detailed SMTP errors are not exposed via the public API.
Using templates
Instead of passing subject and html, you can use a saved template:
await mz.emails.send({
smtpId: "your-smtp-id",
from: "hello@yourdomain.com",
to: "user@example.com",
templateKey: "welcome_email",
variables: {
name: "Harsh"
}
})Templates support simple variable interpolation:
{{name}} {{email}} {{age}} {{custom}}
No conditionals or logic are supported.
Idempotency
To prevent duplicate sends, provide an idempotency key:
await mz.emails.send(
{
smtpId: "your-smtp-id",
from: "hello@yourdomain.com",
to: "user@example.com",
subject: "Order Confirmation",
html: "<p>Thank you</p>"
},
{
idempotencyKey: "order-123"
}
)Handling errors
The SDK throws MailzenoError on failure.
import { MailZeno, MailZenoError } from "@mailzeno/client"
try {
await mz.emails.send({
smtpId: "your-smtp-id",
from: "hello@yourdomain.com",
to: "invalid-email",
subject: "Test",
html: "<p>Test</p>"
})
} catch (err) {
if (err instanceof MailZenoError) {
console.error("Message:", err.message)
console.error("Status:", err.status)
console.error("Code:", err.code)
console.error("Request ID:", err.requestId)
}
}REST API (without SDK)
You can call the API directly:
curl -X POST https://api.mailzeno.dev/v1/emails \
-H "Authorization: Bearer mz_api_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"smtpId": "your-smtp-id",
"from": "hello@yourdomain.com",
"to": "user@example.com",
"subject": "Hello",
"html": "<h1>Hello World</h1>"
}'Successful response
{
"messageId": "<smtp-provider-message-id>",
"accepted": ["user@example.com"],
"rejected": [],
"response": "250 OK"
}Rate limits
Requests are limited per minute and per day based on your plan.
When rate limits are exceeded, the API returns:
- HTTP 429
retry-afterheaderx-ratelimit-*headers