This repository is a ColdBox module that provides fluent, protocol-agnostic email sending with support for multiple mail backends (CFMail, Mailgun, Postmark, File, InMemory), async queuing, token replacement, and a scheduler for queue processing.
Keep guidance short and actionable. Prefer small, verifiable edits and reference the real files below.
- Big picture
- ColdBox module: entrypoint and wiring in
ModuleConfig.cfc. - The core service
models/MailService.cfc(singleton, WireBox ID:MailService@cbmailservices) manages:- Registered mailer protocols via
variables.mailers(a struct of named protocol configurations). - A
ConcurrentLinkedQueue(models/ConcurrentLinkedQueue.cfc) for async mail queuing. - Token replacement in mail bodies using
@token@markers. - Announcement of
preMailSendandpostMailSendinterception points.
- Registered mailer protocols via
- The
Mailobject (models/Mail.cfc) is a fluent builder for email payloads:setTo(),setFrom(),setSubject(),setBody(),addMailPart(),addMailParam(), etc. All config lives invariables.config. - Sending happens via
mailService.send( mail )ormailService.sendAsync( mail )(returns a ColdBox Future). - The mixin helper
helpers/mixins.cfmexposesnewMail()in handlers/interceptors/models. - The
Mailabledelegate (models/delegates/Mailable.cfc) can be mixed into any object to inject the MailService and delegatenewMail().
- Source layout at a glance
- Core service:
models/MailService.cfc— protocol registry, newMail(), send()/sendAsync(), processQueue(), token replacement, configuration merging. - Mail payload:
models/Mail.cfc— fluent property setters, body/content/buildBody(), addMailPart()/addMailParam(), struct/JSON serialization, HTML/template rendering. - Protocols:
models/protocols/—CFMailProtocol.cfc(native cfmail tag),FileProtocol.cfc(writes .eml files),InMemoryProtocol.cfc(test-friendly, stores mail in array),MailgunProtocol.cfc(Mailgun REST API),NullProtocol.cfc(no-op),PostmarkProtocol.cfc(Postmark REST API). All extendmodels/AbstractProtocol.cfc. - AbstractProtocol:
models/AbstractProtocol.cfc— base class withpropertiesstruct,send()contract (must be implemented by protocols). - Queue:
models/ConcurrentLinkedQueue.cfc— thread-safe linked queue for async mail jobs. - Delegates:
models/delegates/Mailable.cfc— injectsMailService@cbmailservices, delegatesnewMail(). - Helpers:
helpers/mixins.cfm— exposesnewMail()mixin viathis.applicationHelper. - Config:
config/Scheduler.cfc— ColdBox scheduled taskMailQueuethat callsMailService.processQueue()every minute with no overlaps, gated byrunQueueTasksetting. - Module wiring:
ModuleConfig.cfcandbox.jsonfor metadata, dependencies, and scripts. - Build/CI:
build/Build.cfc,build/release.boxr.
- Developer workflows (how to run, test, build)
- Install deps and test harness:
box installat repo root. - Run local server:
box server start serverConfigFile="server-{engine}.json"where engine is one oflucee@6,adobe@2023,adobe@2025,boxlang@1,boxlang-cfml@1. - Start fake SMTP server (needed for integration tests):
docker compose -f test-harness/tests/resources/docker-compose.yml up --detach. - Run tests:
box testbox run --verbose(server must be running on port 60299). Or openhttp://localhost:60299/tests/runner.cfm. - Run build tasks:
box task run taskFile=build/Build.cfc. - Server configs available:
server-lucee@6.json,server-adobe@2023.json,server-adobe@2025.json,server-boxlang@1.json(no cfml compat),server-boxlang-cfml@1.json(with cfml compat).
- Patterns & conventions to follow
- MailService protocol lookup: When calling
newMail( mailer="xyz" ), the service looks upvariables.mailers[ mailer ]for the protocol class and settings. Default mailer is"default". - Protocol contract: Every protocol must implement a
send( required mail )method that receives theMailobject. Return value should include{ error : boolean, messages : [] }. - Mail payload flow:
mailService.newMail()→ configure Mail object fluently →mailService.send( mail )ormailService.sendAsync( mail )for queued sending. - Token replacement: Body text uses
@variableName@tokens. ThetokenMarkermodule setting controls the delimiter (default@). Tokens are replaced frommail.config.bodyTokens. - Module settings structure:
{ tokenMarker, defaultProtocol, mailers: { name: { class, properties } }, defaults: {}, runQueueTask: boolean }. Protocol classes resolve relative tocbmailservices.models.protocols.*namespace. - WireBox ID to preserve:
MailService@cbmailservices. - Interceptor points:
preMailSendandpostMailSendfire around everysend()call with the Mail payload in the event data.
- Events & integration points
preMailSend— announced before a mail is sent. The Mail object is in the interception data.postMailSend— announced after a mail is sent. The Mail object (with results populated) is in the interception data.- The scheduler
MailQueueprocesses queued mails every minute viaprocessQueue(). This can be disabled via therunQueueTaskmodule setting. - Custom interception points are registered in
ModuleConfig.cfc→interceptorSettings.customInterceptionPoints.
- Tests & test-harness specifics
- Test harness lives in
test-harness/. Contains a minimal ColdBox app. - Test specs:
test-harness/tests/specs/—AbstractProtocolTest.cfc,ConcurrentLinkedQueueTest.cfc,IntegrationTest.cfc,MailServiceTest.cfc,MailTest.cfc,protocols/(per-protocol tests). - Integration tests rely on a fake SMTP server via
test-harness/tests/resources/docker-compose.yml(MailHog on port 1025/8025). - Test resources:
test-harness/tests/resources/(docker-compose, mail templates, etc.). - Runner:
test-harness/tests/runner.cfmexpects a running CF server on port 60299.
- Small, high-value tasks for AI agents
- Add a new protocol by extending
AbstractProtocol, implementingsend(), and registering it in module settings. - Add a focused unit test for a protocol's
send()method intest-harness/tests/specs/protocols/. - When changing
MailService.send()or token replacement logic, updatetest-harness/tests/specs/MailServiceTest.cfc. - Preserve the
MailService@cbmailservicesWireBox ID and thesend( mail )/sendAsync( mail )signatures. - When adding new Mail properties, update
Mail.cfc'sonMissingMethodor add explicit getters/setters, and updateMailTest.cfc.
- Safety and CI
- CI uses
build/Build.cfcandbox.jsonscripts. Do not modify CI scripts without updatingbox.jsonandbuild/Build.cfc. - GitHub Actions workflows in
.github/workflows/:cron.yml,pr.yml,release.yml,snapshot.yml,tests.yml. .envfile for local environment overrides (see.env.exampleif present).- The
tests.ymlmatrix covers:boxlang-cfml@1,adobe@2023,adobe@2025,boxlang@1,lucee@6with ColdBox^8.0.0plus experimentalberuns for all engines.
- AI agent skills (
.agents/skills/)
- 71 skill definitions are available in
.agents/skills/, sourced from ortus-boxlang/skills and coldbox/skills GitHub repos. - BoxLang (29): language fundamentals, OOP, async, caching, config, database, security, testing, web, CLI, CommandBox, miniserver, interceptors, modules, files, zip, scheduled tasks, cfml-migration, java-integration, docbox, code-documenter, code-reviewer, best-practices, functional-programming, templating, application-descriptor, file-watchers, ortus-coding-standards.
- ColdBox (32): handler/interceptor/layout/module development, routing, REST API, event model, DI (WireBox), cache, async, logging, config, CLI, proxy, decorators, flash messaging, request context, AI integration, app layouts, view rendering, scheduled tasks, reviewer, documenter, testing (base classes, handler, http-methods, integration, interceptor, model), database-migrations, wirebox-aop.
- TestBox (10): BDD, xUnit, assertions, expectations, mockbox, cbmockdata, runners, reporters, listeners, testing-coverage, testing-fixtures.
- Skills lock file:
skills-lock.jsontracks source hashes for all installed skills.
If anything above is unclear or missing (protocol contract expectations, scheduler configuration, async patterns), tell me which area to expand and I will iterate.