Create HTML mails in SSJS using MIME


/*
 * Class to generate HTML e-mail messages from SSJS
 * 
 * Author: Mark Leusink (m.leusink@gmail.com)
 * 
 * Version: 2012-11-20
 * 
 * History:
 * 2011-12-07	initial version
 * 2011-12-08	added support for inline images
 * 2011-12-19	fixed wrong method name in usage samples
 * 2012-11-20       added code to set JSON contents
                                changed setHeaderVal() to addValText() to set the sender with the correct encoding
 * 2013-01-08        small bug fix in _addAttachments() function 
 * 2013-11-20       option to set a different reply to address (thanks Ken Behrens!)
 * 2014-01-02       changed setHeaderVal() to addValText() for better Subject header encoding (thanks Egor Margineanu!)
 *  
 * Usage example (simple):
 * 
 * var mail = new HTMLMail();
 * mail.setTo( "m.leusink@gmail.com")
 * mail.setSubject("Your notification");
 * mail.addHTML("<b>Hello world</b>");
 * mail.send();
 * 
* Usage example (extended):
 * 
 * var mail = new HTMLMail();
 * mail.setTo( "m.leusink@gmail.com")
 * mail.setCC( ["user@domain.com", "anotheruser@domaino.com"] );
 * mail.setBB( "user3@domaino.com");
 * mail.setSubject("Your notification");
 * mail.addHTML("<h1>Hi!</h1>");
 * mail.addHTML("<table><tbody><tr><td>contents in a table here</td></tr></tbody></table>");
 * mail.addDocAttachment( "DC9126E84C59093FC1257953003C13E6", "jellyfish.jpg")
 * mail.addFileAttachment( "c:/temp/report.pdf");
 * mail.setSender("m.leusink@gmail.com", "Mark Leusink");
 * mail.send();
 */

var HTMLMail = function() {
	
	this._to = [];
	this._cc = [];
	this._bcc = [];
	
	this._fromEmail = null;
	this._fromName = null;
        this._reply = [];
	
	this._subject = "";
	this._contentsHTML = [];
	this._contentsText = [];
	this._contentsJSON = null;
	
	this._attachments = [];
		
	/*
	 * set "to", "CC" and/ or "BCC" addresses
	 * to = string or array of strings containing either e-mail addresses or Notes names
	 */
	this.setTo = function( to:String ) {
		if (typeof to === "string") { to = [to] };
		this._to = to;
	};
	this.setCC = function( to:String ) {
		if (typeof to === "string") { to = [to] };
		this._cc = to;		
	}
	this.setBCC = function( to:String ) {
		if (typeof to === "string") { to = [to] };
		this._bcc = to;		
	}
        this.setReply = function( to:String ) { 
             if (typeof to === "string") { to = [to] }; 
             this._reply = to; 
        } 
	
	//set the subject of the message
	this.setSubject = function( subject:String ) {
		this._subject = subject;
	}
	
	this.addHTML = function( content:String ) {
		this._contentsHTML.push(content);
		
		//create a plain text representation of the HTML contents:
		//remove all HTML tags and add a line break
		var plainText = content.replace( /<[a-zA-Z\/][^>]*>/g, "");
		this._contentsText.push( plainText + "\n" );		
	}
	
	this.addText = function( content:String ) {
		this._contentsText.push( content );
		
		//add html part by replacing all line breaks with a <br /> tag
		var htmlText = @ReplaceSubstring(content, @Char(13), "<br />");
		this._contentsHTML.push(htmlText);
	}

	this.setJSON = function( content:String ) {
		this._contentsJSON = content;
	}
	
	//set the sender of the message
	//fromEmail is required, fromName is optional
	this.setSender = function( fromEmail:String, fromName:String ) {
		if ( fromEmail.length > 0 ) {
			this._fromEmail = fromEmail;
			this._fromName = fromName;
		}
	}
	
	/*
	 * add an attachment on a document to the mail message
	 * 
	 * unid (string) = unid of the document in the current database that contains the file to be send
	 * fileName (string) = well... guess...
	 * inlineImage (boolean, defaults to false): if set to true, this image is marked as "inline" (used in the content)
	 */ 
	this.addDocAttachment = function( unid:String , fileName:String, inlineImage:boolean) {
		var contentId = @Unique().toLowerCase();
		if (typeof inlineImage=="undefined") { inlineImage = false; }
		this._attachments.push( { type : "document", unid: unid, fileName : fileName, contentId : contentId, inline : inlineImage } );
		return "cid:" + contentId;
	}
	
	/* 
	 * add an OS file to the mail message
	 * 
	 * path (string): path of the file on the server
	 * fileName (string): fileName of the file
	 * inlineImage (boolean, defaults to false): if set to true, this image is marked as "inline" (used in the content)
	 */ 
	this.addFileAttachment = function( path:String, fileName:String, inlineImage:boolean  ) {
		var contentId = @Unique().toLowerCase();
		if (typeof inlineImage=="undefined") { inlineImage = false; }
		this._attachments.push( { type : "file", path : path, fileName : fileName, contentId : contentId, inline : inlineImage } );
		return "cid:" + contentId;
	}
	
	this.send = function() {
	
		session.setConvertMime(false);
		
		var doc:NotesDocument = database.createDocument();
		doc.replaceItemValue("RecNoOutOfOffice", "1");		//no replies from out of office agents
		
		var mimeRoot:NotesMIMEEntity = doc.createMIMEEntity("Body");
		var mimeHeader:NotesMIMEHeader;
		
		//set to
		if (this._to.length>0) {
			mimeHeader = mimeRoot.createHeader("To");
			mimeHeader.setHeaderVal( this._to.join(","));
		}
		//set cc
		if (this._cc.length>0) {
			mimeHeader = mimeRoot.createHeader("CC");
			mimeHeader.setHeaderVal( this._cc.join(","));
		}
		//set bcc
		if (this._bcc.length>0) {
			mimeHeader = mimeRoot.createHeader("BCC");
			mimeHeader.setHeaderVal( this._bcc.join(","));
		}
		
		//set subject
		mimeHeader = mimeRoot.createHeader("Subject");
		mimeHeader.addValText(this._subject, "UTF-8");

		var mimeBoundary = doc.getUniversalID().toLowerCase();
		var stream:NotesStream;
		var mimeEntity:NotesMIMEEntity;
		
		//create text/alternative directive: text/plain and text/html part will be childs of this entity
		var mimeRootChild = mimeRoot.createChildEntity();
		mimeHeader = mimeRootChild.createHeader("Content-Type");
		mimeHeader.setHeaderVal( "multipart/alternative; boundary=\"" + mimeBoundary + "\"" );

		//create plain text part
		if (this._contentsText.length>0) {
			mimeEntity = mimeRootChild.createChildEntity();
			stream = session.createStream();
			stream.writeText(this._contentsText.join(""));
			mimeEntity.setContentFromText(stream, "text/plain; charset=\"UTF-8\"", NotesMIMEEntity.ENC_NONE);
			stream.close();
		}
		
		//create HTML part
		if (this._contentsHTML.length>0) {
			mimeEntity = mimeRootChild.createChildEntity();
			stream = session.createStream();
			stream.writeText(this._contentsHTML.join("\n"));
			mimeEntity.setContentFromText(stream, "text/html; charset=\"UTF-8\"", NotesMIMEEntity.ENC_NONE);
			stream.close();
		}
		//create embedded JSON part
		if (this._contentsJSON) {
			mimeEntity = mimeRootChild.createChildEntity();
			stream = session.createStream();
			var json = "{\"url\" : \"" + this._contentsJSON + "\"}\n";
			stream.writeText(json);
			mimeEntity.setContentFromText(stream, "application/embed+json; charset=\"UTF-8\"", NotesMIMEEntity.ENC_NONE);
			stream.close();
		}
		
		//add attachments
		this._addAttachments(mimeRoot);
		
		//set the sender
		this._setSender(mimeRoot);
		
		//send the e-mail	
		doc.send();
		
		session.setConvertMime(true);
	};

	//retrieve a file from a document that should be added to the message
	this._addAttachments = function( mimeRoot:NotesMIMEEntity ) {
		
		var streamFile:NotesStream = null;
		
		//process document attachments
		for (var i=0; i<this._attachments.length; i++) {
			var att = this._attachments[i];
			
			is = null;
			
			//get content type for file
			var contentType = "application/octet-stream";
			var extension = @LowerCase(@RightBack( att.fileName, "."));
			if (extension=="gif") {
				contentType = "image/gif";
			} else if (extension=="jpg" || extension=="jpeg") {
				contentType = "image/jpeg";
			} else if (extension=="png") {
				contentType = "image/png";
			}
			
			contentType += "; name=\"" + att.fileName + "\"";
			
			var eo:NotesEmbeddedObject = null;
			var is = null;
			
			try {
			
				if ( att.type.equals("document")) {
				
					//retrieve the document containing the attachment to send from the current database
					var docFile:NotesDocument = database.getDocumentByUNID( att.unid );
					if (null != docFile) {

						eo = docFile.getAttachment(att.fileName);
						is = eo.getInputStream();
					} 
				
				} else {
					
					is = new java.io.FileInputStream( att.path + att.fileName );
					
				}
				
				if (is != null) {
					
					var mimeChild = mimeRoot.createChildEntity();
					var mimeHeader = mimeChild.createHeader("Content-Disposition");
					
					if (att.inline) {
						mimeHeader.setHeaderVal("inline; filename=\"" + att.fileName + "\"");
					} else {
						mimeHeader.setHeaderVal("attachment; filename=\"" + att.fileName + "\"");
					}
					
					mimeHeader = mimeChild.createHeader("Content-ID");
					mimeHeader.setHeaderVal( "<" + att.contentId + ">" );

					streamFile = session.createStream();
					streamFile.setContents(is);
					mimeChild.setContentFromBytes(streamFile, contentType, NotesMIMEEntity.ENC_IDENTITY_BINARY);
				
				}
				
			
			} catch (e) {
				print("error while adding attachment: " + e.toString());
			} finally {
				if (is != null) { is.close(); }
				if (eo != null) { eo.recycle(); }
			}

		}	
	
	}
	
	//change the sender of an e-mail
	this._setSender = function( mimeRoot:NotesMIMEEntity ) {
		
		if (this._fromEmail==null) {
			return;
		}
		
		var mimeHeader:NotesMIMEHeader = null;
		
                //set reply address
                 if (this._reply.length>0) { 
                      mimeHeader = mimeRoot.createHeader("Reply-To"); 
                      mimeHeader.setHeaderVal( this._reply.join(",")); 
                 } else { 
                      mimeHeader = mimeRoot.createHeader("Reply-To"); 
                      mimeHeader.setHeaderVal(this._fromEmail);         
                 } 
		
		mimeHeader = mimeRoot.createHeader("Return-Path");
		mimeHeader.setHeaderVal(this._fromEmail);
		
		if (this._fromName==null) {
			
			mimeHeader = mimeRoot.createHeader("From");
			mimeHeader.setHeaderVal(this._fromEmail);
			mimeHeader = mimeRoot.createHeader("Sender");
			mimeHeader.setHeaderVal(this._fromEmail);
		
		} else {
			
			mimeHeader = mimeRoot.createHeader("From");
			mimeHeader.addValText( "\"" + this._fromName + "\" <" + this._fromEmail + ">", "UTF-8" );
			mimeHeader = mimeRoot.createHeader("Sender");
			mimeHeader.addValText( "\"" + this._fromName + "\" <" + this._fromEmail + ">", "UTF-8" );
			
		}
		
	};
		
}
All code submitted to OpenNTF XSnippets, whether submitted as a "Snippet" or in the body of a Comment, is provided under the Apache License Version 2.0. See Terms of Use for full details.
9 comment(s)Login first to comment...
Brian M Moore
(at 20:49 on 31.07.2018)
Gmail will reject messages sent from this unless you put in the "Inetfrom" header:

if (this._fromName==null) {

//Inet from allows this to go to Gmail addresses
mimeHeader = mimeRoot.createHeader("From");
mimeHeader.setHeaderVal(this._fromEmail);
mimeHeader = mimeRoot.createHeader("Sender");
mimeHeader.setHeaderVal(this._fromEmail);
mimeHeader = mimeRoot.createHeader("InetFrom");
mimeHeader.setHeaderVal(this._fromEmail);

} else {

mimeHeader = mimeRoot.createHeader("From");
mimeHeader.setHeaderVal( "\"" + this._fromName + "\" <" + this._fromEmail + ">" );
mimeHeader = mimeRoot.createHeader("Sender");
mimeHeader.setHeaderVal( "\"" + this._fromName + "\" <" + this._fromEmail + ">" );
mimeHeader = mimeRoot.createHeader("InetFrom");
mimeHeader.setHeaderVal( "\"" + this._fromName + "\" <" + this._fromEmail + ">" );

}
Sergio Georgini
(at 04:15 on 04.10.2016)
Great stuff
sagar pat
(at 11:56 on 11.04.2016)
How to add utf-8 encoding to the subject?
jeniffer homes
(at 09:56 on 18.12.2015)
could you add this to add embedded JSON for the embedded experience scenario
Carlo Mejia
(at 18:10 on 02.11.2015)
I modified the code so that I could get rid of the "Sent By:" which Domino security adds if you use the doc.send() method. I don't take credit for this, it's an old hack from 5 and 7 LotusScript days:

changed the createdocument line to:
var doc:NotesDocument = sessionAsSigner.getDatabase( session.getServerName(),"mail.box" ).createDocument();
added:
doc.replaceItemValue("recipients",this._to);
and changed doc.send() to:
doc.save();

This creates the document directly in your mail.box so that you don't need to use the send() function. When you save it, the router automatically send for you with the recipient as the from and replyTo.
Stephanus G Widjanarko
(at 03:07 on 09.06.2014)
I'm trying to send html email with body generated from richtext field

when I run it I get:
Script interpreter error, line=89, col=29: Unknown member 'replace' in Java class 'com.ibm.xsp.http.MimeMultipart'
at [xpHTMLMail].(

test mail
)

---
89: var plainText = content.replace( /<[a-zA-Z\/][^>]*>/g, "");
---

can someone help?
Cienki Bolek
(at 05:15 on 12.12.2013)
How add utf-8 encoding to subject?
Martin Rolph
(at 08:16 on 08.01.2013)
Great snippet.
We've used this but changed the send function to create using a sessionAsSigner to allow it to be used in pages before a user logs in (e.g. forgot password functionality)

this.send = function() {

sessionAsSigner.setConvertMime(false);

var doc:NotesDocument = sessionAsSigner.getCurrentDatabase().createDocument();
Niklas Heidloff
(at 02:11 on 20.11.2012)
Mark, could you add this to add embedded JSON for the embedded experience scenario? See my blog for more details.

//create embedded JSON part
if (this._contentsJSON) {
mimeEntity = mimeRootChild.createChildEntity();
stream = session.createStream();
var json = "{\"url\" : \"" + this._contentsJSON + "\"}\n";
stream.writeText(json);
mimeEntity.setContentFromText(stream, "application/embed+json; charset=\"UTF-8\"", NotesMIMEEntity.ENC_NONE);
stream.close();
}


this._contentsJSON;

this.setJSON = function( content:String ) {
this._contentsJSON = content;
}