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

@antelopejs/dms-mailing

v0.2.3

Published

<div align="center"> <a href="./LICENSE"><img alt="License" src="https://img.shields.io/badge/license-Apache--2.0-blue?style=for-the-badge&labelColor=000000"></a> <a href="https://discord.gg/sjK28QHrA7"><img src="https://img.shields.io/badge/Discord-18181

Downloads

242

Readme

@antelopejs/dms-mailing

Mailing module for the AntelopeJS DMS: a template library with a block-based e-mail editor, a send log, an overview dashboard and settings. It owns the data (tenant-scoped Mongo tables), renders e-mails through the DMS html-render chain and sends them through @antelopejs/interface-email.

Layout

| Path | What it holds | | --- | --- | | src/index.ts | Module lifecycle (construct/start/stop/destroy); registers the Vue frontend module, the automation nodes, the tenant-export contributor, the realtime topics and the retention cron. | | src/pages/ | The DMS pages: overview, templates, editor, sends and the settings form. | | src/data/ | The TableView data controllers behind /api/mailing/tables/*. | | src/routes/ | The HTTP API under /api/mailing (templates, sends, events, metrics, settings, provider). | | src/engine/ | The pure-TS rendering engine: block tree + variables → resolved e-mail. | | ../interface-dms-mailing/ | The separately published public AntelopeJS interface (see below). | | src/automation/ | The dms-automation action and trigger. | | src/crons/ | The daily, replay-safe send-log retention job. | | src/hooks/ | The tenant data export contributor. | | frontend-vue/ | Vue Inertia module: gallery display, detail drawers, block editor, dms.email.ts server template entry and the mailing-*.json i18n catalogs. | | playground/ | Standalone AntelopeJS project wiring this module to the DMS, Mongo, the API server and the mailer, for local development. |

Public interface

Published as @antelopejs/interface-dms-mailing.

import {
  RecordEmailEvent,
  SendTemplate,
} from "@antelopejs/interface-dms-mailing";

// Render a live template and send it.
const result = await SendTemplate("order-confirmation", {
  tenantId,
  to: [{ email: "[email protected]", name: "Sofie" }],
  locale: "fr",
  variables: { order: { total: "48.00" } },
  source: "checkout",
});

// Report a provider event against the send it belongs to.
await RecordEmailEvent(tenantId, {
  provider: "brevo",
  messageId,
  type: "bounced",
  details: { reason: "mailbox full" },
});

SendTemplate accepts one address or an array. With more than one recipient and a provider that advertises features.batch, the module renders the e-mail once and issues a single SendBatch; otherwise it loops Send. Either way one row per recipient lands in the send log, and the returned SendTemplateResult describes the first one.

Webhook contract

Provider events are posted to POST /api/mailing/events/:provider, guarded by the x-mailing-webhook-secret header (the secret lives in the mailing settings, per tenant).

curl -X POST http://localhost:5010/api/mailing/events/brevo \
  -H "Content-Type: application/json" \
  -H "x-mailing-webhook-secret: <settings.webhookSecret>" \
  -d '{"messageId":"<provider message id>","type":"bounced","at":"2026-09-09T10:00:00.000Z","details":{"reason":"mailbox full"}}'

type is one of queued, sent, delivered, opened, clicked, bounced, spam, failed, unsubscribed. The event is appended to the send's timeline, the send status only ever moves forward, and a bounced, spam or failed event notifies the tenant owners in the DMS bell.

Retention

The daily sweep retires sends only when their creation time, latest event time and latest ingested activity all precede the tenant's retention cutoff. Queued sends remain active. Retirement irreversibly closes a send to provider events; the sweep then removes its events and finally the send. A failed cleanup leaves the retirement marker available for another sweep to resume.

Concurrent sweeps require no shared cron lock. Send mutations use the database interface's Table.atomicMutation revision contract. Retirement rechecks the send's age and activity before comparing its revision; concurrent webhook activity advances that revision and invalidates the stale candidate. Known webhook conflicts reread and recompute counters, with a bounded retry limit. An unknown write outcome fails without retrying or reporting a deletion.

This requires the atomic-mutation interface and adapter implementations; generic filtered updates and deletes are not an alternative. The dependency releases are prerequisites for rollout. Every send is inserted with a unique identity and revision, stored in the declared revision column.

Each sweep inspects at most 20,000 send candidates per tenant and reports only actual send deletions. It also reconciles orphan events left by writers that crashed after inserting an event for an already-deleted send. That reconciliation scans the event table in keyset pages of 500 rows, with a parent lookup per distinct send in each page; its total work is not capped by the send limit. Event writes create the parent first and never reuse deleted send IDs. Neither retention path deletes templates or files. Disabled retention skips both passes.

Automation nodes

Registered against the optional @antelopejs/interface-dms-automation. They are inert no-ops when no module implements it.

| Node | Id | Purpose | | --- | --- | --- | | Action | mailing.send-template | Renders a live template and sends it. Inputs tenantId, slug, to (required), locale, variables, source. | | Trigger | mailing.email-event | Fires on provider events. Config types filters the event types; empty fires on every one. |

Development

pnpm install
pnpm --dir frontend-vue install
pnpm --dir playground install

pnpm dev            # backend + module, watching src/
pnpm frontend:dev   # the DMS front-end, in another terminal

The backend listens on http://localhost:5010 and needs a MongoDB on mongodb://localhost:27017 (database playground_dms_mailing).

Both packages are public on npm, published under the @antelopejs scope with npm trusted publishing and provenance. The interface package must be released before @antelopejs/dms-mailing. Each package has its own manual workflow: Release DMS mailing interface publishes ../interface-dms-mailing, Release DMS mailing module publishes this package and refuses to run until the interface version its dependency range is floored at resolves on npm. Inside the workspace that same range resolves to the sibling package, through link-workspace-packages.

Conventions

Every backend-declared text uses the $key convention: a string starting with $ is resolved against the i18n catalogs by the front-end. Add keys to frontend-vue/i18n/locales/mailing-en-GB.json and its French counterpart.

Checks

pnpm lint        # oxlint + oxfmt + the layer's eslint
pnpm typecheck   # tsconfig.json (the one editors and the type-aware lint read)
pnpm knip        # unused dependencies
pnpm build       # tsconfig.build.json -> dist/
pnpm test        # backend (mocha through `ajs module test`) + layer (vitest)
pnpm test:frontend # generated client/SSR/email builds and Vue typecheck

The frontend check uses the published DMS and frontend adapter packages. It renders English/French email fixtures locally and does not send email.