The Cliam package is back on track with a serious update about the quality, the dX, the flexibility and the security.
For those ones that are not aware of what Cliam can achieve for them (and believe me, he can), just a short trailer:
One input. One output. Any provider. Now more than ever.
And some number that matters
10 providers. 1 interface. 0 new runtime dependencies in v3.
Pick your provider. Switch tomorrow. Your code doesn't care.
import { Cliam } from 'cliam';
Cliam.mail('user.welcome', payload); // always the same
The last marketing post is here. Let's quickly deep dive into the v3 release logs.
New providers
Two heavy hitters join the roster.
Resend - the developer-first email API built for the modern stack. Clean, predictable, fast.
Amazon SES - enterprise-grade deliverability at AWS scale. And because we know you hate unnecessary dependencies: SES authentication (AWS Signature V4) is implemented natively using Node.js built-in crypto. No @aws-sdk. No bloat. Zero new runtime dependencies.
Stack upgraded
Cliam now runs on TypeScript 6. ESM all the way, moduleResolution: Bundler, built with tsup. No CommonJS cruft, no compatibility shims.
The toolchain got a pass too: Biome 2.x handles linting and formatting, Bun runs the test suite, and tsc --noEmit is wired in as a dedicated typecheck script so your IDE and your CI agree on what's broken.
DX improved
The API surface got cleaner.
-
inlineImagesis gone. It was never doing what you thought it was. Usecontentattachments like everyone else. -
sendinblueis also gone. It was Brevo. It's still Brevo. Update your config:provider: 'brevo'. - Template IDs now live exclusively on the transporter configuration, not scattered across options. One place, one source of truth.
- Theme colors are now injected as named Handlebars variables (
primaryColor,secondaryColor, …) instead of post-render hex string replacement. Predictable. Debuggable. Actually makes sense.
Security improved
56 vulnerabilities. Including criticals. Now 0.
The previous architecture routed all providers - including HTTP API ones - through nodemailer, dragging along its entire dependency tree. That tree was old, wide, and full of known CVEs.
v3 cuts it at the root. HTTP API providers (Brevo, Mailersend, Mailgun, Mailjet, Mandrill, Postmark, Resend, Sendgrid, SES, Sparkpost) now talk to their APIs through our own thin HttpClient wrapper built on ky. Nodemailer stays for SMTP - but only for SMTP, pinned to its latest maintained release (v8), with a clean bill of health.
Less surface. Less trust. Less exposure.
The rest of the security picture:
- AWS SES requests are signed per-request using a proper Signature V4 implementation: timestamp, payload hash, HMAC chain, the works. No pre-shared static tokens flying around.
- All provider API key formats are validated at configuration time with provider-specific Joi regexes. You know immediately if something is wrong, not when your first email fails at 2am.
- Sandbox mode remains a hard gate: nothing leaves the process when it's on.
Type system cleaned up
The internal type system went through a full consistency pass.
- Enums replaced with
as constobjects + derived union types - one source of truth, no parallel declarations. -
IMail.metais now narrowed:fromandreplyToare required by the type, not just by convention. - Body interfaces defined per provider, ready to be wired into
build()return types for full end-to-end type coverage.
Bundle size improved
Before v3, import { Cliam } from 'cliam' silently loaded every provider, all ten of them, their HTTP clients, their payload mappers, all of it. You used Brevo. You got Sparkpost too. And Mailjet. And the rest.
Now providers are tree-shakeable. Import everything at once, or load only what you actually ship:
import { Cliam } from 'cliam/core';
import 'cliam/providers/brevo'; // that's it
Your bundler sees exactly what you use. Nothing more makes it into your artifact.
Types improved
The public API is now fully typed and fully exported. Consumers get first-class access to every interface they need:
import type { IPayload, IClientConfiguration, IAddressable, IAttachment } from 'cliam';
No more reaching into internals. No more any patches at the call site. Your IDE knows what meta.from is. Your compiler catches the typo before you do.
Configuration improved
Two modes, your choice, no process assassination.
// Programmatic — config lives in your code
Cliam.configure({ sandbox: true, transporters: [...] });
// File-based — config lives in .cliamrc.js
Cliam.configureFromFile('/path/to/config.js');
The critical part: bad configuration used to call process.exit(). Your app just died, silently, in the middle of startup, with a log line you may or may not have seen. That's gone. Configuration errors now throw, which means you catch them, you log them your way, you decide what happens next. Your process is yours again.
Thanks for the reading, enjoy your dev, and see you there:
steve-lebleu
/
cliam
Agnostic transactional email sending in Node.js environment
Transactional emails with a kick
Agnostic transactional email sending in Node.js environment
> Why ?
To improve and facilitate the implementation, the flexibility and the maintenance of transactional emailing tasks.
> Features
- Agnostic transactional email sending using web API or SMTP server. One input, one output.
- Multiple simultaneous transporters.
- Configuration based, not implementation based: easy switch between different modes.
- Normalized transactions events.
- Securized payloads.
- Customisable default templates.
> Table of contents
- Requirements
- Getting started
- Selective imports
- Beneficiary use cases
- Supported web API providers
- Upgrading to v3
- Licence
- Documentation
> Requirements
- Node.js >= 18.19.0
- NPM >= 10.2.3
> Getting started
Install
> npm i cliam --save
Configure
Cliam must be configured once at application startup, before any call to mail(). Two approaches are available.
Option A - Pass a configuration object directly:
import { Cliam } from 'cliam';
import type { IClientConfiguration } from 'cliam';…

Top comments (0)