FORGEBOX Enterprise 🚀 - Take your ColdFusion (CFML) Development to Modern Times! Learn More...

ColdBox Mail Services

v2.0.2+40 Public

Welcome to the ColdBox Mail Services => (cbmailservices)

Sending email doesn't have to be complicated or archaic. The ColdBox Mail Services (cbmailservices) module will allow you to send email in a fluent and abstracted way in multiple protocols for many environments in a single cohesive API. The supported protocols are:

  • CFMail - Traditional cfmail sending
  • File - Write emails to disk
  • InMemory - Store email mementos in an array. Perfect for testing.
  • Null - Ignores emails sent to it.
  • Postmark - Send via the PostMark API Service (https://postmarkapp.com/)

It also sports tons of useful features for mail sending:

  • Async Mail
  • Mail Queues
  • Mail merging of variables
  • Mail attachments, headers and parameters
  • View and Layout+View rendering for mail
  • Mail tracking
  • Multiple mailers
  • Success and Error callbacks
  • So Much More!

LICENSE

Apache License, Version 2.0.

SYSTEM REQUIREMENTS

  • Lucee 5+
  • ColdFusion 2018+

INSTRUCTIONS

Just drop into your modules folder or use the box-cli to install

box install cbmailservices

This registers a new mixin helper newMail() and the mail service via the WireBox ID of [email protected]. You will initiate a mail payload via the newMail() helper or the newMail() method in the MailService. This will give you access to the [email protected] object which you will fluently use to send mail.

Settings

You can configure the module by creating a cbmailservices key under the moduleSettings structure in the config/Coldbox.cfc file. Here you will configure all the different mailers, default protocol, default sending settings and so much more.

moduleSettings = {
    cbmailServices = {
        // The default token Marker Symbol
        tokenMarker     : "@",
        // Default protocol to use, it must be defined in the mailers configuration
        defaultProtocol : "default",
        // Here you can register one or many mailers by name
        mailers         : { 
            "default" : { class : "CFMail" },
            "files" : { class:"File", properties : { filePath : "/logs" },
            "postmark" : { class:"PostMark", properties : { apiKey : "234" } 
        },
        // The defaults for all mail config payloads and protocols
        defaults        : {
            from : "[email protected]",
            cc : "[email protected]"
        }
    }
}

By default, the mail services are configured to send mail via the cfmail tag using a mailer called default.

Mail Protocols

The mail services can send mail via different protocols. The available protocol aliases are:

  • CFMail
  • Null
  • InMemory
  • File
  • Postmark

Please note that some of the protocol have property requirements.

defaultProtocol : "default",
mailers : {
	// Default CFMail
	"default" : {
		class : "CFMail"
	},

	// FileProtocol
	"files" = {
		class = "File",
		properties = {
			filePath = "logs",
			autoExpand = true
		}
	},

	// NullProtocol
	"null" = {
		class = "Null",
		properties = {}
	},

	// InMemoryProtocol
	"memory" = {
		class = "InMemory",
		properties = {}
	},

	// PostMark
	"postmark" = {
		class = "Postmark",
		properties = {
			APIKey = "123"
		}
	};
}

Sending Mail

You can initiate a mail payload via the mixin helper (newMail()) or via the injected mail service's newMail() method. The arguments you pass into this method will be used to seed the payload with all the arguments passed to the cfmail tag or the chosen protocol settings. You can also pass an optional mailer argument which will override the default protocol to one of your liking.

Please note that the mixin helper can ONLY be used in handlers, interceptors, layouts and views. You will need to use the injection if you want to send mail from your models.

Helper Approach

// Mixin Helper Approach
newMail( 
	to         : "[email protected]",
	from       : "[email protected]",
	subject    : "Mail Services Rock",
	type       : "html", // Can be plain, html, or text
	bodyTokens : { 
		user    : "Luis", 
		product : "ColdBox", 
		link    : event.buildLink( 'home' )
	}
)
.setBody("
    <p>Dear @[email protected],</p>
    <p>Thank you for downloading @[email protected], have a great day!</p>
    <p><a href='@[email protected]'>@[email protected]</a></p> 
")
.send()
.onSuccess( function( result, mail ){
	// Process the success
})
.onError( function( result, mail ){
	// Process the error
});

MailService Approach

Use the WireBox ID of [email protected] to inject the service.

component{

	property name="mailService" inject="[email protected]";

	...

	function submitOrder( required order ){

		...

		variables.mailService
		.newMail( 
			to         : "[email protected]",
			from       : "[email protected]",
			subject    : "Mail Services Rock",
			type       : "html",
			bodyTokens : { 
				user    : "Luis", 
				product : "ColdBox", 
				link    : event.buildLink( 'home' )
			}
		)
		.setBody("
			<p>Dear @[email protected],</p>
			<p>Thank you for downloading @[email protected], have a great day!</p>
			<p><a href='@[email protected]'>@[email protected]</a></p> 
		")
		.send()
		.onSuccess( function( result, mail ){
			// Process the success
		})
		.onError( function( result, mail ){
			// Process the error
		});

	}

}

Callbacks

The mail payload allows you to register two callbacks for success and errors:

  • onSuccess( callback )
  • onError( callback )

Each callback argument is a function/closure/lambda that receives two arguments:

  • result : The result structure with at least two keys: { error :boolean, messages: array }
  • mail : The mail payload itself.
.send()
	.onSuccess( function( result, mail ){
		// Process the success
	})
	.onError( function( result, mail ){
		// Process the error
	});

Changing Mailers

You can easily change to use a specific mailer protocol by specifiying the mailer argument to the newMail() calls or by calling the setMailer( mailer ) method.

newMail( mailer : "files" ),,,


newMail()
	.setMailer( "files" )

Body Tokens

The mail service allows you to register a structure of tokens that can be replaced by key name on the body content for you. The tokens are demarcated by the tokenMarker setting which defaults to @.

@[email protected]

Before sending the mail, the service will replace all the tokens with the specific key names in your content and then send the mail. You can use the bodyTokens argument to the newMail() or configure() methods, or you can use the setBodyTokens() method.

// Via constructor
newMail( 
	to         : "[email protected]",
	from       : "[email protected]",
	subject    : "Mail Services Rock",
	type       : "html",
	bodyTokens : { 
		user    : "Luis", 
		product : "ColdBox", 
		link    : event.buildLink( 'home' )
	}
)
.setBody("
	<p>Dear @[email protected],</p>
	<p>Thank you for downloading @[email protected], have a great day!</p>
	<p><a href='@[email protected]'>@[email protected]</a></p> 
")
.send()

// Body Tokens Method
newMail( 
	to         : "[email protected]",
	subject    : "Mail Services Rock",
	type       : "html",
)
.setBodyTokens( { 
	user    : "Luis", 
	product : "ColdBox", 
	link    : event.buildLink( 'home' )
})
.setBody("
	<p>Dear @[email protected],</p>
	<p>Thank you for downloading @[email protected], have a great day!</p>
	<p><a href='@[email protected]'>@[email protected]</a></p> 
")
.send()

Rendering Views

You can also set the body of the email to be a view or a layout+view combination using the setView() method. Here is the method signature:

/**
 * Render or a view layout combination as the body for this email.  If you use this, the `type`
 * of the email will be set to `html` as well.  You can also bind the view/layout with
 * the args struct and use them accordingly.  You can also use body tokens that the service will
 * replace for you at runtime.
 *
 * @view The view to render as the body
 * @args The structure of arguments to bind the view/layout with
 * @module Optional, the module the view is located in
 * @layout Optional, If passed, we will render the view in this layout
 * @layoutModule Optional, If passed, the module the layout is in
 */
Mail function setView(
	required view,
	struct args = {},
	module      = "",
	layout,
	layoutModule = ""
)

Please note that you can bind your views and layotus with the args structure as well. You can also use the bodyTokens in your views. Then you can use it in your mail sending goodness:

newMail( 
	to         : "[email protected]",
	subject    : "Mail Services Rock",
	type       : "html",
)
.setBodyTokens( { 
	user    : "Luis", 
	product : "ColdBox", 
	link    : event.buildLink( 'home' )
})
.setView( view : "emails/newUser" )
.send()

newMail( 
	to         : "[email protected]",
	subject    : "Mail Services Rock",
	type       : "html",
)
.setView( view : "emails/newUser", layout : "emails" )
.send()

Mail Attachments

You can easily add mail attachments using mail params (next section) directly or our fancy helper method called addAttachments(). Here is our method signature:

/**
 * Add attachment(s) to this payload using a list or array of file locations
 *
 * @files A list or array of files to attach to this payload
 * @remove If true, ColdFusion removes attachment files (if any) after the mail is successfully delivered.
 */
Mail function addAttachments( required files, boolean remove = false )

The files argument can be a list of file locations or an array of file locations to send.

newMail(
	subject = "Hello",
	from    = "[email protected]",
	to      = "[email protected]",
	body    = "Here are your docs"
)
.addAttachments( "c:\temp\reports\report.pdf", true )
.addAttachments( expandpath( "/reports/anotherReport.pdf" ), true )
.addAttachments( [
	expandPath( "/logs/maillog.txt" )
	expandPath( "/logs/maillog2.txt" )
], true )
.send();

Mail Params

You can easily add mail parameters (cfmailparam) to a payload so you can attach headers or files to the message by using the addMailParam() method. Please see the https://cfdocs.org/cfmailparam cfmail param docs for more information.

Signature

/**
 * Attach a file or adss a header to the email payload
 * 
 * @contentID The Identifier for the attached file.
 * @disposition How the attached file is to be handled: attachment, inline
 * @file Attaches file to a message. Mutually exclusive with name argument.
 * @type The MIME media type for the attachment.
 * @name The name of the email header to attach. See https://cfdocs.org/cfmailparam. Mututally exclusive with file
 * @value The value of the header
 * @remove Tells ColdFusion to remove any attachments after sucdcesful mail delivery
 * @content Lets you send the contents of a ColdFusion variable as an attachment 
 */
Mail function addMailParam(
	contentID,
	disposition,
	file,
	type,
	name,
	value,
	boolean remove,
	content
)

Example

newMail()
	.configure(
		from    = "[email protected]",
		to      = "[email protected]",
		subject = "Mail With Params - Hello Luis"
	)
	.setBody( "Hello This is my great unit test" )
	.addMailParam(
		name  = "Disposition-Notification-To",
		value = "[email protected]"
	)
	.addMailParam( name = "Importance", value = "High" )
	.send();

Mail Parts

You can also add mail parts via the cfmailpart feature of cfmail (https://cfdocs.org/cfmailpart). This allows you to build multi-parted emails.

Signature

/**
 * Add a new mail part to this mail payload
 * 
 * @charset The charset of the part, defaults to utf-8
 * @type The valid mime type: text/plain or text/html
 * @wraptext Specifies the maximum line length, in characters of the mail text.
 * @type The MIME media type for the attachment.
 * @body The body of the email according to the type.
 */
Mail function addMailPart(
	charset = "utf-8",
	type,
	numeric wraptext,
	body
){

Example

newMail(
	from    = "[email protected]",
	to      = "[email protected]",
	subject = "Mail MultiPart No Params - Hello Luis"
)
.addMailPart(
	type = "text",
	body = "You are reading this message as plain text, because your mail reader does not handle it."
)
.addMailPart( type = "html", body = "<h1>This is the body of the message.</h1>" )
.send()

Mail Helper Methods

We have also registered several methods to help you when sending mail:

  • setReadReceipt( email ) - Set the read receipt email
  • setSendReceipt( email ) - Set the send receipt email
  • setHtml( body ) - Set a multi-part body for html
  • setText( body ) - Set a multi-part body for text
  • addAttachments( files, remove=false) - Easily add attachments
  • getMemento() - Get the entire mail settings for the payload
  • hasErrors():boolean - Verifies if there are any errors in the mailing
  • getResultMessages():array - Get's the array of messages of the sending of the mail
  • getResults():struct - Get the structure of the results of sending the mail

Mail Additional Info

The Mail object has some additional methods to allow you to pass additional information so protocols can leverage them:

mail.setAdditionalInfo( struct );
mail.getAdditionalInfo();

mail.setAdditionalInfoItem( key, value );
mail.getAdditionalInfoItem( key );

Async Mail

You can easily send mail asynchronously via the ColdBox Async Manager using the sendAsync() method. This will return to you a ColdBox Future object (https://coldbox.ortusbooks.com/digging-deeper/promises-async-programming)

newMail( 
	to         : "[email protected]",
	from       : "[email protected]",
	subject    : "Mail Services Rock",
	type       : "html",
	bodyTokens : { 
		user    : "Luis", 
		product : "ColdBox", 
		link    : event.buildLink( 'home' )
	}
)
.setBody("
    <p>Dear @[email protected],</p>
    <p>Thank you for downloading @[email protected], have a great day!</p>
    <p><a href='@[email protected]'>@[email protected]</a></p> 
")
.sendAsync()
.then( function( mail ){
	// Async pipeline that can process the mail once it is sent.

})

Mail Queue

You can also detach the mail and let the cbmailservices Mail Queue send it for you. The module's mail scheduler runs on a one-minute interval and will send any mail found in the processing queue. All you need to do is use the queue() method and be done!

var mailId = newMail( 
	to         : "[email protected]",
	from       : "[email protected]",
	subject    : "Mail Services Rock",
	type       : "html",
	bodyTokens : { 
		user    : "Luis", 
		product : "ColdBox", 
		link    : event.buildLink( 'home' )
	}
)
.setBody("
    <p>Dear @[email protected],</p>
    <p>Thank you for downloading @[email protected], have a great day!</p>
    <p><a href='@[email protected]'>@[email protected]</a></p> 
")
.queue();

The queue method will return back a task ID guid, which you can use to track the task down in your logs or via the Mail Service.

Custom Protocols

In order to create your own custom protocol you will create a CFC that inherits from cbmailservices.models.AbstractProtocol and make sure you implement the init() and send() method. Please see our docs for much more information: https://coldbox-mailservices.ortusbooks.com/advanced/building-protocols

Mail Events

The module will register two interception points. PreMailSend and PostMailSend. These interception points are useful to alter the mail object before it gets sent out, and/or perform any functions after the mail gets sent out. An example interceptor would be:

component extends="coldbox.system.Interceptor"{
    void function configure(){
        
    }

    boolean function preMailSend( event, interceptData, buffer, rc, prc ){
        var environment = getSetting('environment');
        var appName = getSetting('appName');
        var mail = interceptData.mail;
        var subject = mail.getSubject()

        if(environment eq 'development'){
            //change recipient if we are on development
            mail.setTo('[email protected]');  
            //prefix the subject if we are on development
            mail.setSubject('<DEV-#appName#> #subject#');
        }       

        return false;
    }

    boolean function postMailSend( event, interceptData, buffer, rc, prc ){
        if(interceptData.result.error eq true){
            //log mail failure here...
        }

        return false;
    }

}

You can find much more information here: https://coldbox-mailservices.ortusbooks.com/advanced/mail-events


Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp www.ortussolutions.com


HONOR GOES TO GOD ABOVE ALL

Because of His grace, this project exists. If you don't like this, then don't read it, its not for you.

"Therefore being justified by faith, we have peace with God through our Lord Jesus Christ: By whom also we have access by faith into this grace wherein we stand, and rejoice in hope of the glory of God. And not only so, but we glory in tribulations also: knowing that tribulation worketh patience; And patience, experience; and experience, hope: And hope maketh not ashamed; because the love of God is shed abroad in our hearts by the Holy Ghost which is given unto us. ." Romans 5:5

THE DAILY BREAD

"I am the way, and the truth, and the life; no one comes to the Father, but by me (JESUS)" Jn 14:1-12

Dependencies (0)


Dev Dependencies (0)


CHANGELOG

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.


[v2.0.2] => 2021-NOV-17

Fixed

  • Fixed getFileMimeType() so postmark attachments can work. Thanks to @garciadev

[v2.0.1] => 2021-NOV-12

Fixed

  • BOX-119 CBMailService - Setting the defaultProtocol in moduleSettings to something other than default gets ignored

[v2.0.0] => 2021-NOV-08

Changed

  • COMPATIBILITY : Settings are now using ColdBox 5 module approach of moduleSettings.cbmailservices instead of a root key element called cbmailservices. Make sure you update your settings and move them to modulesettings.cbmailservices
  • COMPATIBILITY : Changed all arguments called default to defaultValue to avoid ACF issues with the parser
  • Mail object config() renamed to configure()
  • The return results structure from the protocols errorArray has been renamed to just messages as it can contain warnings, information messages as well as error messages
  • PostmarkAPI result returns MessageID instead of message_id now.

Added

  • Ability for the mail payload to render the body from a view or a view/layout combination using the setView() method.
  • New ability to queue mail for sending using the async scheduler for the module and the new queue() method
  • New asynchronous mail sending via sendAsync() which returns a ColdBox Future
  • New mixin helper: newMail() so you can get access to send mails easily in handlers and interceptors.
  • In order to run and validate SMPT tests, we now use FakeSMTP as a container located in /test-harnes/tests/resources/docker-compose.yml. This will send mail to disk for us when testing smtp. If you want to run the tests on your machine, you will need to startup the container.
  • Every protocol now has a log LogBox logger configured object thanks to the AbstractProtocol.
  • Every protocol gets a name property now for a numan readable name thanks to the AbstractProtocol.
  • Mail object now can send itself via the new send() method which delegates to the service, but provides a nice sending DSL.
  • Mail object now has dynamic getters and setters for ALL configuration objects.
  • You can now use aliases to build out any of the core protocols: CFMail, File, InMemory, Null or Postmark instead of the full path.
  • You can now use a wirebox id or class path as the protocol class apart from the core protocols.
  • Added ability for the getProperty( property, defaultValue ) method on the abstract protocol to have a default value.
  • Migration to script of all core items.
  • Migration to github actions.
  • Adobe 2021 Support.
  • Modernization of all source code.

Removed

  • MailSettingsBean removed in favor of a more cohesive MailService
  • protocol setting removed in favor of multiple mailers approach and defaultProtocol usage. Please see docs.
  • Adobe ColdFusion 2016 Support.

[v1.6.0] => 2021-FEB-22

Addded

  • Two new protocols: NullProtocol, InMemoryProtocol
    • The NullProtocol ignores all calls to it.
    • The InMemoryProtocol stores mail mementos in an internal array. This can be useful for testing to check that mail was sent. It also includes a handle hasMessage method which takes a predicate callback and checks it against each sent mail. A reset method is included for use inside tests.
  • New CI updates and code quality systems
  • New updates for ColdBox 6

[v1.5.0] => 2019-NOV-12

New Features

  • Added a fromName to the Mail bean to track names due to some protocols allowing it
  • The module will register two interception points. PreMailSend and PostMailSend

Improvements

  • New module layout
  • Removed unecessary routing endpoint

Bugs

  • Var scoping issue

[v1.4.2]

  • Fixes incorrect argument collection nesting on protocol registration

[v1.4.1]

  • Auto create folder paths in FileProtocol if they do not exist

[v1.4.0]

  • Updated to use module templating
  • Proposed additionalInfo data struct for provider specific implementations. Added a couple of helper methods : https://github.com/coldbox-modules/cbox-mailservices/pull/5
  • Updated to leverage WireBox for object creations instead of internal new and createobjects

[v1.3.0]

  • Fix on date formatting on file protocol thanks to @elpete
  • Fix for type inclusion on the file protocol thanks to @elpete

[v1.2.0]

  • Travis integration
  • DocBox updates
  • Build process updates

[v1.1.0]

  • Updated build process
  • Updated readme and instructions

[v1.0.0]

  • Create first module version

 

$ box install cbmailservices

No collaborators yet.
     
  • {{ getFullDate("Nov 17 2021 09:11 PM GMT") }}
  • {{ getFullDate("Nov 17 2021 09:11 PM GMT") }}
  • 7,052
  • 5,039
  • 55,320