Skip to content

Add mailtrap email transport support - #27873

Merged
ComfortablyCoding merged 14 commits into
directus:mainfrom
tsokolovs:feat/mailtrap-email-transport
Aug 27, 2026
Merged

ComfortablyCoding merged 14 commits into
directus:mainfrom
tsokolovs:feat/mailtrap-email-transport

Conversation

@tsokolovs

@tsokolovs tsokolovs commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

What's Changed

Adds Mailtrap as a native option for EMAIL_TRANSPORT, alongside the existing sendmail, smtp, ses, and mailgun transports. It uses Mailtrap's official mailtrap package, which ships a Nodemailer transport (MailtrapTransport).

The transport supports all three Mailtrap modes:

  • Live sending via an API token.
  • Sandbox / Email Testing by capturing messages in a test inbox.
  • Bulk stream for high-volume sending.

Implementation mirrors the existing Mailgun transport in api/src/mailer.ts (lazy require, env-driven options). Options are only passed to the SDK when their env vars are set.

New environment variables

Variable Type Description
EMAIL_MAILTRAP_TOKEN string Mailtrap API token
EMAIL_MAILTRAP_SANDBOX boolean Send to a test inbox instead of delivering
EMAIL_MAILTRAP_INBOX_ID number Inbox ID used when sandbox is enabled
EMAIL_MAILTRAP_BULK boolean Use the bulk sending stream

Tested Scenarios

  • Unit test added for the new transport; api/src/mailer.test.ts passes (5/5), @directus/env passes (78/78).
  • Live sandbox send: message delivered to a Mailtrap test inbox (verified via the API).
  • Live production send: delivered to a real mailbox.
  • Bulk stream: 10 live messages sent via EMAIL_MAILTRAP_BULK=true, all succeeded.
  • ESLint + Prettier clean on changed files.

Review Notes / Questions / Concerns

  • Adding the mailtrap dependency pulls in axios, which dedupes qs to 6.15.2 in the lockfile. This is the only transitive change beyond the direct addition.

Checklist

Leave unchecked where not applicable

  • Tests added/updated
  • Documentation PR created in directus/docs Document Mailtrap email transport docs#768
  • OpenAPI updated
  • SDK (@directus/sdk) updated to reflect the changes
  • Types (@directus/types) updated to reflect the changes
  • GraphQL schema updated to reflect the changes
  • System data (@directus/system-data) updated for changes to system collections/fields/relations
  • Database migration added for schema/system changes
  • Environment variables documented for new/changed config
  • App translations added for new user-facing strings
  • Security implications apply

@tsokolovs

tsokolovs commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

I've used an AI agent to create a PR for me (via the gh cli) to make it easier for me to ensure I've fulfilled all the guidelines outlined in contributing.md file. The code itself was written manually. Happy to answer all the questions you have.

@codecov

codecov Bot commented Jul 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 71.32%. Comparing base (61f43d3) to head (5602f74).

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #27873      +/-   ##
==========================================
+ Coverage   71.24%   71.32%   +0.07%     
==========================================
  Files        2510     2510              
  Lines      174658   174672      +14     
  Branches    18879    18909      +30     
==========================================
+ Hits       124442   124577     +135     
+ Misses      50216    50095     -121     
Flag Coverage Δ
api 59.63% <100.00%> (+0.20%) ⬆️
app 78.91% <ø> (ø)
cli 97.53% <ø> (ø)
composables 83.87% <ø> (ø)
constants 4.66% <ø> (ø)
create-directus-extension 96.55% <ø> (ø)
create-directus-project 98.43% <ø> (ø)
env 99.74% <100.00%> (+<0.01%) ⬆️
errors 96.29% <ø> (ø)
extensions 56.32% <ø> (ø)
extensions-registry 95.43% <ø> (ø)
extensions-sdk 16.68% <ø> (ø)
format-title 100.00% <ø> (ø)
memory 100.00% <ø> (ø)
pressure 77.63% <ø> (ø)
release-notes-generator 84.77% <ø> (ø)
schema-builder 81.42% <ø> (ø)
sdk 36.20% <ø> (ø)
storage 92.00% <ø> (ø)
storage-driver-azure 73.33% <ø> (ø)
storage-driver-cloudinary 80.87% <ø> (ø)
storage-driver-gcs 66.95% <ø> (ø)
storage-driver-local 70.89% <ø> (ø)
storage-driver-s3 52.27% <ø> (ø)
storage-driver-supabase 67.40% <ø> (ø)
system-data 32.42% <ø> (ø)
update-check 55.67% <ø> (ø)
utils 93.19% <ø> (ø)
validation 43.78% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@tsokolovs tsokolovs changed the title Add Mailtrap email transport 🤖🤖🤖 Add Mailtrap email transport Jul 14, 2026
@tsokolovs
tsokolovs force-pushed the feat/mailtrap-email-transport branch 3 times, most recently from adeb5aa to b0c90c2 Compare July 22, 2026 12:26
@tsokolovs
tsokolovs force-pushed the feat/mailtrap-email-transport branch 2 times, most recently from 9f945af to 1141592 Compare July 30, 2026 07:43

@AlexGaillard AlexGaillard left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this, just a few small changes here and also TY for pointing out the shortcomings of our mailer.test.ts, it's a tricky one to test 😅 .

Comment thread api/src/mailer.ts Outdated
Comment thread api/src/mailer.test.ts
Comment thread api/src/mailer.test.ts Outdated
@tsokolovs
tsokolovs force-pushed the feat/mailtrap-email-transport branch 2 times, most recently from 55e2309 to 7e6e5d3 Compare August 5, 2026 06:18
@tsokolovs
tsokolovs force-pushed the feat/mailtrap-email-transport branch from e745b8f to 2575b50 Compare August 11, 2026 09:29
@tsokolovs
tsokolovs force-pushed the feat/mailtrap-email-transport branch from 2575b50 to 7a7da81 Compare August 13, 2026 08:13
Add a native `mailtrap` option for `EMAIL_TRANSPORT` using Mailtrap's official nodemailer transport. Supports live sending, the bulk stream, and the sandbox testing inbox via EMAIL_MAILTRAP_TOKEN, EMAIL_MAILTRAP_SANDBOX, EMAIL_MAILTRAP_INBOX_ID, and EMAIL_MAILTRAP_BULK.
Re-import getMailer in the Mailtrap test so the module-scoped transporter cache doesn't short-circuit the new branch, ensuring it actually executes under coverage.
The mailtrap@4.6.1 snapshot referenced axios@1.16.1, which has no
corresponding entry in the lockfile. Point it at the already-resolved
axios@1.18.1 so that pnpm install --frozen-lockfile succeeds.
@tsokolovs
tsokolovs force-pushed the feat/mailtrap-email-transport branch from 7a7da81 to 5df0715 Compare August 14, 2026 11:04

@ComfortablyCoding ComfortablyCoding left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! LGTM.

P.S.

I removed the custom errors for invalid setups since we lazy-load the mailer, so those errors would only be surfaced when sending anyway making them redundant.

I left the token check in place because Mailtrap doesn’t implement nodemailer.verify, which could otherwise lead to false results.

@ComfortablyCoding ComfortablyCoding changed the title Add Mailtrap email transport Added mailtrap email transport support Aug 27, 2026
@ComfortablyCoding ComfortablyCoding changed the title Added mailtrap email transport support Add mailtrap email transport support Aug 27, 2026
@ComfortablyCoding
ComfortablyCoding merged commit e29e467 into directus:main Aug 27, 2026
101 checks passed
@github-actions github-actions Bot added this to the Next Release milestone Aug 27, 2026
@tsokolovs
tsokolovs deleted the feat/mailtrap-email-transport branch August 31, 2026 08:05
alvarosabu pushed a commit that referenced this pull request Sep 9, 2026
* add shared util for collection status

* add inactive prop to field tree and field list

* disable inactive collections in collection picker interfaces

* disable inactive collections in the content module

* terminology change

* handle m2a fields

* handle table column selection

* handle kanban options

* insights

* changeset

* remove tooltip - would take additional work to get it working in all locations

* rehydrate fields and relations when status changes

* move insight panel checks into store

* fix disabled fields in snapshots and tests

* Revert "fix disabled fields in snapshots and tests"

This reverts commit 19308ca.

* Add `mailtrap` email transport support (#27873)

* Add Mailtrap email transport 🤖🤖🤖

Add a native `mailtrap` option for `EMAIL_TRANSPORT` using Mailtrap's official nodemailer transport. Supports live sending, the bulk stream, and the sandbox testing inbox via EMAIL_MAILTRAP_TOKEN, EMAIL_MAILTRAP_SANDBOX, EMAIL_MAILTRAP_INBOX_ID, and EMAIL_MAILTRAP_BULK.

* Cover @directus/env in changeset

* Sign CLA: add tsokolovs to contributors.yml

* Cover the mailtrap transport branch in tests

Re-import getMailer in the Mailtrap test so the module-scoped transporter cache doesn't short-circuit the new branch, ensuring it actually executes under coverage.

* Fix broken lockfile entry for mailtrap axios dependency

The mailtrap@4.6.1 snapshot referenced axios@1.16.1, which has no
corresponding entry in the lockfile. Point it at the already-resolved
axios@1.18.1 so that pnpm install --frozen-lockfile succeeds.

* More robust mailtrap checks and more meaningful tests

* move to optional deps

* prefer config from the EMAIL_MAILTRAP_ prefix

* improve testing

* eport mailer failures in the health check

* update mailtrap to latest

* reword changeset

* fmt

---------

Co-authored-by: daedalus <44623501+ComfortablyCoding@users.noreply.github.com>

* Fix `uploaded_on` not being set for TUS uploads (#28043)

* fix(api): set uploaded_on when TUS upload completes

* fix(api): handle return explicitly onResponseError

* changeset

* reuse payload for update and event

* reword changeset

* additional tests

* Update orange-crews-stick.md

* prefer implicit

---------

Co-authored-by: judda <44623501+ComfortablyCoding@users.noreply.github.com>

* Derive MCP flow operation layout from the operation graph (#28159)

* Derive MCP flow operation layout from the operation graph

* Stabilize layout order and fix stale required-positions prompt

* Only stamp placeholder positions on new nested operations

* Reject creates that provide only one position coordinate

* Stop display fields asking for inactive collections

* Keep m2a fields loading when a block type is turned off

* Stop relational fields asking for inactive collections

* render message stating collection is inactive

* switch to global translation function in template for consistency

* Check every step of a field path for inactive collections

* Fix type error when checking kanban group fields

* Stop the kanban layout querying an inactive groups collection

* show v-notice when khanban cannot render because the group collection is inactive

* handle inactive collection error and display as warning

* Skip inactive collections when resolving selected m2a items

* Fix m2a colon paths bypassing the inactive-collection field filter

* strip comments

---------

Co-authored-by: judda <44623501+ComfortablyCoding@users.noreply.github.com>
Co-authored-by: Brainslug <br41nslug@users.noreply.github.com>
Co-authored-by: Brainslug <tim@brainslug.nl>
Co-authored-by: Toms Sokolovs <50104066+tsokolovs@users.noreply.github.com>
Co-authored-by: kheiner <open-source-activity-for+directus@heiner.work>
Co-authored-by: Bryant Gillespie <bryant@directus.io>
ComfortablyCoding added a commit that referenced this pull request Sep 14, 2026
* fix disabled collection filtering

* changeset

* prettier

* Clanker e2e tests

* dont use global seed

* add comment

* Update .changeset/plenty-clouds-cry.md

Co-authored-by: judda <44623501+ComfortablyCoding@users.noreply.github.com>

* add colllection inactive error for REST

* fix admin check

* gracefuly handle inactive collections in relations

* gracefuly handle inactive collections in shares

* gracefuly handle inactive collections in versions

* use an array instead of set for schema sync over redis

* fixed outdated test expectation

* rename and add relational tests

* fix wildcard issue

* updated changeset

* use schema getter to guard inactive collections

* prettier

* fix disabled fields in snapshots and tests

* reverting collection-exists middleware and added tests

* refactor to separate middleware

* wire up controllers again

* keep inactive collections on the schema

* revert fields service changes

* wire up keeping the inactive collection on the schema

* throw the right error in the items service

* extract shared utility

* catch relational traversal in the ast

* add more e2e tests

* check inactive collections on the websocket

* add error test

* fix eslint warning

* Disable inactive collections across the Studio - Frontend (#28048)

* add shared util for collection status

* add inactive prop to field tree and field list

* disable inactive collections in collection picker interfaces

* disable inactive collections in the content module

* terminology change

* handle m2a fields

* handle table column selection

* handle kanban options

* insights

* changeset

* remove tooltip - would take additional work to get it working in all locations

* rehydrate fields and relations when status changes

* move insight panel checks into store

* fix disabled fields in snapshots and tests

* Revert "fix disabled fields in snapshots and tests"

This reverts commit 19308ca.

* Add `mailtrap` email transport support (#27873)

* Add Mailtrap email transport 🤖🤖🤖

Add a native `mailtrap` option for `EMAIL_TRANSPORT` using Mailtrap's official nodemailer transport. Supports live sending, the bulk stream, and the sandbox testing inbox via EMAIL_MAILTRAP_TOKEN, EMAIL_MAILTRAP_SANDBOX, EMAIL_MAILTRAP_INBOX_ID, and EMAIL_MAILTRAP_BULK.

* Cover @directus/env in changeset

* Sign CLA: add tsokolovs to contributors.yml

* Cover the mailtrap transport branch in tests

Re-import getMailer in the Mailtrap test so the module-scoped transporter cache doesn't short-circuit the new branch, ensuring it actually executes under coverage.

* Fix broken lockfile entry for mailtrap axios dependency

The mailtrap@4.6.1 snapshot referenced axios@1.16.1, which has no
corresponding entry in the lockfile. Point it at the already-resolved
axios@1.18.1 so that pnpm install --frozen-lockfile succeeds.

* More robust mailtrap checks and more meaningful tests

* move to optional deps

* prefer config from the EMAIL_MAILTRAP_ prefix

* improve testing

* eport mailer failures in the health check

* update mailtrap to latest

* reword changeset

* fmt

---------

Co-authored-by: daedalus <44623501+ComfortablyCoding@users.noreply.github.com>

* Fix `uploaded_on` not being set for TUS uploads (#28043)

* fix(api): set uploaded_on when TUS upload completes

* fix(api): handle return explicitly onResponseError

* changeset

* reuse payload for update and event

* reword changeset

* additional tests

* Update orange-crews-stick.md

* prefer implicit

---------

Co-authored-by: judda <44623501+ComfortablyCoding@users.noreply.github.com>

* Derive MCP flow operation layout from the operation graph (#28159)

* Derive MCP flow operation layout from the operation graph

* Stabilize layout order and fix stale required-positions prompt

* Only stamp placeholder positions on new nested operations

* Reject creates that provide only one position coordinate

* Stop display fields asking for inactive collections

* Keep m2a fields loading when a block type is turned off

* Stop relational fields asking for inactive collections

* render message stating collection is inactive

* switch to global translation function in template for consistency

* Check every step of a field path for inactive collections

* Fix type error when checking kanban group fields

* Stop the kanban layout querying an inactive groups collection

* show v-notice when khanban cannot render because the group collection is inactive

* handle inactive collection error and display as warning

* Skip inactive collections when resolving selected m2a items

* Fix m2a colon paths bypassing the inactive-collection field filter

* strip comments

---------

Co-authored-by: judda <44623501+ComfortablyCoding@users.noreply.github.com>
Co-authored-by: Brainslug <br41nslug@users.noreply.github.com>
Co-authored-by: Brainslug <tim@brainslug.nl>
Co-authored-by: Toms Sokolovs <50104066+tsokolovs@users.noreply.github.com>
Co-authored-by: kheiner <open-source-activity-for+directus@heiner.work>
Co-authored-by: Bryant Gillespie <bryant@directus.io>

* keep status on the schema for O(1) lookup

* fixed incorrect comment

* moved field to error options parameter

* add context comment

* prevent calling getRelation multiple times

* fix wildcard check

* override request based permission check

* rename to assertCollectionActive

* add schema check

* drop the redundant active check

* fix websocket uid in errors

* update changeset

* prettier

* fix unit test

* fix a2o error

* Update .changeset/empty-lilies-prove.md

Co-authored-by: judda <44623501+ComfortablyCoding@users.noreply.github.com>

* use getCollectionFromSchema

* Update packages/types/src/schema.ts

Co-authored-by: judda <44623501+ComfortablyCoding@users.noreply.github.com>

* use isAdmin

* make optional object

* add uid to errors

* use knownRelation

* just do system check

---------

Co-authored-by: judda <44623501+ComfortablyCoding@users.noreply.github.com>
Co-authored-by: Rob Luton <rob.luton@gmail.com>
Co-authored-by: Toms Sokolovs <50104066+tsokolovs@users.noreply.github.com>
Co-authored-by: kheiner <open-source-activity-for+directus@heiner.work>
Co-authored-by: Bryant Gillespie <bryant@directus.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants