Skip to Content

The other day on Twitter there was a nice conversation with John Patterson around examples in XSA he would like to see.

https://twitter.com/thomas_jung/status/985841122202341376

For a few of the items he wanted, like bg-deploy, I was able to point to existing content that hopefully will help. But for some of the items there certainly were lacking examples.  One of them he mentioned was Email from XSA (pure node and xsjs).

This is a particular example or exercise that is often requested but I’m personally reluctant to do. It gets complicated fast because of the dependency upon an external SMTP server and the individual setup and authentication steps needed for the various different providers.  But doing a little research on the topic I found Ethereal – https://ethereal.email/

Ethereal is a great little fake SMTP service that is perfect for testing, education and demos.  It can be configured and used like any real SMTP service, but doesn’t actually send the emails out nor does it have any complicated setup scenarios. You can easily generate accounts from code (great for unit testing) or from their web site.

Now that I had an SMTP service and an account there I could focus on the SAP HANA XSA side of the equation. While many of the source code samples for JavaScript based SMTP online simply hardcode the connection parameters and authentication, let’s use the correct approach and maintain the information as a User Provided Service in XSA. While this can be done from the command line with the xs cups command, if you have the HANA 2.0 SPS 02 or higher version of XSA installed with the XSA admin cockpit, you can also create User-Provided Services from the Web UI. Using the authentication and configuration parameters provided by Ethereal when I created an account, I store this information in JSON format in the User Provided Service.

Next I need to add the User Provided Service as a resource in the mta.yaml file of my project in the SAP Web IDE for SAP HANA.

Now I’ve created two different modules (IE micro services) in my project. Both are JavaScript/Node.js modules but one will use the XSJS compatibility module and an XSJS service and the other will show how to use SMTP from a pure Node.js programming model. Both of these modules need a Requires entry for the ethereal.smtp resource we created in the previous step.

The complete mta.yaml should look like this:

ID: smtpExample
_schema-version: '2.0'
version: 0.0.1
modules:
  - name: xsjsSMTP
    type: nodejs
    path: xsjsSMTP
    provides:
      - name: xsjsSMTP_api
        properties:
          url: '${default-url}'
    requires:
      - name: ethereal.smtp
  - name: nodeSMTP
    type: nodejs
    path: nodeSMTP
    provides:
      - name: nodeSMTP_api
        properties:
          url: '${default-url}'
    requires:
      - name: ethereal.smtp
resources:
  - name: ethereal.smtp
    type: org.cloudfoundry.existing-service
    parameters:
      service-name: ethereal.smtp

Now we can focus on the coding in each of our modules.  Let’s start with the XSJS compatibility approach.  We already have a $.net.mail API in XSJS from the XSC days.  We mainly need to wire the User Provided Service settings into the XSJS bootstrap. In our xsjsSMTP module and the server.js, we have the startup of the XSJS framework. In here we can load the user provided service and load it into the options for the xsjs startup just like we would with the HANA or UAA service.

//Add SMTP
try {
	options = Object.assign(options, xsenv.getServices({mail: {"name" : "ethereal.smtp"}}));	
} catch (err) {
	console.log("[WARN]", err.message);
}

// start server
xsjs(options).listen(port);

That’s all there is to it. You could have existing XSJS services that use the $.net.Mail API and now they will work via whatever settings you supply in the User Provided Service.  For example this little snippet sends an Email from XSJS:

/*eslint no-console: 0, no-unused-vars: 0*/
"use strict";

//create email from JS Object and send
var mail = new $.net.Mail({
   sender: {address: "thomas.jung@hana.mail.com"},
   to: [{ name: "Thomas Jung", address: "dummy@mail.com", nameEncoding: "US-ASCII"}],
//   cc: ["cc1@sap.com", {address: "cc2@hana.mail.com"}],
//   bcc: [{ name: "Jonnie Doe", address: "jonnie.doe@hana.mail.com"}],
   subject: "Mail Test from XSJS Compatibility",
   subjectEncoding: "UTF-8",
   parts: [ new $.net.Mail.Part({
       type: $.net.Mail.Part.TYPE_TEXT,
       text: "The body of the mail from XSJS Compatibility.",
       contentType: "text/plain",
       encoding: "UTF-8"
   })]
});
var returnValue = mail.send();
var response = "MessageId = " + returnValue.messageId + ", final reply = " + returnValue.finalReply;

$.response.status = $.net.http.OK;
$.response.contentType = "text/html";
$.response.setBody(response);

The complete documentation on this $.net.Mail API can be found here: https://help.sap.com/http.svc/rc/3de842783af24336b6305a3c0223a369/2.0.02/en-US/$.net.Mail.html

Now let’s look at the pure Node.js programming model approach. Here we don’t have the $ APIs of XSJS, but we do have access to over half a million open source Node.js modules.  One of the most common for sending emails is the nodemailer module. We will use this module but feed it the same connection and authentication parameters from the User Provided Service.  We will even use the same @sap/xsenv module to load the User Provided Service details.

/*eslint no-console: 0, no-unused-vars: 0, no-undef:0*/
/*eslint-env node, es6 */

"use strict";
var xsenv = require("@sap/xsenv");
var port = process.env.PORT || 3000;
var server = require("http").createServer();
var express = require("express");

//logging
var logging = require("@sap/logging");
var appContext = logging.createAppContext();

//Initialize Express App for XS UAA and HDBEXT Middleware
var app = express();
app.use(logging.expressMiddleware(appContext));

app.get("/", (req, res) => {
	let options = {};
	//Add SMTP
	try {
		options = Object.assign(options, xsenv.getServices({
			mail: {
				"name": "ethereal.smtp"
			}
		}));
	} catch (err) {
		console.log("[WARN]", err.message);
	}
	const nodemailer = require("nodemailer");
	// create reusable transporter object using the default SMTP transport
	console.log(JSON.stringify(options.mail));
	let transporter = nodemailer.createTransport(options.mail);

	// setup email data with unicode symbols
	let mailOptions = {
		from: "\"Thomas Jung\" <thomas.jung@hana.mail.com", // sender address
		to: "Dummy <dummy@mail.com>", // list of receivers
		subject: "Mail Test from Pure Node.js using NodeMailer", // Subject line
		text: "The body of the mail from Pure Node.js using NodeMailer" // plain text body
			//        html: '<b>Hello world?</b>' // html body
	};

	// send mail with defined transport object
	transporter.sendMail(mailOptions, (error, info) => {
		if (error) {
			return console.log(error);
		}
		console.log("Message sent: %s", info.messageId);
		// Preview only available when sending through an Ethereal account
		console.log("Preview URL: %s", nodemailer.getTestMessageUrl(info));
		// Message sent: <b658f8ca-6296-ccf4-8306-87d57a0b4321@example.com>
		// Preview URL: https://ethereal.email/message/WaQKMgKddxQDoou...
		var output = "Preview URL: " + nodemailer.getTestMessageUrl(info);
		res.type("text/html").status(200).send(output);
	});
});

//Start the Server 
server.on("request", app);
server.listen(port, function() {
	console.info(`HTTP Server: ${server.address().port}`);
});

After running both new services, you can return to ethereal.email and view those emails and their technical details.  We don’t even have to use real email address for the to/from. This is a great way to test without having to bother an admin for SMTP details or even worse: trying to setup your own SMTP server.

If you are wanting to have a look at the complete source code project or clone it and play around with it on your own system, you can access it on github.com.

 

To report this post you need to login first.

1 Comment

You must be Logged on to comment or reply to a post.

Leave a Reply