npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@jagu.cz/email-layer

v3.0.0

Published

A small Nuxt layer for sending e-mail with one type-safe entry point — `sendEmail({ … })` — across pluggable **providers** (Zimbra, SMTP, or your own), reusable **contacts**, and reusable **templates**.

Downloads

352

Readme

Nuxt Email Layer

A small Nuxt layer for sending e-mail with one type-safe entry point — sendEmail({ … }) — across pluggable providers (Zimbra, SMTP, or your own), reusable contacts, and reusable templates.

pnpm add @jagu.cz/email-layer

Add it to your nuxt.config.ts:

export default defineNuxtConfig({
  extends: ['@jagu.cz/email-layer'],
})

Auto-imports & where things live

The public API — sendEmail, defineEmailContact, defineEmailTemplate, defineEmailProvider, registerEmailProvider — is auto-imported in the Nitro server context. Don't import it; just call it. It is server-only (Nitro routes, plugins, server utils) — it is not available in Vue components or anywhere on the client.

Where each piece belongs in your project:

| File | Holds | |------|-------| | server/utils/emails.ts | Your CONTACTS / TEMPLATES definitions (auto-imported) | | server/api/*.ts | Route handlers that call sendEmail(...) | | server/plugins/*.ts | registerEmailProvider(...) for custom providers | | nuxt.config.ts + env | Provider selection & credentials |

Quick start

export default defineEventHandler(async (event) => {
  const body = await readBody<Form>(event)
  const html = await render(EmailTemplate, body)

  return await sendEmail({
    to: '[email protected]',
    subject: 'Nový zájemce o služby',
    html,
  })
})

sendEmail returns { ok: true, provider, dryRun? }. In development (or with NUXT_EMAIL_DRY_RUN=true) nothing is actually sent — the message is logged and dryRun: true is returned, so you don't need the old if (NODE_ENV === 'development') return guard in your handlers.

Configuration

Select a default provider and add credentials via environment variables:

| Variable | Description | |----------|-------------| | NUXT_EMAIL_PROVIDER | Default provider: zimbra, smtp, or a custom name | | NUXT_EMAIL_DEFAULT_FROM_NAME | Sender display name used when a message has no explicit from | | NUXT_EMAIL_DRY_RUN | true/false; empty falls back to dev mode |

Zimbra

[email protected]
NUXT_EMAIL_ZIMBRA_PASSWORD=your-password

SMTP

NUXT_EMAIL_SMTP_HOST=smtp.example.com
NUXT_EMAIL_SMTP_PORT=587
NUXT_EMAIL_SMTP_USERNAME=your-username
NUXT_EMAIL_SMTP_PASSWORD=your-password
[email protected]

sendEmail(message, options?)

message is either a direct message (explicit to + subject) or a templated message (template + optional overrides). Provide at least one body — html, text, or both.

| Field | Type | Notes | |-------|------|-------| | to | string \| EmailAddress \| Array<…> | Required unless a template provides it | | subject | string | Required unless a template provides it | | html | string | HTML body — at least one of html / text is required | | text | string | Plain-text body — at least one of html / text is required | | from | string \| EmailAddress | Defaults to the provider account + NUXT_EMAIL_DEFAULT_FROM_NAME | | cc / bcc | string \| EmailAddress \| Array<…> | Optional | | replyTo | string \| EmailAddress | Optional | | attachments | EmailAttachment[] | { filename, content: Buffer \| string, contentType? } | | template | EmailTemplate | A defineEmailTemplate preset (see below) | | variables | inferred | Required & typed when the template's subject is a function |

options.provider overrides the provider for a single call:

await sendEmail({ to: '[email protected]', subject: 'Hi', html }, { provider: 'smtp' })

Contacts

Author reusable Name + Email identities once and reference them directly — no retyped literals.

Define them in a shared file (auto-imported from server/utils):

// server/utils/emails.ts
export const CONTACTS = {
  Web: defineEmailContact({ name: 'Jagu', email: '[email protected]' }),
  Support: defineEmailContact({ name: 'Jagu Support', email: '[email protected]' }),
}

Then reference them when you send — in a route handler:

// server/api/contact.post.ts
export default defineEventHandler(async () => {
  return await sendEmail({ to: CONTACTS.Web, subject: 'Hi', html })
})

Templates

A template is a reusable preset of from / subject / cc / bcc / replyTo / providernot the HTML. Explicit fields on a sendEmail call override the template.

Define them alongside your contacts:

// server/utils/emails.ts
export const TEMPLATES = {
  Inquiry: defineEmailTemplate({
    from: CONTACTS.Web,
    to: CONTACTS.Web,
    subject: 'Nový zájemce o služby',
    provider: 'zimbra',
  }),
  // A function subject makes `variables` required and type-checked at the call site.
  Order: defineEmailTemplate({
    from: CONTACTS.Web,
    subject: (vars: { id: number }) => `Order #${vars.id}`,
  }),
}

Use them from a handler — the template fills in the preset, you supply the body:

// server/api/order.post.ts
export default defineEventHandler(async () => {
  await sendEmail({ template: TEMPLATES.Inquiry, html })
  await sendEmail({ template: TEMPLATES.Order, variables: { id: 42 }, html })
})

Adding a provider

A provider implements one method: send(message). Register it on server startup and augment the registry interface so its name is type-safe everywhere.

// server/plugins/email.ts
declare global {
  interface EmailProviderRegistry {
    resend: true
  }
}

export default defineNitroPlugin(() => {
  registerEmailProvider(
    'resend',
    defineEmailProvider({
      async send(message) {
        // message.to/cc/bcc are EmailAddress[]; subject/html are resolved; attachments default to [].
        await myResendCall(message)
      },
    })
  )
})

Now NUXT_EMAIL_PROVIDER=resend, { provider: 'resend' }, and provider: 'resend' in a template are all autocompleted and type-checked.

Upgrading from v2

v3 is a clean break: the old sendZimbraEmail(...) / sendSmtpEmail(...) positional functions are gone, replaced by a single sendEmail({ ... }). Work through these steps:

1. Bump the dependency to ^3.0.0 and reinstall.

2. Convert every call site. Find them with grep -rn 'sendZimbraEmail\|sendSmtpEmail' server, then rewrite each from positional args to a message object. The provider is no longer baked into the function name — it comes from NUXT_EMAIL_PROVIDER, or pass { provider } to pin one:

// v2
await sendZimbraEmail('Jagu', '[email protected]', 'Subject', template, [])

// v3
await sendEmail(
  { from: { name: 'Jagu', email: '[email protected]' }, to: '[email protected]', subject: 'Subject', html: template },
  { provider: 'zimbra' },
)

3. Rename env vars in every deployment — they're now namespaced under EMAIL:

| v2 | v3 | |----|----| | NUXT_ZIMBRA_EMAIL | NUXT_EMAIL_ZIMBRA_EMAIL | | NUXT_ZIMBRA_PASSWORD | NUXT_EMAIL_ZIMBRA_PASSWORD | | NUXT_SMTP_HOST | NUXT_EMAIL_SMTP_HOST | | NUXT_SMTP_PORT | NUXT_EMAIL_SMTP_PORT | | NUXT_SMTP_USERNAME | NUXT_EMAIL_SMTP_USERNAME | | NUXT_SMTP_PASSWORD | NUXT_EMAIL_SMTP_PASSWORD | | NUXT_SMTP_EMAIL | NUXT_EMAIL_SMTP_EMAIL |

4. Set NUXT_EMAIL_PROVIDER (zimbra or smtp) in each deployment to pick the default provider.

5. Delete manual dev guards. Remove any if (process.env.NODE_ENV === 'development') return from your handlers — the built-in dry-run (on by default in dev) replaces them.

Development

The .playground directory is a regular Nuxt app that extends this layer.

pnpm install
pnpm dev        # boot .playground on http://localhost:3000
pnpm typecheck
pnpm lint

Distributing

Bump the version, confirm files in package.json, then:

npm publish --access public