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" );
			
		}
		
	};
		
}





See the description at the top of the class for a description on how to use this class or read this article on my blog:

http://linqed.eu/?p=45


JavaScript (Server)
Mark Leusink
December 7, 2011 3:11 AM
Rating
1,378

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.



4 comment(s)Login first to comment...
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;
}