Send OTP via sendgrid(email) API

Problem this snippet solves:

This is an example of how to send an OTP generated by the Big-IP via email using the sendgrid api.

Preparations

Preface

Most of the code and examples in this tutorial are just cut and paste from different places with slight modifications. I've decided to try it out after trying the twilio tutorial by Niels van Sluis: https://devcentral.f5.com/codeshare/send-an-one-time-password-otp-via-the-twilio-sms-gateway-1132.

Afterward, I wanted to better understand the irule LX architecture and usage, so I watched this great tutorial by Artiom: https://www.youtube.com/watch?v=7yRP2fPCxIs&index=1&list=PLRL802iBI7n-6y8_eNawyeUZ8E0YwWro8

And this is my first attempt to write a tutorial...

Configurations

  • In case you're configuring from scratch, you need to install sendgrid plugin, otherwise its already included in the tgz file.

To install the plugin:

cd /var/ilx/workspaces/Common/ilx_sendgrid/extensions/sendgrid/node_modules
npm install --save @sendgrid/mail

  • The index.js includes 2 options to send an email. If you want to use the first option. Simple email, then uncomment before //Use this method to send regular email and comment before //Use this method to send email with dynamic template

There are no additional configurations to be done.

  • If you want to send an email with a dynamic template(customized), the second(default) option then first you need to create the template via the sendgrid UI:

https://sendgrid.com/dynamic_templates

You can make different customizations to your email.

I've just used an example template which can be found on their github: https://github.com/sendgrid/email-templates/blob/master/paste-templates/password-reset.html and played with it just to understand how it works.

Here is an ugly modified template with the variables that will be presented in the email: https://github.com/alexnimo/F5/blob/master/iRules_LX/sendgrid/otp_template.html

More information can be found here:

https://sendgrid.com/docs/ui/sending-email/how-to-send-an-email-with-dynamic-transactional-templates/

https://sendgrid.com/docs/ui/sending-email/using-handlebars/


I've generated and used the code from the documentation:


https://dynamic-templates.api-docs.io/3.0/mail-send-with-dynamic-transactional-templates/v3-mail-send


If the default option is used(dynamic templates) then you must include the template id in the variable template_id



Hope I haven't forgot anything....

How to use this snippet:

Code variables:

var api_key = The api key generated in sendgrid

Everything else can be customized.

template_id = Template ID created in sendgrid.

Code :

'use strict';

// Import the f5-nodejs module.
var f5 = require('f5-nodejs');

// Import the sendgrid module.
const sgMail = require('@sendgrid/mail');

//Set API key
var api_key = "PASTE API KEY HERE"
sgMail.setApiKey(api_key);

// Create a new rpc server for listening to TCL iRule calls.
var ilx = new f5.ILXServer();

//Construct email
ilx.addMethod('ilx_sendmail', function(tclArgs, funcResp) {
    console.log("Method Invoked, Args:", tclArgs.params());
    
    const userEmail = tclArgs.params()[2];
    var emailText = 'Dear' + tclArgs.params()[0] + ' Your OTP is: ' + tclArgs.params()[1];
    
/*
//Use this method to send regular email
const msg = {
  to: userEmail,
  from: 'OTP_Admin@otptest.com',
  subject: 'Please See your OTP',
  text: emailText,
 // html: 'and easy to do anywhere, even with Node.js',
};

//Send email
sgMail.send(msg, (error, result) => {
    if (error) {
      console.log("Error sending email, the issue is:" + error)
      funcResp.reply(error);
    }
    else {
      return result
      funcResp.reply("email sent");
    }
  });
*/

//Use this method to send email with dynamic template

var http = require("https");

var options = {
  "method": "POST",
  "hostname": "api.sendgrid.com",
  "port": null,
  "path": "/v3/mail/send",
  "headers": {
    "authorization": "Bearer " + api_key,
    "content-type": "application/json"
  }
};

var req = http.request(options, function (res) {
  var chunks = [];

  res.on("data", function (chunk) {
    chunks.push(chunk);
  });

  res.on("end", function () {
    var body = Buffer.concat(chunks);
    console.log(body.toString());
  });
});


req.write(JSON.stringify({ personalizations: 
   [ { to: [ { email: userEmail , name: tclArgs.params()[0] } ],
       dynamic_template_data: 
        { user: tclArgs.params()[0],
          otp: tclArgs.params()[1],
          partysize: 4,
          english: true } } ],
  from: { email: 'OTP_Admin@otptest.com', name: 'OTP Admin' },
  reply_to: { email: 'OTP_Admin@otptest.com', name: 'OTP Admin' },
  template_id: 'PASTE TEMPLATE ID HERE' }));
req.end();
funcResp.reply("email sent");
});

ilx.listen();

Tested this on version:

13.0
Updated Jun 06, 2023
Version 2.0
  • Hi,

     

    First off, good job on this article.

     

    Did you actually test this with the simple email option enabled? I'm attempting to get it working on BIG-IP v 15.1.0, but I'm finding that your index.js TCL code for simple email doesn't seem to be working.

     

    I've tried both importing your workspace archive, and rolling the entire config by hand, same result.

     

    I started by created the iLX workspace from scratch, added a new extension, and finally installed the plugin using this command:

    npm install --save --no-bin-links @sendgrid/mail

    (BTW I was told by f5 support to always use the "--no-bin-links" option with npm so that no symbolic links create dependencies..there's a bug ID to require it on future versions). 

     

    I wish to use the first (simple) email option. But when I disable (comment out) the default email option, and copy/paste the code into index.js, the TCL interpreter throws a number of warnings..which I've played with for 8 hours now...can't figure it out.

     

    I've also tried just copying/using lines 1 - 44.

     

    Can you verify if you can get the following working???

    'use strict';
     
    // Import the f5-nodejs module.
    var f5 = require('f5-nodejs');
     
    // Import the sendgrid module.
    const sgMail = require('@sendgrid/mail');
     
    //Set API key
    var api_key = "PASTE API KEY HERE"
    sgMail.setApiKey(api_key);
     
    // Create a new rpc server for listening to TCL iRule calls.
    var ilx = new f5.ILXServer();
     
    //Construct email
    ilx.addMethod('ilx_sendmail', function(tclArgs, funcResp) {
        console.log("Method Invoked, Args:", tclArgs.params());
        
        const userEmail = tclArgs.params()[2];
        var emailText = 'Dear' + tclArgs.params()[0] + ' Your OTP is: ' + tclArgs.params()[1];
        
    // START OF METHOD 1: Use this method to send regular email
    const msg = {
      to: userEmail,
      from: 'OTP_Admin@otptest.com',
      subject: 'Please See your OTP',
      text: emailText,
     // html: '<strong>and easy to do anywhere, even with Node.js</strong>',
    };
     
    //Send email
    sgMail.send(msg, (error, result) => {
        if (error) {
          console.log("Error sending email, the issue is:" + error)
          funcResp.reply(error);
        }
        else {
          return result
          funcResp.reply("email sent");
        }
      });
    // END OF METHOD 1 
     
    /*
    // START OF METHOD 2: Use this method to send email with dynamic template
     
    var http = require("https");
     
    var options = {
      "method": "POST",
      "hostname": "api.sendgrid.com",
      "port": null,
      "path": "/v3/mail/send",
      "headers": {
        "authorization": "Bearer " + api_key,
        "content-type": "application/json"
      }
    };
     
    var req = http.request(options, function (res) {
      var chunks = [];
     
      res.on("data", function (chunk) {
        chunks.push(chunk);
      });
     
      res.on("end", function () {
        var body = Buffer.concat(chunks);
        console.log(body.toString());
      });
    });
     
     
    req.write(JSON.stringify({ personalizations: 
       [ { to: [ { email: userEmail , name: tclArgs.params()[0] } ],
           dynamic_template_data: 
            { user: tclArgs.params()[0],
              otp: tclArgs.params()[1],
              partysize: 4,
              english: true } } ],
      from: { email: 'OTP_Admin@otptest.com', name: 'OTP Admin' },
      reply_to: { email: 'OTP_Admin@otptest.com', name: 'OTP Admin' },
      template_id: 'PASTE TEMPLATE ID HERE' }));
    req.end();
    funcResp.reply("email sent");
    });
     
    ilx.listen();
    // END OF METHOD 2
    */

     

    Additionally, would we not require the ilx listener uncommented at the very end, even when using the simple email method?

    Otherwise how is your ilx_sendmail method gonna be called?

     

    ilx.listen();

     

     

    Cheers.