A few weeks ago I blogged about creating a generic "forms handler" with OpenWhisk. The idea was that you could point any form at the action and it would gather up the form data and email the results. One of the issues with my action was that it only worked via a HTTP call to the end point. That made it fine for a simple Ajax call from JavaScript, but it missed one of the cooler features that most form handling services provide - the ability to redirect to another URL when done.

Turns out that support is now available in OpenWhisk as a new feature, "web actions." Fellow IBMer Rodric Rabbah wrote up a great post on the feature here: Serverless HTTP handlers with OpenWhisk. Also be sure to see his follow up here: Web Actions: Serverless Web Apps with OpenWhisk

I won't repeat the instructions, but essentially it comes down to a few changes:

  • First, when creating (or updating) your action, add --annotation web-export true
  • Second, figure out the URL. Your URL will be https://openwhisk.ng.bluemix.net/api/v1/experimental/web/NAMESPACE/PACKAGE/ACTION.TYPE. If you aren't using a package for your action, use default. The TYPE value is one of json, html, text, or http.

Basically, that's it. (There's more, but again, see Rodric's post). Given that my action was already mostly done, the modifications were pretty simple. I'll share the entire action at the end, but here are the mods I added.

First, I simply look for a new parameter, _next. If specified, it means that we want to redirect after processing the form. I made this optional to still support the Ajax version I created initially.

let next = '';
if(args['_next']) next = args['_next'];

Then I simply look for this when done:

if(next === '') {
	resolve({result:'Ok'});
} else {
	resolve({
		headers: { location: next},
		code: 302
	});
}

The final step was updating my action with the new code and that new flag to open it up as a web action. I modified my form to skip using JavaScript for the submission and just post to the new URL:

<form id="contactForm"
action = "https://openwhisk.ng.bluemix.net/api/v1/experimental/web/rcamden@us.ibm.com_My%20Space/default/sendmail.http" 
method="post">

<input type="hidden" name="_next" value="https://www.raymondcamden.com">

Note the use of HTTP as the content type. I'm not sending HTML back, but rather HTTP headers. If I wanted to created a dynamic "thank you" page I'd use the HTML extension instead. The _next value I used is just my blog, but in a real world situation it would be a page specifically thanking the user for their feedback.

And that's it. This is a great addition to the platform and I can't wait to kick the tires a bit more with it. Here's the complete source of the action, and as always, if you have any questions, comments, or suggestions, just add a comment below.

//SendGrid API Key
var SG_KEY = 'SG.secretkeysbetweenyouandme';

//people we are allowed to email
var RECIPS = ["raymondcamden@gmail.com","ray@camdenfamily.com"];

var helper = require('sendgrid').mail;

function main(args) {

	let from_email = new helper.Email(RECIPS[0]);
	let to_email = new helper.Email(RECIPS[0]);

	let next = '';
	if(args['_next']) next = args['_next'];

	/*
	Ok, so args will be our form data. Look for a few special item
	*/

	if(args["_from"]) {
		from_email = new helper.Email(args["_from"]);
	}

	if(args["_to"]) {
		if(RECIPS.indexOf(args["_to"]) === -1) {
			return {error:"Invalid _to address: "+args["_to"]};
		} else {
			to_email = new helper.Email(args["_to"]);
		}
	}

	let subject = 'Form Submission';

	if(args["_subject"]) {
		subject = args["_subject"];
	}

	/*
	Now loop through the rest of the args to create the form submission email.
	*/

	//todo: make date a bit prettier
	let date = new Date();
	let content = `
Form Submitted at ${date}
--------------------------------
`;

	for(let key in args) {
		//blanket ignore if _*
		if(key.indexOf("_") != 0) {
			content += `
${key}:			${args[key]}
`;
		}
	}

	let mailContent = new helper.Content('text/plain', content);
	let mail = new helper.Mail(from_email, subject, to_email, mailContent);
	let sg = require('sendgrid')(SG_KEY);

	return new Promise( (resolve, reject) => {

		var request = sg.emptyRequest({
			method: 'POST',
			path: '/v3/mail/send',
			body: mail.toJSON()
		});
		
		sg.API(request, function(error, response) {
			if(error) {
				console.log(error.response.body);
				reject({error:error.message}) 
			} else {
				console.log('got to good result, next is '+next);
				/*
				new logic - handle possible redirect
				*/
				if(next === '') {
					resolve({result:'Ok'});
				} else {
					resolve({
						headers: { location: next},
						code: 302
					});
				}
			}
		});

	});

}

exports.main = main;