You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Behavioral audit (P1 batch 2, p1-7-email) corrections to
digging-deeper/sending-email.mdx:
- Mailer pattern: replace new wheels.Controller().sendEmail(...) — throws
on every engine (no variables.params) — with the verified
controller(name, params) factory form; add a caution citing #3078.
- Attachments: examples used slash-containing paths that skip filePath
resolution; switch to bare filenames, document the separator rule and
that filePath expands relative to the web root (public/files/).
- sendEmail defaults: from/to/subject are engine-required parameters, so
configured defaults for them never apply; drop from= from both
set(functionName="sendEmail", ...) examples and document why.
- Background-job rationale: cfmail spools by default, so the request does
not normally wait for the SMTP handoff; reword the latency claim.
Signed-off-by: Peter Amiri <peter@alurium.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: web/sites/guides/src/content/docs/v4-0-0/digging-deeper/sending-email.mdx
+24-12Lines changed: 24 additions & 12 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -52,28 +52,33 @@ You don't want to repeat `server`, `username`, `password` on every `sendEmail()`
52
52
```cfm {test:compile}
53
53
// config/settings.cfm
54
54
set(functionName="sendEmail",
55
-
from="no-reply@example.com",
56
55
server="smtp.example.com",
57
56
port=587,
58
57
useTLS=true,
59
58
username="smtp-user",
60
59
password=env("SMTP_PASSWORD"));
61
60
```
62
61
63
-
Every argument `cfmail` accepts is available here: `server`, `port`, `username`, `password`, `useSSL`, `useTLS`, `from`, `replyto`, `failto`, `subject`, and more. Pulling secrets from `env()` keeps credentials out of source control.
62
+
Every optional argument `cfmail` accepts is available here: `server`, `port`, `username`, `password`, `useSSL`, `useTLS`, `replyto`, `failto`, and more. Pulling secrets from `env()` keeps credentials out of source control.
63
+
64
+
Don't set defaults for `from`, `to`, or `subject`, though — those three are *required* parameters of `sendEmail()`, and your CFML engine enforces them before Wheels gets a chance to apply configured defaults. A `from` default set here is never used; omitting `from=` at a call site throws a missing-parameter error on every engine even with the default configured. Always pass `from=` (and `to=`, `subject=`) explicitly.
64
65
65
66
<Asidetype="caution">
66
-
Wheels does not define a `mailerSettings` struct. SMTP connection arguments go through the `sendEmail` function defaults (above) or via your CFML engine's mail service configuration. Anything you can pass to `cfmail`, you can set as a `sendEmail` default.
67
+
Wheels does not define a `mailerSettings` struct. SMTP connection arguments go through the `sendEmail` function defaults (above) or via your CFML engine's mail service configuration. Anything you can pass to `cfmail`, you can set as a `sendEmail` default — except `from`, `to`, and `subject`, which are required at every call site as described above.
67
68
</Aside>
68
69
69
70
## Organize sends in `app/mailers/`
70
71
71
-
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:
72
+
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. Because `sendEmail()` is a controller function and needs a request-capable controller instance (one with a `params` struct), each mailer method obtains one through the `controller()` factory:
Don't instantiate the controller directly: `new wheels.Controller().sendEmail(...)` throws on every engine ("Component [wheels.Controller] has no accessible Member with name [PARAMS]" on Lucee, "Element PARAMS is undefined" on Adobe) because a bare controller instance has no `params` struct — the rendering pipeline dereferences it even for absolute template paths. The `controller(name, params)` factory builds a request-capable instance. Hardening `sendEmail()` for out-of-request senders is tracked in [#3078](https://github.com/wheels-dev/wheels/issues/3078).
111
+
</Aside>
112
+
100
113
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.
101
114
102
115
Call it from any controller:
@@ -130,7 +143,7 @@ Pass `onlyPath=false` to `linkTo()` inside email templates — a relative path l
130
143
131
144
## Send in a background job
132
145
133
-
`sendEmail()` runs synchronously. On a signup form that means the user's browser waits for SMTP to hand off the message before the redirect fires — adds hundreds of milliseconds on a good day, seconds when the mail server hiccups. Push the send into a job:
146
+
`sendEmail()` runs synchronously: the template render, message composition, and spool/handoff all happen inside the request before the redirect fires. By default both Lucee and Adobe spool the message to disk rather than waiting for the SMTP handoff, so the cost is usually the render and compose — but with spooling disabled, or a slow template, the user's browser eats that latency on every signup. Push the send into a job:
@@ -194,15 +207,15 @@ Most mail clients prefer the HTML part when both are present; the plain-text par
194
207
195
208
## Attachments
196
209
197
-
Pass `file=` (or its alias `files=`) with one or more paths. Paths without a slash are resolved relative to `application.wheels.filePath`(defaults to `files/`at the app root):
210
+
Pass `file=` (or its alias `files=`) with one or more paths. Paths without any directory separator (`/` or `\`) are resolved relative to the `filePath`setting — default `files`, which expands relative to the **web root**, so `public/files/`in the standard app template:
Absolute paths and URLs work too (`/var/app/pdfs/invoice.pdf`, `https://cdn.example.com/logo.png`) — anything `cfmailparam`'s `file` attribute accepts is valid.
236
+
A path that *does* contain a separator skips the `filePath` resolution entirely and reaches `cfmailparam` unchanged — so a relative path like `invoices/123.pdf` resolves against the JVM working directory at delivery time, which is almost never what you want. For files outside `public/files/`, build an absolute path yourself (e.g. `file="#ExpandPath('../storage/invoices/#user.id#.pdf')#"`). Absolute paths and URLs work too (`/var/app/pdfs/invoice.pdf`, `https://cdn.example.com/logo.png`) — anything `cfmailparam`'s `file` attribute accepts is valid.
0 commit comments