From ef6b2fe79536320d0c3e271bcff184920ddc8cb1 Mon Sep 17 00:00:00 2001 From: dupontbertrand Date: Mon, 23 Mar 2026 23:13:19 +0100 Subject: [PATCH 1/2] docs: add dupontbertrand:mail-preview to community packages Add documentation page for the mail-preview package, a zero-config dev-mode email preview UI that captures outgoing emails and displays them at /__meteor_mail__/. Ref: https://forums.meteor.com/t/built-in-mail-preview-ui-for-dev-mode/64489 --- v3-docs/docs/.vitepress/config.mts | 4 + v3-docs/docs/community-packages/index.md | 4 + .../docs/community-packages/mail-preview.md | 123 ++++++++++++++++++ 3 files changed, 131 insertions(+) create mode 100644 v3-docs/docs/community-packages/mail-preview.md diff --git a/v3-docs/docs/.vitepress/config.mts b/v3-docs/docs/.vitepress/config.mts index e25264bda4..fe19ac20d4 100644 --- a/v3-docs/docs/.vitepress/config.mts +++ b/v3-docs/docs/.vitepress/config.mts @@ -461,6 +461,10 @@ export default defineConfig({ text: "jam:offline", link: "/community-packages/offline", }, + { + text: "dupontbertrand:mail-preview", + link: "/community-packages/mail-preview", + }, ], collapsed: true, }, diff --git a/v3-docs/docs/community-packages/index.md b/v3-docs/docs/community-packages/index.md index 7de8ec0565..8e5411c860 100644 --- a/v3-docs/docs/community-packages/index.md +++ b/v3-docs/docs/community-packages/index.md @@ -40,6 +40,10 @@ Please bear in mind if you are adding a package to this list, try providing as m - [`jam:soft-delete`](./soft-delete.md), An easy way to add soft deletes to your Meteor app - [`jam:archive`](./archive.md), +#### Developer tools + +- [`dupontbertrand:mail-preview`](./mail-preview.md), Zero-config dev-mode mail preview UI — view captured emails at `/__meteor_mail__/` + #### Utilities - [`jam:offline`](./offline.md), An easy way to give your Meteor app offline capabilities and make it feel instant diff --git a/v3-docs/docs/community-packages/mail-preview.md b/v3-docs/docs/community-packages/mail-preview.md new file mode 100644 index 0000000000..9f654ccc93 --- /dev/null +++ b/v3-docs/docs/community-packages/mail-preview.md @@ -0,0 +1,123 @@ +# Mail Preview + +- `Who maintains the package` – [Bertrand Dupont](https://github.com/dupontbertrand) + +[[toc]] + +## What Is It? + +A zero-config, dev-mode mail preview UI for Meteor. Every email sent via `Email.sendAsync()` is captured and displayed in a browser UI at `/__meteor_mail__/`. + +Inspired by similar features in Rails (Action Mailer Preview), Phoenix (Swoosh), Laravel (Mailtrap), and Django (console backend). + +This is a `devOnly` package — it is **automatically excluded from production builds**. Zero overhead in production, no need to remove it before deploying. + +## How to Download It? + +```bash +meteor add dupontbertrand:mail-preview +``` + +That's it. No configuration needed. + +## How to Use It? + +1. Start your Meteor app in development mode (`meteor run`) +2. Trigger any email — password reset, verification, enrollment, or your own `Email.sendAsync()` calls +3. Open `http://localhost:3000/__meteor_mail__/` in your browser + +### What You Get + +- **Mail list** — live-updating list of all captured emails (polls every 2s, no page reload) +- **Mail detail** — view each email with tabs for **HTML render**, **Plain Text**, and **HTML Source** +- **Clickable links** — verification, password reset, and enrollment links work directly from the preview +- **JSON API** — programmatic access for testing and tooling +- **Clear all** — one-click button to clear captured mails + +### Example: Capturing an Accounts Email + +```js +// Server — nothing special needed, just use Meteor's built-in accounts +import { Accounts } from 'meteor/accounts-base'; + +// When a user registers, Meteor sends a verification email. +// mail-preview captures it automatically. +Accounts.sendVerificationEmail(userId); +``` + +Then open `/__meteor_mail__/` to see the captured email, click the verification link, and it works. + +### Example: Custom Emails with MJML + +```js +// Server +import { Email } from 'meteor/email'; +import mjml2html from 'mjml'; + +const { html } = mjml2html(` + + + + + Hello from Meteor! + + Visit Meteor + + + + + +`); + +await Email.sendAsync({ + from: 'noreply@example.com', + to: 'user@example.com', + subject: 'Welcome!', + html, +}); +``` + +The MJML-rendered email is captured and displayed with full HTML rendering in the preview UI. + +## JSON API + +For programmatic access (useful in tests or tooling): + +| Method | Endpoint | Description | +| -------- | ------------------------------ | ------------------------ | +| `GET` | `/__meteor_mail__/api/mails` | List all captured mails | +| `GET` | `/__meteor_mail__/api/mails/:id` | Get a single mail | +| `DELETE` | `/__meteor_mail__/api/mails` | Clear all captured mails | + +### Example: Using the API in Tests + +```js +// In a test, after triggering an email: +const res = await fetch('http://localhost:3000/__meteor_mail__/api/mails'); +const { mails } = await res.json(); + +assert.equal(mails[0].subject, 'Verify your email'); +assert.ok(mails[0].text.includes('verify-email')); +``` + +## How It Works + +The package uses `Email.hookSend()` to intercept outgoing emails and store them in memory (up to 50 — oldest are evicted). A middleware mounted via `WebApp.rawConnectHandlers` serves the preview UI. + +- **Dev mode only** — guarded by `Meteor.isDevelopment` and `devOnly: true` in `package.js` +- **No SMTP needed** — emails are captured before they reach any transport +- **No external dependencies** — uses only Meteor core packages (`email`, `webapp`, `ecmascript`) +- **Works alongside `MAIL_URL`** — if set, emails are still sent normally; the hook captures a copy + +## Compatibility + +- Meteor 3.4+ +- Works with `accounts-password` emails (verification, reset password, enrollment) +- Works with custom `Email.sendAsync()` / `Email.send()` calls +- Compatible with Rspack bundler + +## Sources + +- **Atmosphere:** [dupontbertrand:mail-preview](https://atmospherejs.com/dupontbertrand/mail-preview) +- **GitHub:** [dupontbertrand/meteor-mail-preview](https://github.com/dupontbertrand/meteor-mail-preview) +- **Forum Discussion:** [Built-in Mail Preview UI for Dev Mode](https://forums.meteor.com/t/built-in-mail-preview-ui-for-dev-mode/64489) From 494290612bd348489cbc69e3d9dca315f04aeb45 Mon Sep 17 00:00:00 2001 From: dupontbertrand Date: Mon, 23 Mar 2026 23:24:50 +0100 Subject: [PATCH 2/2] docs: add dupontbertrand:cluster to community packages - Add documentation page for dupontbertrand:cluster, a Meteor 3 compatible fork of meteorhacks:cluster - Add entry to community packages index under new Scaling / Clustering category - Add sidebar navigation entry --- v3-docs/docs/.vitepress/config.mts | 4 + v3-docs/docs/community-packages/cluster.md | 152 +++++++++++++++++++++ v3-docs/docs/community-packages/index.md | 4 + 3 files changed, 160 insertions(+) create mode 100644 v3-docs/docs/community-packages/cluster.md diff --git a/v3-docs/docs/.vitepress/config.mts b/v3-docs/docs/.vitepress/config.mts index 97af62202c..53eeaffe19 100644 --- a/v3-docs/docs/.vitepress/config.mts +++ b/v3-docs/docs/.vitepress/config.mts @@ -461,6 +461,10 @@ export default defineConfig({ text: "jam:offline", link: "/community-packages/offline", }, + { + text: "dupontbertrand:cluster", + link: "/community-packages/cluster", + }, ], collapsed: true, }, diff --git a/v3-docs/docs/community-packages/cluster.md b/v3-docs/docs/community-packages/cluster.md new file mode 100644 index 0000000000..04b8f6fadb --- /dev/null +++ b/v3-docs/docs/community-packages/cluster.md @@ -0,0 +1,152 @@ +# Cluster + +- `Who maintains the package` – [Bertrand Dupont](https://github.com/dupontbertrand) + +[[toc]] + +## What Is It? + +A Meteor 3 compatible fork of [`meteorhacks:cluster`](https://github.com/meteorhacks/cluster), providing multi-core support, load balancing, and service discovery for Meteor apps. + +The original package was abandoned around 2016. This fork ports it to Meteor 3 with async/await, modern dependencies, and updated MongoDB driver — while preserving the same public API. + +::: warning +This is a **compatibility / migration bridge**, not a new recommended scaling architecture. If you only need multi-core CPU utilization, an external process manager like [PM2](https://pm2.keymetrics.io/) is simpler. This package is for apps that relied on `meteorhacks:cluster` features like service discovery, DDP-aware proxying, or `Cluster.discoverConnection()`. +::: + +## How to Download It? + +```bash +meteor add dupontbertrand:cluster +``` + +## How to Use It? + +### Multi-Core (Simplest Use Case) + +Just set the `CLUSTER_WORKERS_COUNT` environment variable: + +```bash +# Use all available CPU cores +CLUSTER_WORKERS_COUNT=auto meteor run + +# Or specify a number +CLUSTER_WORKERS_COUNT=4 meteor run +``` + +The package forks child processes automatically. No code changes needed. + +### Service Discovery + Load Balancing + +For multi-instance deployments with automatic service registration and DDP-aware load balancing: + +```bash +export CLUSTER_DISCOVERY_URL="mongodb://your-mongo-host/cluster" +export CLUSTER_SERVICE="web" +export CLUSTER_WORKERS_COUNT=2 +``` + +Instances register themselves in MongoDB and discover each other automatically. No need to reconfigure a load balancer when adding or removing instances. + +### Microservices + +Connect to other services in the cluster by name: + +```js +// Server +const serviceConnection = Cluster.discoverConnection('payments'); + +// Call methods on the remote service +const result = await serviceConnection.callAsync('processPayment', data); +``` + +## API + +### `Cluster.connect(discoveryUrl, [options])` + +Connect to a discovery backend (currently MongoDB). + +```js +Cluster.connect('mongodb://host/db'); +``` + +Usually configured via the `CLUSTER_DISCOVERY_URL` environment variable instead of calling directly. + +### `Cluster.register(serviceName, [options])` + +Register this instance as a named service. + +```js +Cluster.register('web'); +``` + +Options: +- `endpoint` — the URL other instances use to reach this one (defaults to `ROOT_URL`) +- `balancer` — URL of the balancer (defaults to `CLUSTER_BALANCER_URL`) +- `uiService` — the service that serves the UI (defaults to the registered service name) + +Usually configured via the `CLUSTER_SERVICE` environment variable. + +### `Cluster.discoverConnection(serviceName, [ddpOptions])` + +Get a DDP connection to a named service. The connection automatically tracks healthy instances via the discovery backend. + +```js +const conn = Cluster.discoverConnection('analytics'); +const data = await conn.callAsync('getReport', params); +``` + +### `Cluster.allowPublicAccess(serviceList)` + +Allow public (non-authenticated) access to specific services. + +```js +Cluster.allowPublicAccess(['web', 'api']); +``` + +Usually configured via the `CLUSTER_PUBLIC_SERVICES` environment variable (comma-separated). + +## Environment Variables + +| Variable | Description | Default | +| -------- | ----------- | ------- | +| `CLUSTER_WORKERS_COUNT` | Number of worker processes (`auto` = all cores) | 1 (no clustering) | +| `CLUSTER_DISCOVERY_URL` | MongoDB URL for service discovery | — | +| `CLUSTER_SERVICE` | Name to register this instance as | — | +| `CLUSTER_ENDPOINT_URL` | URL for other instances to reach this one | `ROOT_URL` | +| `CLUSTER_BALANCER_URL` | URL of the load balancer | — | +| `CLUSTER_PUBLIC_SERVICES` | Comma-separated list of public services | — | +| `CLUSTER_UI_SERVICE` | Service that serves the UI | same as `CLUSTER_SERVICE` | + +## What Changed From meteorhacks:cluster + +This fork preserves the original API. Under the hood: + +- MongoDB discovery backend rewritten in **async/await** (was Fibers/wrapAsync) +- MongoDB driver updated from 1.4.x to **6.12.0** +- `underscore` replaced with native ES2015+ +- npm dependencies updated (`cookies`, `http-proxy`, `portscanner`) +- Fixed `Buffer()` deprecation and a pre-existing IPC listener bug +- Balancer made transport-aware for compatibility with pluggable transports ([#14231](https://github.com/meteor/meteor/pull/14231)) +- Test suite adapted for Meteor 3 + +Full changelog: [METEOR3-PORT.md](https://github.com/dupontbertrand/cluster/blob/master/METEOR3-PORT.md) + +## Known Limitations + +- The balancer uses `OverShadowServerEvent` to intercept HTTP/WS traffic at the `httpServer` level. This is invasive and could break if Meteor changes its listener initialization order. +- No Windows testing yet. +- The worker pool uses `child_process.fork` with per-worker ports (not `node:cluster`). + +## Compatibility + +- Meteor 3.4+ +- Node 22 +- Tested on Linux + +## Sources + +- **Atmosphere:** [dupontbertrand:cluster](https://atmospherejs.com/dupontbertrand/cluster) +- **GitHub:** [dupontbertrand/cluster](https://github.com/dupontbertrand/cluster) +- **Original package:** [meteorhacks/cluster](https://github.com/meteorhacks/cluster) +- **Forum Discussion:** [Memory usage and sessions quadruple after upgrading to Meteor 3](https://forums.meteor.com/t/memory-usage-and-sessions-quadruple-after-upgrading-to-meteor-3/64496) diff --git a/v3-docs/docs/community-packages/index.md b/v3-docs/docs/community-packages/index.md index ee31736a45..e49248e39f 100644 --- a/v3-docs/docs/community-packages/index.md +++ b/v3-docs/docs/community-packages/index.md @@ -36,6 +36,10 @@ Please bear in mind if you are adding a package to this list, try providing as m - [`jam:soft-delete`](./soft-delete.md), An easy way to add soft deletes to your Meteor app - [`jam:archive`](./archive.md), +#### Scaling / Clustering + +- [`dupontbertrand:cluster`](./cluster.md), Meteor 3 fork of meteorhacks:cluster — multi-core, load balancing, service discovery + #### Utilities - [`jam:offline`](./offline.md), An easy way to give your Meteor app offline capabilities and make it feel instant