The previous lesson covered how a message actually gets delivered. This lesson covers cfmail itself: its full attribute set, cfmailparam and cfmailpart for attachments and multipart messages, and a real query-driven mail merge.
Learning Objectives
After completing this lesson, you'll be able to:
- Use cfmail's core attributes for recipients, formatting, and delivery.
- Attach a file and set a custom header with cfmailparam, and send a multipart message with cfmailpart.
- Send a personalized message per row of a query with cfmail's query/group attributes.
- Recognize where Lucee's mail tag genuinely differs from Adobe's cfmail.
cfmail's Core Attributes
| Attribute | Meaning |
|---|---|
| from / to / cc / bcc / replyTo | Sender and recipient addresses (comma-delimited for multiple) |
| subject | The message subject line |
| type | text/plain (default) or text/html |
| charset | Character encoding, defaults to utf-8 |
| server / port / username / password | Overrides the Administrator's SMTP configuration for this message |
| useSSL / useTLS | Secure connection options |
| priority | 1–5, or highest/high/normal/low/lowest |
| failTo | Address notified on a delivery failure |
| spoolEnable | Whether to spool (queue) the message rather than send it immediately |
| mimeAttach | A file path to attach, for a single simple attachment |
A Real Example: HTML Email With an Attachment and a Custom Header
<cfmail to="#customer.email#" from="orders@myshop.com" subject="Your Invoice" type="html">
<cfmailparam name="X-Order-Id" value="#order.id#">
<cfmailparam file="#invoicePath#" type="application/pdf">
<p>Hi #customer.firstName#, your invoice is attached.</p>
</cfmail>cfmailparam does two different jobs depending on its attributes: name/value sets a custom header, file (with an optional type) attaches a file.
A Real Example: cfmailpart for a Plain-Text Fallback
<cfmail to="#customer.email#" from="orders@myshop.com" subject="Your Invoice">
<cfmailpart type="text">
Hi #customer.firstName#, your invoice is attached. View it online at #invoiceUrl#.
</cfmailpart>
<cfmailpart type="html">
<p>Hi #customer.firstName#, your invoice is attached. <a href="#invoiceUrl#">View it online</a>.</p>
</cfmailpart>
</cfmail>When cfmailpart is used, all of the message's content must be inside one cfmailpart or another, a mail client that can't render HTML falls back to the text part automatically.
A Real Example: A Query-Driven Mail Merge
cfmail's query attribute sends one personalized message per row, looping the tag body the way cfoutput would loop over the same query.
<cfmail to="#email#" from="billing@myshop.com" subject="Payment Reminder" query="overdueAccounts">
Dear #firstName#,
Your account balance of #dollarFormat(balanceDue)# is overdue.
</cfmail>group (with groupCaseSensitive) sends one message per group of rows sharing a column value, instead of one per row, useful when a single customer has several line items that all belong in the same email.
Digital Signing and Encryption
cfmail also supports S/MIME digital signing (sign, keystore, keyAlias, keyPassword) and encryption (encrypt, recipientCert, encryptionAlgorithm, with DES_EDE3_CBC, RC2_CBC, and AES128/192/256_CBC available). These are real attributes for a genuine compliance or security requirement, not something most applications need to reach for by default.
Real Engine Differences: Lucee's mail Tag
| Aspect | Adobe cfmail | Lucee mail |
|---|---|---|
| Query-driven looping | query, group, groupCaseSensitive, startRow, maxRows all supported | Not implemented at all, query, group, groupCaseSensitive, startRow, and maxRows have no effect |
| Async/spooling | spoolEnable | async (spoolEnable is deprecated), sent via Lucee's Task Manager with automatic retry logic |
| Scheduled delivery | Not available | sendTime schedules a message for future delivery through the spooler |
| Proxy support | Not available | proxyServer/proxyPort/proxyUser/proxyPassword for sending through a proxy |
| Packaging | Built in | Since Lucee 7.1, mail functionality moved to a separate Mail extension (included in the full JAR distribution) |
The missing query/group support is a real, practical migration gotcha, a cfmail-based mail merge built on Adobe ColdFusion needs to be rewritten as an explicit loop on Lucee.
A Real Lucee Gotcha: TLS Version Restrictions
Lucee respects the JDK's own TLS restrictions. On Java 11.0.11+, 17+, and 21+, that means only TLSv1.2 and TLSv1.3 are available by default, an SMTP server that only offers an older protocol needs the mail.smtp.ssl.protocols system property overridden explicitly.
Common Beginner Mistakes
Confusing cfmailparam and cfmailpart
cfmailparam attaches a file or sets a custom header. cfmailpart provides an alternate format (plain text vs HTML) of the same message. They solve different problems.
Mixing content outside of cfmailpart when cfmailpart is used at all
Once any cfmailpart is present, all of the message's content needs to be inside one part or another, content outside them isn't included correctly.
Porting a query/group-based mail merge to Lucee unchanged
Lucee doesn't implement query, group, groupCaseSensitive, startRow, or maxRows on its mail tag at all, that logic needs to become an explicit loop instead.
Assuming an older SMTP server's TLS version will just work on newer Lucee/Java versions
Java 11.0.11+, 17+, and 21+ restrict TLS to 1.2/1.3 by default, an older protocol needs the mail.smtp.ssl.protocols system property overridden explicitly.
Best Practices
- Use cfmailpart for a real HTML/plain-text fallback rather than relying on every recipient's client rendering HTML.
- Reach for cfmailparam's file attribute for attachments rather than mimeAttach when more than one attachment or a custom header is needed.
- Check for Lucee's unimplemented query/group support before porting an Adobe-based mail merge.
- Reserve digital signing/encryption attributes for an actual compliance requirement, they add real operational complexity (keystores, certificates).
Interview Questions
What's the difference between cfmailparam and cfmailpart?
cfmailparam attaches a file or adds a custom header. cfmailpart provides an alternate content format (like plain text alongside HTML) of the same message body.
How would you send one personalized email per row of a query on Adobe ColdFusion, and why wouldn't the same approach work unchanged on Lucee?
cfmail's query attribute (with group for grouping related rows) loops the tag body once per row or group. Lucee doesn't implement query, group, groupCaseSensitive, startRow, or maxRows at all, that logic has to become an explicit loop instead.
What replaced spoolEnable on Lucee, and what does it add?
async, which sends through Lucee's Task Manager with automatic retry logic, spoolEnable is deprecated on Lucee in favor of it.
Why might an email to an older SMTP server suddenly fail after a Java upgrade?
Java 11.0.11+, 17+, and 21+ restrict TLS to versions 1.2/1.3 by default, an SMTP server offering only an older protocol needs mail.smtp.ssl.protocols overridden explicitly to keep working.
Summary
In this lesson, you used cfmail's core attributes, attached a file and set a custom header with cfmailparam, sent a multipart message with cfmailpart, built a real query-driven mail merge, and covered where Lucee's mail tag genuinely differs, its missing query/group support, async replacing spoolEnable, sendTime scheduling, and a real TLS version gotcha on newer Java.
What's Next?
The next lesson covers cfimap, reading and managing mail on an IMAP server directly from CFML.