DEV Community

Dominic Rockson
Dominic Rockson

Posted on

Your first transactional email shouldn't need a new account

Every side project eventually needs to send an email: a welcome message, a password reset, a receipt. And that's usually the moment the project quietly grows a second project — a mail-service account, API keys, a template system, a test setup, and the question of what happens when the provider is down.

Yatta JS keeps email inside the backend. It's a backend-as-a-server for Bun: one process with your database, auth, mail, storage, realtime, jobs and observability baked in. The mail engine is already there — createMailer from yatta.js/mail — and it starts in terminal mode, so in development your emails print to the console instead of going anywhere. Nothing to sign up for to write your first welcome email.

When you're ready for real delivery, you change one line: mode becomes smtp in production and you pick a provider preset — Resend, Postmark, SendGrid, SES, Gmail, Mailgun, Brevo — which fills in host, port and TLS for you. The only things you add are credentials and a From address on a domain you've verified. That's the whole integration.

import { createMailer } from "yatta.js/mail";

export const mailer = createMailer({
  defaultFrom: "Yatta App <hello@yourdomain.com>",
  mode: process.env.NODE_ENV === "production" ? "smtp" : "terminal",
  provider: "resend",
});
Enter fullscreen mode Exit fullscreen mode

Templates stay typed: you register a template once with its data shape, and sending it is one call.

await mailer.send({
  to: "user@example.com",
  subject: "Welcome to Yatta!",
  template: "welcome",
  data: { name: "Alice", verifyUrl: "https://app.dev/verify?token=abc" },
});
Enter fullscreen mode Exit fullscreen mode

Markdown in, HTML plus an automatic plain-text version out. There's a preview call that renders the email without sending anything, and a memory mode for tests where you can assert on what was "sent" without touching the network.

The part I like most is the boring safety that comes free: header values are stripped of CR and LF so a subject can't inject extra headers, template values are escaped by default, there's RFC 8058 one-click unsubscribe, idempotency keys, retries for transient failures, and rate limiting you configure in the same object. And the docs' own advice is to send from a job, so a mail failure can't fail a user request — the same job queue that's already in the process.

None of this removes the real work of email — you still have to verify your domain and publish SPF, DKIM and DMARC, or providers will drop your mail. What a bundled backend removes is the week spent assembling a mail stack before your app has sent its first real email.

If your side project has been sitting without a welcome email because the mail setup looked like a whole project, that's the test. Start from a backend where email is already in it, and see how much sooner the first real email goes out.

Yatta JS is open source under MIT, on npm as yatta.js (1.0.5). Docs: https://yatta.js.org — Code: https://github.com/psrockstar098/yatta.js

What's the email your project keeps postponing — welcome, password reset, or receipts?

Top comments (0)