diff --git a/changelog.d/3078-sending-email-guide-mailer-factory.fixed.md b/changelog.d/3078-sending-email-guide-mailer-factory.fixed.md new file mode 100644 index 0000000000..f6a5a51fe8 --- /dev/null +++ b/changelog.d/3078-sending-email-guide-mailer-factory.fixed.md @@ -0,0 +1 @@ +- Corrects the `app/mailers/` pattern in the Sending Email guide: replaces `new wheels.Controller().sendEmail()` — which throws on every engine due to missing `variables.params` — with `new wheels.Global().controller(name, params).sendEmail()`, the supported factory that fully initializes the instance (#3078). diff --git a/web/sites/guides/src/content/docs/v4-0-0/digging-deeper/sending-email.mdx b/web/sites/guides/src/content/docs/v4-0-0/digging-deeper/sending-email.mdx index 68f2ce1b0b..c45773014e 100644 --- a/web/sites/guides/src/content/docs/v4-0-0/digging-deeper/sending-email.mdx +++ b/web/sites/guides/src/content/docs/v4-0-0/digging-deeper/sending-email.mdx @@ -68,12 +68,16 @@ Every argument `cfmail` accepts is available here: `server`, `port`, `username`, ## Organize sends in `app/mailers/` -For anything beyond a one-liner, move the send call out of the controller and into a mailer component. Mailers are plain CFCs — no framework base class — that wrap `sendEmail()` behind a named method: +For anything beyond a one-liner, move the send call out of the controller and into a mailer component. Mailers are plain CFCs — no framework base class — that expose named methods wrapping `sendEmail()`. Because `sendEmail()` is a controller method, each mailer method creates a fully-initialized controller instance via `new wheels.Global().controller()`: ```cfm {test:compile} title="app/mailers/UserMailer.cfc" component { public any function sendWelcome(required any user) { - return new wheels.Controller().sendEmail( + local.mailer = new wheels.Global().controller( + name="UserMailer", + params={controller: "userMailer", action: "sendWelcome"} + ); + return local.mailer.sendEmail( template="/mailers/user/welcome", layout="/mailers/layout", from="no-reply@example.com", @@ -84,7 +88,11 @@ component { } public any function sendPasswordReset(required any user, required string token) { - return new wheels.Controller().sendEmail( + local.mailer = new wheels.Global().controller( + name="UserMailer", + params={controller: "userMailer", action: "sendPasswordReset"} + ); + return local.mailer.sendEmail( template="/mailers/user/password_reset", layout="/mailers/layout", from="no-reply@example.com", @@ -97,6 +105,10 @@ component { } ``` + + Put the views under `app/views/mailers/user/welcome.cfm` and `app/views/mailers/user/password_reset.cfm`. The leading slash on `template=` makes the path absolute (rooted at `app/views/`) so the mailer works regardless of which controller triggered it. Call it from any controller: