# MailTs > Native SMTP/IMAP TypeScript SDK — zero runtime dependencies. Queue, DLQ, DKIM, iCal, telemetry — out of the box. MailTs (`@mailts/core`) is a TypeScript-first email SDK built on Node.js built-ins (`node:net`, `node:tls`, `node:crypto`, `node:stream`, `node:http`, `node:https`). It has zero runtime npm dependencies and ships as ~25 kB. ## Packages - `@mailts/core` v0.3.0 — The mail SDK: SMTP, IMAP, queue, templates, middleware, transports - `@mailts/cli` v0.1.6 — Terminal CLI for sending, reading, queue management - `@mailts/trap` v1.0.3 — Local SMTP trap server with HTTP preview UI - `@mailts/testing` v0.1.0 — Vitest helpers (`useTrapServer`) for integration tests ## Installation ``` npm install @mailts/core ``` Requires Node.js 18+. ## Core API ### MailTs constructor ```ts import { MailTs } from '@mailts/core'; const mail = new MailTs({ smtp: { host: 'smtp.gmail.com', port: 587, auth: { type: 'plain', user: 'you@gmail.com', pass: process.env.SMTP_PASS }, pool: { maxConnections: 5, maxMessages: 100, idleTimeout: 30_000 }, dkim: { domainName: 'example.com', keySelector: 'mail', privateKey: process.env.DKIM_PRIVATE_KEY, }, }, imap: { host: 'imap.gmail.com', port: 993, secure: true, auth: { type: 'plain', user: 'you@gmail.com', pass: process.env.IMAP_PASS }, }, queue: { concurrency: 5, maxRetries: 3, retryBackoff: 'exponential', jitter: true, deadLetter: { enabled: true }, }, logger: { level: 'debug', format: 'raw', protocol: true }, devMode: false, }); ``` ### Sending email ```ts const result = await mail.send({ from: { email: 'you@gmail.com', name: 'Your App' }, to: 'recipient@example.com', cc: 'cc@example.com', bcc: ['bcc1@example.com', 'bcc2@example.com'], replyTo: 'support@example.com', subject: 'Hello from mailts!', text: 'Plain-text fallback.', html: '

Hello!

', attachments: [ { filename: 'report.pdf', path: './report.pdf' }, { filename: 'logo.png', content: buffer, contentType: 'image/png', cid: 'logo' }, ], priority: 'high', headers: { 'X-Custom': 'value' }, ical: { summary: 'Team Sync', start: new Date('2026-06-01T14:00:00'), end: new Date('2026-06-01T15:00:00'), timezone: 'America/New_York', organizer: { name: 'Alice', email: 'alice@example.com' }, attendees: [{ email: 'bob@example.com', name: 'Bob', rsvp: true }], method: 'REQUEST', // 'REQUEST' | 'CANCEL' | 'REPLY' | 'PUBLISH' }, }); if (result.ok) { console.log('Sent:', result.messageId); } else { console.error('Failed:', result.error.message, '— retryable:', result.error.retryable); } ``` ### Shorthand helpers ```ts await mail.notify({ from: '...', to: '...', subject: 'Event', text: '...' }); // Prepends [NOTIFICATION] to subject await mail.alert({ from: '...', to: '...', subject: 'CPU high', text: '...' }); // Prepends [ALERT] and forces high priority await mail.ping({ from: '...', to: '...', subject: 'Ping', text: '...' }); // Minimal heartbeat send ``` ### Templates ```ts mail.define('welcome', { from: { email: 'welcome@example.com', name: 'Acme' }, subject: 'Welcome, {{user.name}}!', template: 'Hi {{user.name}}, your account is ready.', }); await mail.trigger('welcome', { to: 'newuser@example.com', data: { user: { name: 'Alice' } }, }); // One-shot inline template: await mail.sendTemplate({ from: 'billing@example.com', to: 'user@example.com', subject: 'Invoice #{{invoiceId}}', template: 'Hi {{name}}, your invoice for ${{amount}} is due.', data: { name: 'Alice', invoiceId: '1042', amount: '49.00' }, }); // Custom template engine (e.g. Handlebars): mail.setTemplateEngine({ render: (compiled, data) => Handlebars.template(compiled as HandlebarsTemplateDelegate)(data), }); ``` ### Middleware ```ts mail.use(async (msg, next) => { msg.headers = { ...msg.headers, 'X-Mailer': 'myapp/1.0' }; await next(); }); ``` ### Hot-swap credentials ```ts mail.configure({ smtp: { host: 'smtp.sendgrid.net', port: 587, auth: { type: 'plain', user: 'apikey', pass: process.env.SENDGRID_API_KEY }, }, }); ``` ### Health checks ```ts const h = await mail.health(); // { smtp: { ok: true, latencyMs: 42 }, imap: { ok: true, latencyMs: 18 } } await mail.testConnection(); // boolean — probe SMTP without using the pool ``` ### Lifecycle ```ts await mail.shutdown(); // drain queue, close connections await mail.dispatch(opts, signal); // bypass queue and middleware, thread AbortSignal ``` ## SMTP Auth types - `'plain'` — PLAIN auth with `user` + `pass` - `'login'` — LOGIN auth with `user` + `pass` - `'xoauth2'` — XOAUTH2 with `user` + `token` (short-lived OAuth2 access token) ## IMAP API ```ts const session = mail.imap; await session.connect(); // Select and inspect a mailbox const status = await session.open('INBOX'); // { exists: 120, unseen: 5, uidNext: 121 } // Check status without selecting (safe for background polling) const s = await session.getStatus('INBOX', ['MESSAGES', 'UNSEEN', 'UIDNEXT']); // Fetch messages const messages = await session.fetch({ seen: false, limit: 10, bodies: true }); // Each message: { uid, envelope, flags, modseq, headers, body } // CONDSTORE incremental sync const changed = await session.fetchChanged(lastKnownModSeq); // Flag operations await session.markSeen([101, 102]); await session.markUnseen([103]); await session.markFlagged([104]); await session.markUnflagged([104]); await session.setFlags([105], ['\\Answered'], true); // set arbitrary IMAP flags // Move, copy, delete await session.move([110], 'Archive'); await session.copy([111], 'Backup'); await session.delete([112]); // Append raw message await session.append('Sent', rawBuffer, ['\\Seen']); // Mailbox management await session.listMailboxes(); await session.createMailbox('Projects/Alpha'); await session.renameMailbox('Projects/Alpha', 'Projects/Done'); await session.deleteMailbox('Projects/Done'); // IDLE push notifications await session.idle((msg) => console.log('New message:', msg.seq)); // Stop idle: session.stopIdle(); await session.close(); ``` ## Queue API ```ts // Enqueue returns a QueueJob — use job.id to track/cancel const job = mail.queue.enqueue( { to: 'user@example.com', subject: 'Hi', text: '...' }, { priority: 'critical' }, // 'critical' | 'high' | 'normal' | 'low' ); mail.queue.cancel(job.id); // cancel pending job mail.queue.interrupt(job.id); // re-queue running job (attempt count unchanged) mail.queue.abort(job.id); // abort running job (counts as a failed attempt) mail.queue.cancelAll(); mail.queue.interruptAll(); mail.queue.abortAll(); mail.queue.pause(); // stop picking up new jobs mail.queue.play(); // resume await mail.queue.drain(); // wait until all jobs settle await mail.queue.shutdown(5_000); // drain with timeout, then force-close const stats = mail.queue.stats(); // { pending: 3, running: 1, completed: 42, failed: 0, dead: 0 } // Events mail.queue.on('success', (job, result) => {}); mail.queue.on('retry', (job, error) => {}); mail.queue.on('dead', (job, error) => {}); mail.queue.on('cancelled', (job) => {}); mail.queue.on('interrupted', (job) => {}); mail.queue.on('enqueued', (job) => {}); mail.queue.on('started', (job) => {}); mail.queue.on('drained', () => {}); ``` ## Logging / telemetry ```ts // Stream SMTP protocol trace to stdout (raw format, credentials auto-redacted) mail.logger.stream({ format: 'raw' }).pipe(process.stdout); // JSON structured logs to a file mail.logger.stream({ format: 'json' }).pipe(createWriteStream('/tmp/mail.log')); // Typed event listener mail.logger.onEvent((e: LogEvent) => { // e.level, e.phase, e.direction, e.message, e.timestamp, e.meta if (e.level === 'error') reportToSentry(e); }); ``` Log levels: `'debug'` | `'info'` | `'warn'` | `'error'` Log formats: `'pretty'` (ANSI-coloured) | `'json'` | `'raw'` (plain text) Set `protocol: true` in logger config to stream every SMTP/IMAP command and reply. ## HTTP Transports (third-party APIs) ```ts import { ResendTransport, SendGridTransport, MailgunTransport, PostmarkTransport, SesTransport } from '@mailts/core/transports'; const mail = new MailTs({ transport: new ResendTransport({ apiKey: process.env.RESEND_API_KEY }), }); // Same mail.send() interface — swap transport without changing call sites ``` ## Local development — @mailts/trap ```ts import { TrapServer } from '@mailts/trap'; const trap = new TrapServer({ smtpPort: 1025, httpPort: 1080 }); await trap.start(); // Inspect captured mail at http://localhost:1080 ``` ## Testing — @mailts/testing ```ts import { useTrapServer } from '@mailts/testing'; const { getTrap } = useTrapServer(); // Vitest lifecycle hook test('sends welcome email', async () => { const mail = new MailTs({ smtp: { host: '127.0.0.1', port: getTrap().smtpPort, pool: false } }); await mail.send({ from: 'app@example.com', to: 'user@example.com', subject: 'Welcome', text: 'Hi' }); const [msg] = getTrap().store.getAll(); expect(msg.subject).toBe('Welcome'); }); ``` ## MailWorker (cross-process queues) ```ts import { MailWorker, type QueueDriver, type DriverMessage } from '@mailts/core'; class RedisDriver implements QueueDriver { async dequeue(): Promise { /* ... */ } async ack(id: string): Promise { /* ... */ } async nack(id: string, reason?: Error): Promise { /* ... */ } } const worker = new MailWorker(new RedisDriver(), { smtp: { /* ... */ } }); await worker.start(); ``` ## DKIM signing DKIM is configured under `smtp.dkim` (not at top level): ```ts const mail = new MailTs({ smtp: { host: 'smtp.gmail.com', port: 587, auth: { type: 'plain', user: 'you@gmail.com', pass: process.env.SMTP_PASS }, dkim: { domainName: 'example.com', keySelector: 'mail', // matches mail._domainkey.example.com TXT record privateKey: process.env.DKIM_PRIVATE_KEY, }, }, }); ``` ## iCal / calendar invites ```ts await mail.send({ from: 'organizer@example.com', to: 'attendee@example.com', subject: 'Meeting invite', text: 'You have a meeting.', ical: { summary: 'Team Sync', start: new Date('2026-06-01T14:00:00'), end: new Date('2026-06-01T15:00:00'), timezone: 'America/New_York', organizer: { name: 'Alice', email: 'alice@example.com' }, attendees: [{ email: 'bob@example.com', name: 'Bob', rsvp: true }], location: 'Conference Room A', method: 'REQUEST', // 'REQUEST' | 'CANCEL' | 'REPLY' | 'PUBLISH' uid: 'meeting-2026-06-01@example.com', description: 'Quarterly planning session', }, }); ``` ## Links - GitHub: https://github.com/anishhs-gh/mailts - npm (@mailts/core): https://www.npmjs.com/package/@mailts/core - Documentation: https://mailts.anishhs.com - API reference: https://mailts.anishhs.com/#/api - Examples: https://mailts.anishhs.com/#/examples - Changelog: https://mailts.anishhs.com/#/changelog