Using webtasks to send emails with harp

tl;dr - This post shows how to send an email in a static website using webtask.io, Mailgun and harp

Mailgun setup

Mailgun labels itself as The Email Service For Developers, which basically allows you to send email via an API. And that’s all is needed. You can go and create an account. Mailgun immediately shows you some sample code using curl after logging in and verifying your account.

This is a curl call, you can use to trigger sending a new email, which we will use later on.

curl -s --user 'api:MAILGUN_API_KEY' \
    https://api.mailgun.net/v3/MAILGUN_API_URL \
    -F from='Your friendly spinscale.de SMTP form mailer <alex@reelsen.net>' \
    -F to='Alexander Reelsen <alex@reelsen.net>' \
    -F subject='Hello Alexander Reelsen' \
    -F text='Congratulations Alexander Reelsen, you just sent an email with Mailgun!  You are truly awesome!'

After the initial setup (without configuring a domain name) you already get 300 free emails per day - which is completely sufficient for testing. With the right configuration by adding DNS entries, you get 10000 free emails per month.

Setting up harp

Let’s create a test harp project and try to send an email. I dislike installing harp globally, thus I move a few things around, to run the boilerplate in the same directory I installed harp.

mkdir harp-mailgun-test
cd harp-mailgun-test
npm init -y
npm i --save-dev harp
npm i --save-dev bower
./node_modules/.bin/harp init test --boilerplate jvandemo/hb-bootstrap
mv test/* .
rm -fr test
./node_modules/.bin/bower install
./node_modules/.bin/harp server

Next step is to change public/index.jade to this

.container
    .jumbotron
        h1 Mail test
        button.btn.btn-primary.btn-lg(type="button", data-toggle="modal", data-target="#myModal") Mail Form

        form(method='post', action='https://webtask.it.auth0.com/api/run/wt-YOUR_EMAIL/email')
          div.modal.fade#myModal(tabindex="-1", role="dialog", aria-labelledby="myModalLabel")
            div.modal-dialog(role="document")
              div.modal-content
                div.modal-header
                  button.close(type="button", data-dismiss="modal", aria-label="Close")
                    span(aria-hidden="true") ×
                  h4.modal-title Modal title

                div.modal-body
                  div.form-group
                    label Email
                    input.form-control(type='email', placeHolder='email', name='email')
                  div.form-group
                    label Name
                    input.form-control(type='text', placeHolder='Your name', name='name')
                  div.form-group
                    label Message
                    textarea.form-control(type='text', placeHolder='Your name', name='message')

                div.modal-footer
                  button.btn.btn-default(type="button", data-dismiss="modal") Close
                  button.btn.btn-primary(type="submit") Send

Opening up your browser at http://localhost:9000 will show you the modal and allow you to send an email. However the endpoint is 404’ing right now, so let’s fix that and add a new endpoint that can be inserted above.

webtasks.io setup

There are dozens of solutions to trigger sending an email. You could go the lazy way and use flipmail, which is doing this automatically. However I cannot see who is behind this and SSL is not supported. The other obvious solution is AWS Lambda with API Gateway and SES, Google Cloud Functions or iron.io. In this example I chose webtask.io - which also features a fresh new CLI, which we are going to use.

Getting up and running is easy. Log into the webtask.io website, using one the available login methods like github. Then install the webtask CLI and initialize your user account

npm install wt-cli -g
wt init your@email.net

After this you can come up with a simple example, that just returns Hello

echo "module.exports = function (cb) {cb(null, 'Hello');}" > hello.js
wt create hello.js

wt ls
wt inspect hello

The output of wt ls shows you a URL, you cann with curl

# curl https://webtask.it.auth0.com/api/run/wt-YOUR_EMAIL-0/hello
"Hello"

Ok, so a basic call works, let’s wire the Mailgun API into a new event

Lets create an webtask/email.js file in our harp project, which sends a request to the mailgun API

var request = require('request');

/**
 * @param {secret} MAILGUN_API_KEY - Mailgun API KEY
 */
module.exports =  function (ctx, req, res) {

  var MAILGUN_API_KEY = ctx.data.MAILGUN_API_KEY
  if (!MAILGUN_API_KEY) return cb(new Error('Mailgun configuration is missing'))

  var text = ''
  for (var key in ctx.body) {
    text += key + ': ' + ctx.body[key] + '\n'
  }

  request.post('https://api.mailgun.net/v3/MAILGUN_URL')
  .auth('api', MAILGUN_API_KEY)
  .form( { from: 'Your friendly spinscale.de SMTP form mailer <alex@reelsen.net>',
             to: 'Alexander Reelsen <alex@reelsen.net>',
             subject: 'Form Mailer spinscale.de',
             text: text
  }).on('error', function(err) {
    res.writeHead(500)
    console.log('Error sending mail ' + err.toString())
    res.end('Error sending mail')
  }).on('response', function(response) {
    console.log('Got response ' + JSON.stringify(response))
    res.writeHead(301, {'Location': 'https://spinscale.de' })
    res.end()
  })
}

One important part here is to make sure, you do not put secrets into the code directly, but pass it via the context. You can configure the secret when uploading/creating the email task to webtask. Also, this follows the redirect-after-post pattern. You could also render out a ejs template on this site, but that does not make much sense on a third party site. The best way would be to submit an ajax form and return possible error messages, so that the user is never redirected away from the site.

wt create email.js -s MAILGUN_API_KEY=YOUR_SECRET_KEY

Testing time! Let’s run a curl call against our new endpoint!

curl -v -X POST https://webtask.it.auth0.com/api/run/wt-YOUR_EMAIL-0/email \
                --data 'email=user@example.org&message=my message&subject=My subject'

If this works, we are good to go. Start wt logs on a terminal and then open up localhost:9000 and try to sent an email, after you fixed the form submit URL in index.jade. Either you should get an email now, or check the terminal if an error was logged.

Things to keep in mind

Do not forget to version your webtasks when uploading (just put it in the same repo as your static site), so you can keep it in version control and update it. This also ensures that different applications can use different version of the tasks. This would also allow for different environments (production or development in harp) as well.

Drawbacks

  • Webtasks seems to be only TLS - which was fine for me
  • Initially I tried to use the HTTP referer to redirect back to the URL, but that seems to be dropped when the request data is handed over to the webtask. If you want to reuse the same webtask across several sites, you might configure referer, email meta data and access keys as secrets, so those can be handed over as parameters. Also, if you can use the referer, always make sure to only redirect to your own site by adding some extra checks.
  • The free tier of webtasks supports only a few requests per second, which is fine for emails
  • Integration with URLs might be easier with AWS and Amazon API Gateway, even though who are going full vendor lock-in here. Running a static website using S3 and AWS Lambda for this by using serverless will be my next try, together with submitting via AJAX to prevent the user from leaving the website.

That’s it. For anything else, drop me an email, and I am happy to update this!