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

directus-extension-email-send

v2.0.0

Published

A thin courier endpoint that dispatches fully-specified email requests to the internal Directus MailService.

Readme

directus-extension-email-send

A thin courier endpoint that dispatches fully-specified email requests to Directus' internal MailService. It implements no business logic, no template rendering, no storage, no logging, and no sender resolution — MailService remains the single authority for template resolution, rendering, and sender defaults. Requests must be authenticated and permitted through Directus' native policy system before dispatch.

The payload schemas mirror the internals: what you send is what MailService.send receives (a nodemailer EmailOptions subset), validated with valibot and handed down as-is.

Endpoints

Both routes mount under /email/ and expect a JSON body. Addresses are nodemailer-shaped: a bare address string, or { "address": string, "name": string } — when using the object form the display name is required; omit the object entirely and send a string instead.

POST /email/send/html

By default the provided HTML is wrapped in Directus' standard base template — its content block is {% block content %}{{ html }}{% endblock %}, so the html lands inside the branded layout (project name, color, logo and url from your Directus settings; a custom base.liquid in EMAIL_TEMPLATES_PATH overrides it). Set rawHTML: true to send the HTML exactly as received, without any template.

{
	"to": { "address": "[email protected]", "name": "Operations Team" },
	"subject": "Nightly backup finished",
	"html": "<p>The nightly backup finished successfully.</p>",
	"text": "The nightly backup finished successfully.",
	"from": { "address": "[email protected]", "name": "Backup Reporter" },
	"rawHTML": false
}

| Field | Required | Notes | | --- | --- | --- | | to | yes | Bare address string or { "address", "name" } | | subject | yes | Non-empty string | | html | yes | Non-empty string; wrapped in the base template unless rawHTML is true | | text | no | Plain-text alternative | | from | no | Bare address string or { "address", "name" }; omitted → Directus falls back to EMAIL_FROM | | rawHTML | no | true sends the html as received, without any template; default wraps it in the base template |

POST /email/send/template

Hands a template name and data object to the mail service; rendering happens inside Directus.

{
	"to": "[email protected]",
	"subject": "Nightly backup finished",
	"template": { "name": "backup-report", "data": { "machines": 3, "failures": 0 } },
	"from": { "address": "[email protected]", "name": "Backup Reporter" }
}

| Field | Required | Notes | | --- | --- | --- | | to | yes | Bare address string or { "address", "name" } | | subject | yes | Non-empty string | | template.name | yes | Name of a Directus mail template | | template.data | yes | Object passed to the template | | from | no | Bare address string or { "address", "name" }; omitted → Directus falls back to EMAIL_FROM |

Response contracts

  • 200 OK — { "status": "sent" | "suppressed", "message_id"?: "<nodemailer_id>", "response"?: "<nodemailer_response>" }; "sent" when the mail transport delivers; "suppressed" (MailService.send returns null from the source) when the Directus email.send event filter suppresses the message.
  • 400 / 401 / 403 — standard Directus error envelope ({ "errors": [ { "message": "...", "extensions": { "code": "...", ... } } ] }) via @directus/errors
  • 502 Bad Gateway — the one custom body, written explicitly: { "status": "failed", "error": { "name": "...", "message": "..." } } (Mailer rejection; name and message only, no stack traces)

Environment variables

| Variable | Purpose | | --- | --- | | EMAIL_FROM | Default sender address used by Directus itself when the payload omits from — handled by the MailService, not this extension |

Setup

  1. Install the extension and restart Directus. On first boot an init hook creates the email_send_extension_email_send_requests gate collection (visible in the Data Studio, no business fields) plus a default policy "Email Sender" granting create on it, attached to no one. The collection name is prefixed with the extension name so it can never collide with collections created by other extensions.
  2. If the collection already exists, boot never alters it and never creates a duplicate policy. However, if the collection exists while no policy grants create on it (for example when a previous boot was interrupted between the two steps, which happens under PM2 cluster mode where the launcher process boots before the cluster worker), boot heals the gate by creating the default policy.
  3. In the admin panel, attach the default policy — or your own policy granting create on email_send_extension_email_send_requests — to the dispatching service user or humans.
  4. Set EMAIL_FROM (or always pass from in the payload).

Requests without a valid token receive 401. The send capability is resolved through Directus' own /permissions/me endpoint (with the requester's token), the documented way for custom endpoints to check capabilities: a user whose policies grant create on email_send_extension_email_send_requests passes; everyone else receives 403. Deny by default.

Logging

Every log line from this extension is prefixed with [email-send]. On boot it reports each step: creating the gate collection, creating the default policy, or skipping because the collection already exists. If a collection named email_send_requests (without the extension prefix) appears in your instance, it was not created by this extension.

Limitation notice

This extension returns nodemailer transport info, not the rendered email — template rendering happens inside Directus after this extension hands off.

Development

npm install
npm run typecheck
npm test
npm run build

Unit tests are colocated with their sources (*.test.ts); the tests/ directory is reserved for e2e.

Roadmap

Possible V2 features (request logging, template variable getter/getter/patcher/locker) are tracked in ROADMAP.md — none of them are part of the shipped thin-courier endpoint.