intro

Note MailBots version 4+ is tightly coupled with FollowUpThen to reflect our (and our user's!) priorities. FollowUpThen lifecycle hooks (ex: mailbot.onFutTriggerUser) allow a developer to add value to FollowUpThen (the original, proto-MailBot) by modifying behavior or injecting UI elements at different points in the followup lifecycle.

MailBots still exists a platform to extend FollowUpThen. We may re-release it as an independent system in the future. If you'd like to see that happen, or have any feedback in general, feel free to email help@humans.fut.io.

MailBots is a platform for creating bots, AIs and assistants that get things done right from your inbox. Read more at mailbots.com.

Quick Start

Go to mailbots.com and create a MailBot. The bot creation process sets up a working instance of this framework.

Next, let's tell our MailBot what to do when it receives an email:

var MailBot = require("mailbots");
var mailbot = new MailBot(); // assuming .env is set

// When someone emails: say-hi@my-bot.eml.bot, respond "Hello Human!"
mailbot.onCommand("say-hi", function(bot) {
  bot.webhook.quickReply("Hello Human!");
  bot.webhook.respond();
});

mailbot.listen();

say-hi@my-bot.eml.bot is an example of an "email command". Whatever is before the @ sign is a command to your bot to accomplish some task. Read more about email commands.

Docs

Tip: Use our reference guide to quickly look up helpers and method names.

How MailBots Work

A MailBot's purpose is to help someone get something done quickly, efficiently and without leaving their inbox.

Tasks

A unit of work accomplished by a MailBot is called a "task". Tasks can scheduled, edited or "completed". Tasks are always associated with a command.

Commands

MailBots always work in the context of commands while accomplishing tasks. A command can be thought of as the instruction for how to complete a particular task, or the purpose of the task. Email Commands (shown above) are one way to issue commands. Creating a task with the API also requires a command.

Triggering

A task (that carries out a command) can be created then wait for the perfect moment to notify a user. Timliness is a core feature of of a MailBot.

Architecture

When events happen in the MailBots platform (ex: an email is received), your MailBot receives webhooks. Your MailBot then gets some useful bit of work done (ex: enters data into a CRM) and responds with JSON that tells the MailBots platform what to do next (ex: send an email, store data, set a reminder, etc). JSON in, JSON out.

This library simplifies much of the above architecture, allowing you to simply create handler functions for certain events.

The webhook request and response can be viewed via the MailBots Sandbox.

Handlers

MailBots are composed of handlers – functions that run when certain events occur. For example: When the bot receives an email at this address, execute these actions

Skills

Handlers and other bot functionality can be packaged into "skills" – high-level abilities that can shared between bots. Here are some example skills:

  • Interact with a CRM
  • Parse natural language
  • Send a text message or Slack message
  • Render special UI elements

You can publish, share and install new skills from npm.

Examples:

Hello World

Create an email command that says hi.

var MailBotsApp = require("mailbots");
var app = new MailBotsApp();

mailbot.onCommand("hi", function(bot) {
  bot.webhook.quickReply("Hi back!");
  bot.webhook.respond();
});

mailbot.listen();

Set a Reminder

The first handler creates the reminder. The second one handles the reminder when it becomes due.

mailbot.onCommand("hi", function(bot) {
  // Schedule the task to trigger in 1 minute
  bot.webhook.setTriggerTime("1min");
  bot.webhook.respond();
});

// When a task with "hi" command triggers, run this
mailbot.onTrigger("hi", function(bot) {
  bot.webhook.quickReply("Hi 1 minute later!");
  bot.webhook.respond();
});

Handle Action-Emails

MailBots can render quick-action buttons (mailto links that map to executable code) that let users get things done without leaving their inbox. Read more about action emails.

// This first handler renders an email with an action-email button
 mailbot.onCommand("send-buttons", function(bot) {
    bot.webhook.sendEmail({
      to: bot.get('source.from')
      from: "MyBot",
      subject: bot.get('source.subject'),
      body: [

        // 👇 An email-action
        {
          type: "button",
          behavior: "action",
          text: "Press Me",
          action: 'say.hi',
          subject: "Just hit 'send'",
        }
      ]
    });
    bot.webhook.respond();
  };

  // This handler handles the email action
  mailbot.onAction('say.hi', function(bot) {
    bot.webhook.quickReply('hi');
    // Lots of useful things can be done here. Completing a todo item, adding notes to a CRM, etc.
    bot.webhook.respond();
  });

  mailbot.listen();

Install A Skill

Let's install a skill to that extracts the email message from previously quoted emails and signatures.

var mailbotsTalon = require("mailbots-talon");
mailbot.onCommand("hi", function(bot) {
  const emailWithoutSignature = mailbotsTalon.getEmail(bot);
  bot.quickReply(
    "Here is the email without the signature:" + emailWithoutSignature
  );
  bot.webhook.respond();
});

Handler Reference

Note: The first matching event handler ends the request, even if subsequent handlers also match, except where otherwise noted.

onCommand

mailbot.onCommand(command, handlerFn)

Handle when an email command is received. This also fires when a new task is created via the API (All tasks have a command to define their purpose or reason for existence.)

mailbot.onCommand("todo", function(bot) {
  //handle when a task is created with this command string
});

Regular expressions work with all handlers.

mailbot.onCommand(/todo.*/, function(bot) {
  //Handle todo.me@my-ext.eml.bot, todo.you@my-ext.eml.bot, todo.everyone@my-ext.eml.bot
});

onTrigger

mailbot.onTrigger(command, handlerFn)

Timeliness is a MailBot superpower. A user can schedule a task, then days, months or years later your bot can follow up at the exact right moment – scheduled by the user, or by another event. Read more about triggering.

Tasks with different commands may trigger differently. For example:

mailbot.onTrigger("todo.me", function(bot) {
  // Assigned to myself, so only remind me
});
mailbot.onTrigger("todo.assign", function(bot) {
  // Assigned to the person in the 'to' field, so remind them
});
mailbot.onTrigger("todo.crm", function(bot) {
  // Query CRM API, populate email followup with contact data
});

onAction

mailbot.onAction(action, handlerFn)

Handle when a user performs an action that relates to a task.

For example a user can send an action-email to accomplish an action without leaving their inbox (postponing a reminder, completing a todo item, logging a call, etc).

mailbot.onAction("complete", function(bot) {
  // Complete the related todo
});

MailBot Conversations

Set the reply-to address of a MailBot email to an action email to carry out a conversation with the user.

mailbot.onAction("assistant", function(bot) {
  // Use luis-ai middlweare to dtermine intent
  // Store conversation state
  // send-email whose reply-to is also "assistant"
});

onTaskViewed

mailbot.onTaskViewed(command, handlerFn)

Handle when a task is viewed in the MailBots Web UI, allowing a user to view and interact with a future MailBots email.

mailbot.onTaskViewed("todo.me", function(bot) {
  // Show a preview of the future email
});

Different task commands may render differently. For example a task with command todo.crm may query and render current CRM data within the preview.

onEvent

mailbot.onEvent(event, handlerFn)

Handle when the MailBot receives an inbound webhok from a 3rd party system about an external event. For example, a support ticket is created or a lead is added to a CRM.

Note: This action does not automatically create a MailBots Task. One can be created with mailbots-sdk

mailbot.onEvent("issue.created", async function(bot) {
  // Handle event, for example, create a MailBots Task.
  const mailBotsClient = MailBotsClient.fromBot(bot);
  await mailBotsClient.createTask({
    // Pre-authenticated API client
  });
  bot.webhook.respond({ webhook: { status: "success" } });
});

onSettingsViewed

mailbot.onSettingsViewed(handlerFn)

Handle when a user views this MailBot's settings.

Bots can build custom settings pages. These are rendered when a user views bot settings on the MailBots.com admin UI.

See the settings page reference.

The handler's only parameter is a callback function that responds with JSON to to render a settings form. The callback function is passed the bot object as usual.

Settings form JSON can be easily built using the settings helper functions.

Unlike the other handlers, every instance of this handler is called. (ie, all settings pages from all settings handlers are rendered).

NOTE: Do not call bot.webhook.respond() at the end of this particular request. MailBots' internals take care of compiling the JSON and responding.

// Render a settings field for the user to enter their first name
mailbot.onSettingsViewed(async function(bot) {
  const todoSettings = bot.webhook.settingsPage({
    namespace: "todo",
    title: "Todo Settings", // Page title
    menuTitle: "Todo" // Name of menu item
  });
  todoSettings.setUrlParams({ foo: "bar" }); // send a URL param (useful for showing dialogs)
  const urlParams = bot.get("url_params"); // retrieve URL params (see below)
  todoSettings.input({ name: "first_name", title: "First name" });
  todoSettings.buton({ type: "submit" });

  // Populate form values
  todoSettings.populate(bot.get("mailbot.stored_data.todo"));
  // Note bot.webhook.respond() is NOT called
});

URL parameters are passed through to the settings webhook. Use this to pass data into your settings when linking to it or displaying dialogs to the users (see above handler)

mailbot.onSettingsViewed(function(bot) {
  const settingsPage = bot.webhook.settingsPage({ namespace: "todo" });
  const urlParams = bot.get("url_params", {}); //defualts to empty object

  if (urlParams.linkInstructions)) {
    settings.text(`# Instructions to link your account!`);
  }
  // Note that there is no submit button. It's just an informational page.
});

You can also pass URL params via the urlParams key in the button form element (it must be type:submit);

// within a onSettingsViewed form as shown above
settings.button({
  submitText: "Save Notification Settings",
  type: "submit",
  urlParams: { saveSettings: 1 }
  // Tip: Pass through all URL current params, but use caution! (see note)
  // urlParams: {saveSettings: 1, ...bot.get("url_params", {})}
});

NOTE: URL parameters are an easy way to pass data into your bot settings, but keep this in mind while using URL params: Anyone can link a user to their settings page with anything in URL. Do not, for example, create a url like: /settings?delete_everything=true that deletes all their tasks. An unsuspecting user may arrive on their settings page from an external link, not see this in the URL and submit the form only find themselves without any data. Read more.

onSettingsSubmit

mailbot.onSettingsSubmit(handlerFn)

Handle when the user submits their settings.

To persist the newly saved data, it must be explicitly saved. Use bot.webhook.saveMailBotData() or bot.set("mailbot.stored_data") or API calls to do this.

Newly submitted values are available under settings.

Previously saved values are available under the mailbot.stored_data.

Every instance of this handler is called when any settings form is saved (similar to the above onSettingsViewed handler)

This handler is a good place begin an oauth handshake, set up data in other system or perform API calls to other systems.

mailbot.onSettingsSubmit(bot => {
  // assuming the same "todo" namespace as shown in the above examples
  const data = bot.get("settings.todo");

  // handler is fired any times settings are saved, even if its not our form
  if (!data) return;

  // perform API calls, validate connections, update external systems here

  // validate or modify data, then save it.
  bot.set("mailbot.store_data.todo", data);

  // This error would show to the user
  if (error) {
    bot.webhook.respond({
      webhook: {
        status: "error",
        message: "This is  a warning message"
      }
    });
  }
  return;
});

URL Params

URL params are useful for passing data into settings handlers, showing dialogs and more. We tried to preserve the mental model of URL parameters while working with settings forms, but but it does not always apply exactly.

In the onSettingsSubmit handler, you need to pass url_params parameters through the webhoook response:

// in onSettingsSubmit handler
bot.set("url_params", { show_success_dialog: "true" });

This is now accessible as a "url_param" in your onSettingsViewed handler. You will also see it as a URL param in the settings UI:

// in the onSettingsViewed handler, render a dialog with a button that dismisses the dialog
// const settingsForm set up earlier
const urlParams = bot.get("url_params", {});
if (urlParams.show_success_dialog) {
  settingsForm.text("# Success \nYour todo list has been set up!");
  settingsForm.button({ type: "submit",  text: "dismiss", urlParams({ dismissSuccess: true }) });
};

You can also set URL parameters using the button element which can trigger different actions based on the URL (Note the CSR caution above).

// back ino onSettingsSubmit handler.
if (bot.get("url_params.dissmissSuccess")) {
  bot.saveMailBotData("todo.show_success_dialog", null); // set to null to clear the value
}

Setting URL parameters in the onSettingsViewed hook requires a different method:

// onSettingsViewed
// Force the page to have specific URL params
settingsPage.setUrlParams({ key: "value" });

// Force the settings page to have no URL params
settingsPage.setUrlParams({});

on

mailbot.on(webhookEvent, handlerFn)

This is a generic handler for any webhook. It can be used to handle any inbound webhook – mainly ones that are not covered by the handlers above. (Of note: The handlers above are simply wrappers for this lower-level handler).

Example:

mailbot.on("mailbot.installed", function(bot) {
  // Handle when a MailBot is installed
  // Create task with MailBots SDK
  // bot.webhook.respond();
});

The first paramater can be:

  • A string that matches the webhook type. (ie. mailbot.installed)
  • A regular expression that matches on webhook type
  • A function that takes the incoming webhook as the only parameter and returns a boolean value indicating whether or not that webhook should be handled by that function handler.

The second parameter is the function handler that runs only if the matching condition (the first parameter) is met.

mailbot.on("mailbot.installed", function(bot) {
  // Handle when a MailBot is installed
  // Create a task from mailbots sdk
  // bot.webhook.respond();
});

Handling Errors

Ideally, error conditions are handled within the normal application flow. If something unexpected happens you can set up a custom error handler for adding logs, alerting or providing error messaging to users.

If you want to send the user your own error email, use the sendEmail method from the MailBots SDK. (Not all webhook responses send emails).

// define a custom error handler
app.setErrorHandler(function(error, bot) {
  // myCustomLogger.log(error);
  // send email with mailbots sdk
  // send a custom error email to user
  // Respond with 5xx status code to send the generic MailBots error email to the user
  // bot.response.status(500);

  bot.webhook.respond({
    status: "error",
    message: "A custom error message" // Shown to user if in the web UI.
  }); // A webhook response must be sent
});

FollowUpThen Lifecycle Hook Handlers

The below handlers are fired in response to FollowUpThen lifecycle events. Their response signature differs from the native MailBots handlers above since it is injecting UI elements and behavioral changes into the followup cycle.

The response style for all FollowUpThen Lifecycle Hook handlers is shown in onFutCreateUser below.

Available options for the response are described in the ISkillReturnValue interface within types.ts

onFutCreateUser

Modify the FollowUpThen user's confirmation email or modify the task object when a followup is created.

mailbot.onFutCreateUser(bot => {
  bod.webhook.addFutUiBlocks([
    {
      type: "text",
      text: "Text block"
    }
  ]);
});

// using native JSON response with TypeScript
mailbot.onFutCreateUser(bot => {
  const ISkillReturnValue: response = {
    futUiAddition: [
      {
        type: "text",
        text: "Text block"
      }
    ]
  };
  bot.responseJson = response;
  return;
});

onFutCreateNonUser

In rare cases (currently only when a task (-t) type followup is being created, a non-FUT user receives an email which can be modified using the above hook.

onFutPreviewUser

Render of UI elements into the preview email. (Shown when when a FUT user clicks "preview" to see what a reminder format will do). These elements are usually identical to the ones shown in the onFutViewUser and onFutTriggerUser hooks.

onFutPreviewNonUser

If a preview will trigger the a followup to a non-FUT user (ie, when used in "cc"), this allows allows for a preview of what this will look like.

onFutViewUser

Render UI elements when a user is viewing a followup.

onFutViewNonUser

If a FUT has emails sent to non-users, use this to render UI elements for only the non-user email.

onFutTriggerUser

Render UI elements that are only visible to the FollowUpThen user when a followup becomes due.

onFutTriggerNonUser

Render UI elements that are only for the non-user when a followup becomes due (if the followup format has a non-user email component).

onFutUpdate

Take action when a task is edited. This may involve creating, removing or unlinking a linked resource.

onFutAction

The UI elements above may trigger email-based actions (fired from email, or from the FUT UI). This handler allows for the handling of these actions.

The "bot" Object

The bot object passed to the handlers above contains useful helpers that make it easy to handle bot requests. See webhook helpers reference docs.

Setting Data Works By Shallow Merging Data is set by shallow merging. For example.

bot.webhook.setTaskData("my_namespace", { name: "Joe" });
bot.webhook.setTaskData("my_namespace", { key: "123" });
// task data is now
console.log(bot.webhook.responseJson);
// {my_namespace: { name: "Joe", key: "123" }}
bot.webhook.setTaskData("my_namespace", {
  name: "Joe",
  data: { value: "here" } // ⚠️ Overwritten (shallow merge)
});
bot.webhook.setTaskData("my_namespace", {
  data: { value2: "there" }
});
// task data is now
console.log(bot.webhook.responseJson);
// {my_namespace: { data: { value2: "there" } }}

Building Skills

"Skills" are sharable pieces of bot functionality. Skills can encapsulate everything they need (handlers, settings panels helper funcitons and UI elements) so they are ready for use installed. They are great for keeping your code organized and for sharing functionality with others.

Sharing Handlers

We could better organize our handlers in the above examples by grouping them into different files. For example:

// my-new-reminder-skill.js
module.exports.activate = function(mailbot) {
  // Handlers can go here
  // mailbot.onCommand...
};

Activate the skill's handlers like this (normally done in the top-level to ensure the skill is activated only once):

// In top-level app.js
const myNewReminderSkill = require("./my-new-skill")(mailbot);
myNewReminderSkill.activate(mailbot);

Isolating your handlers within skills is a great way to keep your project organized. The down-side is that the skill owns each request from beginning to end – not very flexible.

The next sections cover more granular and composable technique. for sharing functionality across multiple handlers or multiple MailBots.

Sharing the "one-bot function"

A "one-bot function" is a function that takes a single bot object (an instance of BotRequest) which, itself, contains the request / response state and utilities to alter the request. It is one instance of a bot request, hence, a one-bot function.

One-bot functions are called within handlers, allowing for a top-level handler to compose multiple one-bot functions to get something done.

// sayHi.js
// A simple "one-bot function"
function sayHi(bot) {
  bot.webhook.quickReply("hi");
}
module.exports = sayHi;
// in main app.js
const sayHi = require("./sayHi.js");
//...
mailbots.onCommand("hi", function(bot) {
  sayHi(bot);
  bot.webhook.respond();
});

Sharing UI Elements

Skills can also share UI elements.

By convention, UI functions that output UI start with render. For example, renderMemorizationControls.

  var memorizeSkill = require("mailbots-memorize");
  memorizeSkill.activate(mailbot); // activate handlers

  mailbot.onCommand("remember", function(bot) {
    bot.webhook.sendEmail({
      to: "you@email.com"
      from: "MailBots",
      subject: "Email Subject"
      body: [
        {
          type: 'title',
          text: 'A Title'
        },

        // Render JSON UI
        memorizeSkill.renderMemorizationControls(bot)
      ]
    })
    bot.webhook.respond();
  }

Handling Web Requests

The mailbots framework relies on Express.js for many of its internals. Your skill can access the internal Express app object at mailbot.app, allowing your skill to do anything that can be done with Express: authenticate other services, interact with APIs, respond to webhooks and render web pages.

Just like in handling routes in Express:

mailbot.app.get("/hi", function(req, res) {
  res.send("<h1>Hi http request!</h1>");
});

Middleware

Middlware can be exported and "used" by other skills. This is useful for implementing common functionality across multiple handlers.

// Export middleware to log everything
function logEverythingMiddleware(req, res, next) {
  const bot = res.locals.bot; // bot lives here
  const emailSubject = bot.get("source.subject");
  require("my-great-logger").log(`Got an email about ${emailSubject}`);
  next(); // <-- Don't forget this!
}
module.exports = logEverythingMiddleware;
};
// Using our middleware
const logEverythingMiddleware = require("./log-everything");

// Apply to all subsequent handlers
// Note: It does not apply to earlier handlers
mailbot.app.use(logEverythingMiddleware);

mailbot.onCommand("command-one", function(bot) {
  // everything is logged
});
mailbot.onCommand("command-two", function(bot) {
  // everything is logged
});
mailbot.onCommand("command-three", function(bot) {
  // everything is logged
});

Namespacing Conventions

To prevent conflicts and facilitate debugging, it is helpful to follow these conventions. For examples below, our skill is skill-name.

  • The module name

    # Example module name
    npm install mailbots-skill-name
    
  • Store data against the task or bot in a subject with key of the skill name using underscores instead of dashes.

    (task.stored_data = { "skill_name": { "key": "val" } })
  • Preface event names and actions with the skill name or an abbreviation (accounting for character limitations)

    mailbots.onAction("sn.do-action", bot => {});

    Use of "MailBot", "mailbot", "MailBots" and "bots"

    For clarity and to reduce ambiguity, follow these conventions:

    • User-Facing: The name of the platform (and company) is "MailBots". Always studly-cased. Always plural.
    • User-Facing: One email-based bot is a "MailBot". Always studly-cased.
    • Code: mailbot (all lowercased) is an acceptable variable name, but...
    • Code: When the first "M" is capitalized, always capitalize the second. Ex: createMailBot(). Mailbot (lowercase "b") never exists.
    • Code: bot is only used for the bot helper object passed to handler functions.

Installing Skills From npm

Skills can be installed from npm.

Here we will use mailbots-memorize, a skill that creates reminders for any task using spaced repetition, a memorization technique that increases the time between reminders as more reminders are sent.

In your cli, run:

npm install --save mailbots-memorize

In our app.js file we will create a handler that uses our newly installed skill.

// In main app.js file
var memorizeSkill = require("mailbots-memorize")(mailbot);

mailbot.onCommand("remember", function(bot) {
  memorizeSkill.memorizeTask(bot); //  ⬅ Tell bot to memorize your task
  bot.webhook.quickResponse("Memorizing!");
  bot.webhook.respond();
});

// Called each time the reminder is triggered
mailbot.onTrigger("remember", function(bot) {
  memorizeSkill.memorizeTask(bot); // ⬅ Invoke skill to continue memorizing
  bot.webhook.quickResponse("An email with decreasing frequency");
  bot.webhook.respond();
});

Skills With Side-Effects

Skills that accept the mailbots object may automatically alter requests, reply to webhooks or take other action automatically behind the scenes.

⚠️ When building sharable skills, use this "automatic" method with caution. Invoking functionality with one line can be both magical and confusingly opaque. The second example below shows an alternative that keeps code more self-documenting and testable.

// May automatically handle requets (ex, render settings pages, send emails)
var handleEverything = require("mailbots-handle-everything");
handleEverything(mailbot);
// or
require("handleEverything")(mailbot);

Alternatively, skills can export components (middleware, one-bot functions, etc) for you to explicitly use in your handlers.

👍 A more self-documenting and testable method.

// These types of skills offer components to your handlers
var {
  someMiddleware,
  renderSomething
} = require("mailbots-provide-components");

mailbots.app.use(someMiddleware);

mailbots.onCommand("foo", function(bot) {
  doSomething(bot);
});

Different approaches work well for different circumstances. For example, a bot analytics system would be well suited for automatic activation. A toolkit for integrating with a CRM, on the other hand, might make sense as an exported collection of useful middleware and functions.

Welcoming New Users

When a new user installs your MailBot, they are directed to a settings page with the welcome namespace. Render a custom welcome message for your user by creating a settings page that targets this namespace.

mailbot.onSettingsViewed(function(bot)  {
    const welcomeSettings = bot.webhook.settingsPage({
      namespace: "welcome", // MailBots sends new users to this namespace automatically
      menuTitle: "Welcome"
    });

    welcomeSettings.text(`
# Welcome To My MailBot
_Markdown_ works here.`);
)}

Note: While in dev_mode, the MailBot owner is instead redirected to the sandbox.

Your bot receives the mailbot.installed webhook which can be used to schedule a series of welcome emails to the user.

Connecting 3rd Party Services

Connecting a 3rd party system ( CRMs, todo lists, etc) usually requires an access token or similar information from the 3rd party. The easiest way to connect is to ask the user to copy / paste some generated key, token or URL into their settings page. A more friendly user experience is to use OAuth.

OAuth

Use MailBot's internal Express app to provide begin the OAuth process and receive the OAuth callback. Save information on the user's account using the setMailBotData method (as shown below).

Note: The user's Bearer token is saved as a cookie on your bot's URL when the user first authorizes your MailBot. This allows you to use the MailBots SDK when you send a user to a specific URL. (Keep in mind security XSRF implications if you are doing something sensitve here, like deleting an account). For example:

mailbot.app.get("/do-something-for-user", (req, res) => {
  // user's cookies, bearer token, etc are available here
  // redirect elsewhere
});

OAuth Example

Here is is an example OAuth flow, minus the provider-specific logic:

// 1. start OAuth handshake, set OAuth state, etc
mailbot.app.get("/connect_provider", (req, res) => {
  res.redirect(providerRedirectUri);
});

After the user authorizes with the 3rd party service (todo list, repo, CRM, etc) they are redirected to a callback URL, which does most the work, saves the access token, then redirects the user to their settings page with a success message.

// 2. Handle callback after user authorizes on 3rd party site
mailbot.app.get("/provider_callback", async (req, res) => {
  // verify state
  // exchange auth code for token from provider
  // authorize the client SDK and save user's new auth code
  const MailBotsClient = require("@mailbots/mailbots-sdk");
  const mbClient = new MailBotsClient();
  await mbClient.setAccessToken(req.cookies.access_token);
  res.redirect(`${res.locals.bot.config.mailbotSettingsUrl}/success`);
});

The work is performed only on your MailBot's url, but to a user, they just click an "Authorize" button on their MailBots settings, connect a 3rd party account and ended up back on their settings page. Service connected, minimal hassle.

Testing

Export a testable instance of your MailBot by calling mailbot.exportApp() instead of calling mailbot.listen(). Below is an example of testing the exported app with Supertest.

For a sample request (the ./_fixtures/task.created.json file below), fire a request and go to the sandbox. Click the "copy" icon that appears when you over over the the top-level key the request JSON.

Note: Set NODE_ENV to testing to disable webhook validation.

const request = require("supertest");
const mocha = require("mocha");
const expect = require("chai").expect;
const MailBotsApp = require("mailbots");
let mailbot; // re-instantiated before each test

// Utility function to send webhook to our app
function sendWebhook({ exportedApp, webhookJson }) {
  return request(exportedApp)
    .post("/webhooks")
    .set("Accept", "application/json")
    .send(webhookJson);
}

describe("integration tests", function() {
  beforeEach(function() {
    mailbot = new MailBotsApp({ clientId: "foo", clientSecret: "bar" });
  });

  it("responds correctly to a webhook", async function() {
    const mailbot = new MailBotsApp({ clientId: "foo", clientSecret: "bar" });

    // set up handlers
    mailbot.onCommand("memorize", bot => {
      bot.webhook.quickReply("I dig email");
      bot.webhook.respond();
    });

    const exportedApp = mailbot.exportApp();
    const webhookJson = require("./_fixtures/task.created.json"); // Copy request from Sandbox
    let { body } = await sendWebhook({ exportedApp, webhookJson });

    // Test our webhook response
    expect(body.send_messages[0].subject).to.equal("I dig email");
  });
});

Installing

The setup process on mailbots.com creates a pre-installed instance of your MailBot using Glitch.

For local development or production deployments:

1. Install

  • mkdir my-bot
  • npm init -y
  • npm install mailbots
  • touch app.js

2. Add Setup Code

const MailBot = require("mailbots");
const mailbot = new MailBot({
  clientId: "your_client_id",
  clientSecret: "your_secret",
  mailbotUrl: "http://your_bot_url"
});
// NOTE: The recommended way to set up your MailBot is to set
// CLIENT_ID, CLIENT_SECRET and MAILBOT_URL in env and use dotenv

mailbot.onCommand("hello", bot => {
  bot.webhook.quickReply("world");
  bot.webhook.respond();
});

mailbot.listen();
  1. Use ngrok to set up a public-facing url that MailBots.com can reach.

  2. Create a MailBot at mailbots.com. Click "Manual Setup" at the bottom and follow instructions from there.

Contributions

Contributions are welcome in the form of PRs and / or Github tickets for issues, bugs, ideas and feature requests. Please follow the MailBot naming conventions. We use ngrok to mock API requests, included in the test package here. This can be disabled to test against the live API (see package.json).

intro

Note MailBots version 4+ is tightly coupled with FollowUpThen to reflect our (and our user's!) priorities. FollowUpThen lifecycle hooks (ex: mailbot.onFutTriggerUser) allow a developer to add value to FollowUpThen (the original, proto-MailBot) by modifying behavior or injecting UI elements at different points in the followup lifecycle.

MailBots still exists a platform to extend FollowUpThen. We may re-release it as an independent system in the future. If you'd like to see that happen, or have any feedback in general, feel free to email help@humans.fut.io.

MailBots is a platform for creating bots, AIs and assistants that get things done right from your inbox. Read more at mailbots.com.

Quick Start

Go to mailbots.com and create a MailBot. The bot creation process sets up a working instance of this framework.

Next, let's tell our MailBot what to do when it receives an email:

var MailBot = require("mailbots");
var mailbot = new MailBot(); // assuming .env is set

// When someone emails: say-hi@my-bot.eml.bot, respond "Hello Human!"
mailbot.onCommand("say-hi", function(bot) {
  bot.webhook.quickReply("Hello Human!");
  bot.webhook.respond();
});

mailbot.listen();

say-hi@my-bot.eml.bot is an example of an "email command". Whatever is before the @ sign is a command to your bot to accomplish some task. Read more about email commands.

Docs

Tip: Use our reference guide to quickly look up helpers and method names.

How MailBots Work

A MailBot's purpose is to help someone get something done quickly, efficiently and without leaving their inbox.

Tasks

A unit of work accomplished by a MailBot is called a "task". Tasks can scheduled, edited or "completed". Tasks are always associated with a command.

Commands

MailBots always work in the context of commands while accomplishing tasks. A command can be thought of as the instruction for how to complete a particular task, or the purpose of the task. Email Commands (shown above) are one way to issue commands. Creating a task with the API also requires a command.

Triggering

A task (that carries out a command) can be created then wait for the perfect moment to notify a user. Timliness is a core feature of of a MailBot.

Architecture

When events happen in the MailBots platform (ex: an email is received), your MailBot receives webhooks. Your MailBot then gets some useful bit of work done (ex: enters data into a CRM) and responds with JSON that tells the MailBots platform what to do next (ex: send an email, store data, set a reminder, etc). JSON in, JSON out.

This library simplifies much of the above architecture, allowing you to simply create handler functions for certain events.

The webhook request and response can be viewed via the MailBots Sandbox.

Handlers

MailBots are composed of handlers – functions that run when certain events occur. For example: When the bot receives an email at this address, execute these actions

Skills

Handlers and other bot functionality can be packaged into "skills" – high-level abilities that can shared between bots. Here are some example skills:

  • Interact with a CRM
  • Parse natural language
  • Send a text message or Slack message
  • Render special UI elements

You can publish, share and install new skills from npm.

Examples:

Hello World

Create an email command that says hi.

var MailBotsApp = require("mailbots");
var app = new MailBotsApp();

mailbot.onCommand("hi", function(bot) {
  bot.webhook.quickReply("Hi back!");
  bot.webhook.respond();
});

mailbot.listen();

Set a Reminder

The first handler creates the reminder. The second one handles the reminder when it becomes due.

mailbot.onCommand("hi", function(bot) {
  // Schedule the task to trigger in 1 minute
  bot.webhook.setTriggerTime("1min");
  bot.webhook.respond();
});

// When a task with "hi" command triggers, run this
mailbot.onTrigger("hi", function(bot) {
  bot.webhook.quickReply("Hi 1 minute later!");
  bot.webhook.respond();
});

Handle Action-Emails

MailBots can render quick-action buttons (mailto links that map to executable code) that let users get things done without leaving their inbox. Read more about action emails.

// This first handler renders an email with an action-email button
 mailbot.onCommand("send-buttons", function(bot) {
    bot.webhook.sendEmail({
      to: bot.get('source.from')
      from: "MyBot",
      subject: bot.get('source.subject'),
      body: [

        // 👇 An email-action
        {
          type: "button",
          behavior: "action",
          text: "Press Me",
          action: 'say.hi',
          subject: "Just hit 'send'",
        }
      ]
    });
    bot.webhook.respond();
  };

  // This handler handles the email action
  mailbot.onAction('say.hi', function(bot) {
    bot.webhook.quickReply('hi');
    // Lots of useful things can be done here. Completing a todo item, adding notes to a CRM, etc.
    bot.webhook.respond();
  });

  mailbot.listen();

Install A Skill

Let's install a skill to that extracts the email message from previously quoted emails and signatures.

var mailbotsTalon = require("mailbots-talon");
mailbot.onCommand("hi", function(bot) {
  const emailWithoutSignature = mailbotsTalon.getEmail(bot);
  bot.quickReply(
    "Here is the email without the signature:" + emailWithoutSignature
  );
  bot.webhook.respond();
});

Handler Reference

Note: The first matching event handler ends the request, even if subsequent handlers also match, except where otherwise noted.

onCommand

mailbot.onCommand(command, handlerFn)

Handle when an email command is received. This also fires when a new task is created via the API (All tasks have a command to define their purpose or reason for existence.)

mailbot.onCommand("todo", function(bot) {
  //handle when a task is created with this command string
});

Regular expressions work with all handlers.

mailbot.onCommand(/todo.*/, function(bot) {
  //Handle todo.me@my-ext.eml.bot, todo.you@my-ext.eml.bot, todo.everyone@my-ext.eml.bot
});

onTrigger

mailbot.onTrigger(command, handlerFn)

Timeliness is a MailBot superpower. A user can schedule a task, then days, months or years later your bot can follow up at the exact right moment – scheduled by the user, or by another event. Read more about triggering.

Tasks with different commands may trigger differently. For example:

mailbot.onTrigger("todo.me", function(bot) {
  // Assigned to myself, so only remind me
});
mailbot.onTrigger("todo.assign", function(bot) {
  // Assigned to the person in the 'to' field, so remind them
});
mailbot.onTrigger("todo.crm", function(bot) {
  // Query CRM API, populate email followup with contact data
});

onAction

mailbot.onAction(action, handlerFn)

Handle when a user performs an action that relates to a task.

For example a user can send an action-email to accomplish an action without leaving their inbox (postponing a reminder, completing a todo item, logging a call, etc).

mailbot.onAction("complete", function(bot) {
  // Complete the related todo
});

MailBot Conversations

Set the reply-to address of a MailBot email to an action email to carry out a conversation with the user.

mailbot.onAction("assistant", function(bot) {
  // Use luis-ai middlweare to dtermine intent
  // Store conversation state
  // send-email whose reply-to is also "assistant"
});

onTaskViewed

mailbot.onTaskViewed(command, handlerFn)

Handle when a task is viewed in the MailBots Web UI, allowing a user to view and interact with a future MailBots email.

mailbot.onTaskViewed("todo.me", function(bot) {
  // Show a preview of the future email
});

Different task commands may render differently. For example a task with command todo.crm may query and render current CRM data within the preview.

onEvent

mailbot.onEvent(event, handlerFn)

Handle when the MailBot receives an inbound webhok from a 3rd party system about an external event. For example, a support ticket is created or a lead is added to a CRM.

Note: This action does not automatically create a MailBots Task. One can be created with mailbots-sdk

mailbot.onEvent("issue.created", async function(bot) {
  // Handle event, for example, create a MailBots Task.
  const mailBotsClient = MailBotsClient.fromBot(bot);
  await mailBotsClient.createTask({
    // Pre-authenticated API client
  });
  bot.webhook.respond({ webhook: { status: "success" } });
});

onSettingsViewed

mailbot.onSettingsViewed(handlerFn)

Handle when a user views this MailBot's settings.

Bots can build custom settings pages. These are rendered when a user views bot settings on the MailBots.com admin UI.

See the settings page reference.

The handler's only parameter is a callback function that responds with JSON to to render a settings form. The callback function is passed the bot object as usual.

Settings form JSON can be easily built using the settings helper functions.

Unlike the other handlers, every instance of this handler is called. (ie, all settings pages from all settings handlers are rendered).

NOTE: Do not call bot.webhook.respond() at the end of this particular request. MailBots' internals take care of compiling the JSON and responding.

// Render a settings field for the user to enter their first name
mailbot.onSettingsViewed(async function(bot) {
  const todoSettings = bot.webhook.settingsPage({
    namespace: "todo",
    title: "Todo Settings", // Page title
    menuTitle: "Todo" // Name of menu item
  });
  todoSettings.setUrlParams({ foo: "bar" }); // send a URL param (useful for showing dialogs)
  const urlParams = bot.get("url_params"); // retrieve URL params (see below)
  todoSettings.input({ name: "first_name", title: "First name" });
  todoSettings.buton({ type: "submit" });

  // Populate form values
  todoSettings.populate(bot.get("mailbot.stored_data.todo"));
  // Note bot.webhook.respond() is NOT called
});

URL parameters are passed through to the settings webhook. Use this to pass data into your settings when linking to it or displaying dialogs to the users (see above handler)

mailbot.onSettingsViewed(function(bot) {
  const settingsPage = bot.webhook.settingsPage({ namespace: "todo" });
  const urlParams = bot.get("url_params", {}); //defualts to empty object

  if (urlParams.linkInstructions)) {
    settings.text(`# Instructions to link your account!`);
  }
  // Note that there is no submit button. It's just an informational page.
});

You can also pass URL params via the urlParams key in the button form element (it must be type:submit);

// within a onSettingsViewed form as shown above
settings.button({
  submitText: "Save Notification Settings",
  type: "submit",
  urlParams: { saveSettings: 1 }
  // Tip: Pass through all URL current params, but use caution! (see note)
  // urlParams: {saveSettings: 1, ...bot.get("url_params", {})}
});

NOTE: URL parameters are an easy way to pass data into your bot settings, but keep this in mind while using URL params: Anyone can link a user to their settings page with anything in URL. Do not, for example, create a url like: /settings?delete_everything=true that deletes all their tasks. An unsuspecting user may arrive on their settings page from an external link, not see this in the URL and submit the form only find themselves without any data. Read more.

onSettingsSubmit

mailbot.onSettingsSubmit(handlerFn)

Handle when the user submits their settings.

To persist the newly saved data, it must be explicitly saved. Use bot.webhook.saveMailBotData() or bot.set("mailbot.stored_data") or API calls to do this.

Newly submitted values are available under settings.

Previously saved values are available under the mailbot.stored_data.

Every instance of this handler is called when any settings form is saved (similar to the above onSettingsViewed handler)

This handler is a good place begin an oauth handshake, set up data in other system or perform API calls to other systems.

mailbot.onSettingsSubmit(bot => {
  // assuming the same "todo" namespace as shown in the above examples
  const data = bot.get("settings.todo");

  // handler is fired any times settings are saved, even if its not our form
  if (!data) return;

  // perform API calls, validate connections, update external systems here

  // validate or modify data, then save it.
  bot.set("mailbot.store_data.todo", data);

  // This error would show to the user
  if (error) {
    bot.webhook.respond({
      webhook: {
        status: "error",
        message: "This is  a warning message"
      }
    });
  }
  return;
});

URL Params

URL params are useful for passing data into settings handlers, showing dialogs and more. We tried to preserve the mental model of URL parameters while working with settings forms, but but it does not always apply exactly.

In the onSettingsSubmit handler, you need to pass url_params parameters through the webhoook response:

// in onSettingsSubmit handler
bot.set("url_params", { show_success_dialog: "true" });

This is now accessible as a "url_param" in your onSettingsViewed handler. You will also see it as a URL param in the settings UI:

// in the onSettingsViewed handler, render a dialog with a button that dismisses the dialog
// const settingsForm set up earlier
const urlParams = bot.get("url_params", {});
if (urlParams.show_success_dialog) {
  settingsForm.text("# Success \nYour todo list has been set up!");
  settingsForm.button({ type: "submit",  text: "dismiss", urlParams({ dismissSuccess: true }) });
};

You can also set URL parameters using the button element which can trigger different actions based on the URL (Note the CSR caution above).

// back ino onSettingsSubmit handler.
if (bot.get("url_params.dissmissSuccess")) {
  bot.saveMailBotData("todo.show_success_dialog", null); // set to null to clear the value
}

Setting URL parameters in the onSettingsViewed hook requires a different method:

// onSettingsViewed
// Force the page to have specific URL params
settingsPage.setUrlParams({ key: "value" });

// Force the settings page to have no URL params
settingsPage.setUrlParams({});

on

mailbot.on(webhookEvent, handlerFn)

This is a generic handler for any webhook. It can be used to handle any inbound webhook – mainly ones that are not covered by the handlers above. (Of note: The handlers above are simply wrappers for this lower-level handler).

Example:

mailbot.on("mailbot.installed", function(bot) {
  // Handle when a MailBot is installed
  // Create task with MailBots SDK
  // bot.webhook.respond();
});

The first paramater can be:

  • A string that matches the webhook type. (ie. mailbot.installed)
  • A regular expression that matches on webhook type
  • A function that takes the incoming webhook as the only parameter and returns a boolean value indicating whether or not that webhook should be handled by that function handler.

The second parameter is the function handler that runs only if the matching condition (the first parameter) is met.

mailbot.on("mailbot.installed", function(bot) {
  // Handle when a MailBot is installed
  // Create a task from mailbots sdk
  // bot.webhook.respond();
});

Handling Errors

Ideally, error conditions are handled within the normal application flow. If something unexpected happens you can set up a custom error handler for adding logs, alerting or providing error messaging to users.

If you want to send the user your own error email, use the sendEmail method from the MailBots SDK. (Not all webhook responses send emails).

// define a custom error handler
app.setErrorHandler(function(error, bot) {
  // myCustomLogger.log(error);
  // send email with mailbots sdk
  // send a custom error email to user
  // Respond with 5xx status code to send the generic MailBots error email to the user
  // bot.response.status(500);

  bot.webhook.respond({
    status: "error",
    message: "A custom error message" // Shown to user if in the web UI.
  }); // A webhook response must be sent
});

FollowUpThen Lifecycle Hook Handlers

The below handlers are fired in response to FollowUpThen lifecycle events. Their response signature differs from the native MailBots handlers above since it is injecting UI elements and behavioral changes into the followup cycle.

The response style for all FollowUpThen Lifecycle Hook handlers is shown in onFutCreateUser below.

Available options for the response are described in the ISkillReturnValue interface within types.ts

onFutCreateUser

Modify the FollowUpThen user's confirmation email or modify the task object when a followup is created.

mailbot.onFutCreateUser(bot => {
  bod.webhook.addFutUiBlocks([
    {
      type: "text",
      text: "Text block"
    }
  ]);
});

// using native JSON response with TypeScript
mailbot.onFutCreateUser(bot => {
  const ISkillReturnValue: response = {
    futUiAddition: [
      {
        type: "text",
        text: "Text block"
      }
    ]
  };
  bot.responseJson = response;
  return;
});

onFutCreateNonUser

In rare cases (currently only when a task (-t) type followup is being created, a non-FUT user receives an email which can be modified using the above hook.

onFutPreviewUser

Render of UI elements into the preview email. (Shown when when a FUT user clicks "preview" to see what a reminder format will do). These elements are usually identical to the ones shown in the onFutViewUser and onFutTriggerUser hooks.

onFutPreviewNonUser

If a preview will trigger the a followup to a non-FUT user (ie, when used in "cc"), this allows allows for a preview of what this will look like.

onFutViewUser

Render UI elements when a user is viewing a followup.

onFutViewNonUser

If a FUT has emails sent to non-users, use this to render UI elements for only the non-user email.

onFutTriggerUser

Render UI elements that are only visible to the FollowUpThen user when a followup becomes due.

onFutTriggerNonUser

Render UI elements that are only for the non-user when a followup becomes due (if the followup format has a non-user email component).

onFutUpdate

Take action when a task is edited. This may involve creating, removing or unlinking a linked resource.

onFutAction

The UI elements above may trigger email-based actions (fired from email, or from the FUT UI). This handler allows for the handling of these actions.

The "bot" Object

The bot object passed to the handlers above contains useful helpers that make it easy to handle bot requests. See webhook helpers reference docs.

Setting Data Works By Shallow Merging Data is set by shallow merging. For example.

bot.webhook.setTaskData("my_namespace", { name: "Joe" });
bot.webhook.setTaskData("my_namespace", { key: "123" });
// task data is now
console.log(bot.webhook.responseJson);
// {my_namespace: { name: "Joe", key: "123" }}
bot.webhook.setTaskData("my_namespace", {
  name: "Joe",
  data: { value: "here" } // ⚠️ Overwritten (shallow merge)
});
bot.webhook.setTaskData("my_namespace", {
  data: { value2: "there" }
});
// task data is now
console.log(bot.webhook.responseJson);
// {my_namespace: { data: { value2: "there" } }}

Building Skills

"Skills" are sharable pieces of bot functionality. Skills can encapsulate everything they need (handlers, settings panels helper funcitons and UI elements) so they are ready for use installed. They are great for keeping your code organized and for sharing functionality with others.

Sharing Handlers

We could better organize our handlers in the above examples by grouping them into different files. For example:

// my-new-reminder-skill.js
module.exports.activate = function(mailbot) {
  // Handlers can go here
  // mailbot.onCommand...
};

Activate the skill's handlers like this (normally done in the top-level to ensure the skill is activated only once):

// In top-level app.js
const myNewReminderSkill = require("./my-new-skill")(mailbot);
myNewReminderSkill.activate(mailbot);

Isolating your handlers within skills is a great way to keep your project organized. The down-side is that the skill owns each request from beginning to end – not very flexible.

The next sections cover more granular and composable technique. for sharing functionality across multiple handlers or multiple MailBots.

Sharing the "one-bot function"

A "one-bot function" is a function that takes a single bot object (an instance of BotRequest) which, itself, contains the request / response state and utilities to alter the request. It is one instance of a bot request, hence, a one-bot function.

One-bot functions are called within handlers, allowing for a top-level handler to compose multiple one-bot functions to get something done.

// sayHi.js
// A simple "one-bot function"
function sayHi(bot) {
  bot.webhook.quickReply("hi");
}
module.exports = sayHi;
// in main app.js
const sayHi = require("./sayHi.js");
//...
mailbots.onCommand("hi", function(bot) {
  sayHi(bot);
  bot.webhook.respond();
});

Sharing UI Elements

Skills can also share UI elements.

By convention, UI functions that output UI start with render. For example, renderMemorizationControls.

  var memorizeSkill = require("mailbots-memorize");
  memorizeSkill.activate(mailbot); // activate handlers

  mailbot.onCommand("remember", function(bot) {
    bot.webhook.sendEmail({
      to: "you@email.com"
      from: "MailBots",
      subject: "Email Subject"
      body: [
        {
          type: 'title',
          text: 'A Title'
        },

        // Render JSON UI
        memorizeSkill.renderMemorizationControls(bot)
      ]
    })
    bot.webhook.respond();
  }

Handling Web Requests

The mailbots framework relies on Express.js for many of its internals. Your skill can access the internal Express app object at mailbot.app, allowing your skill to do anything that can be done with Express: authenticate other services, interact with APIs, respond to webhooks and render web pages.

Just like in handling routes in Express:

mailbot.app.get("/hi", function(req, res) {
  res.send("<h1>Hi http request!</h1>");
});

Middleware

Middlware can be exported and "used" by other skills. This is useful for implementing common functionality across multiple handlers.

// Export middleware to log everything
function logEverythingMiddleware(req, res, next) {
  const bot = res.locals.bot; // bot lives here
  const emailSubject = bot.get("source.subject");
  require("my-great-logger").log(`Got an email about ${emailSubject}`);
  next(); // <-- Don't forget this!
}
module.exports = logEverythingMiddleware;
};
// Using our middleware
const logEverythingMiddleware = require("./log-everything");

// Apply to all subsequent handlers
// Note: It does not apply to earlier handlers
mailbot.app.use(logEverythingMiddleware);

mailbot.onCommand("command-one", function(bot) {
  // everything is logged
});
mailbot.onCommand("command-two", function(bot) {
  // everything is logged
});
mailbot.onCommand("command-three", function(bot) {
  // everything is logged
});

Namespacing Conventions

To prevent conflicts and facilitate debugging, it is helpful to follow these conventions. For examples below, our skill is skill-name.

  • The module name

    # Example module name
    npm install mailbots-skill-name
    
  • Store data against the task or bot in a subject with key of the skill name using underscores instead of dashes.

    (task.stored_data = { "skill_name": { "key": "val" } })
  • Preface event names and actions with the skill name or an abbreviation (accounting for character limitations)

    mailbots.onAction("sn.do-action", bot => {});

    Use of "MailBot", "mailbot", "MailBots" and "bots"

    For clarity and to reduce ambiguity, follow these conventions:

    • User-Facing: The name of the platform (and company) is "MailBots". Always studly-cased. Always plural.
    • User-Facing: One email-based bot is a "MailBot". Always studly-cased.
    • Code: mailbot (all lowercased) is an acceptable variable name, but...
    • Code: When the first "M" is capitalized, always capitalize the second. Ex: createMailBot(). Mailbot (lowercase "b") never exists.
    • Code: bot is only used for the bot helper object passed to handler functions.

Installing Skills From npm

Skills can be installed from npm.

Here we will use mailbots-memorize, a skill that creates reminders for any task using spaced repetition, a memorization technique that increases the time between reminders as more reminders are sent.

In your cli, run:

npm install --save mailbots-memorize

In our app.js file we will create a handler that uses our newly installed skill.

// In main app.js file
var memorizeSkill = require("mailbots-memorize")(mailbot);

mailbot.onCommand("remember", function(bot) {
  memorizeSkill.memorizeTask(bot); //  ⬅ Tell bot to memorize your task
  bot.webhook.quickResponse("Memorizing!");
  bot.webhook.respond();
});

// Called each time the reminder is triggered
mailbot.onTrigger("remember", function(bot) {
  memorizeSkill.memorizeTask(bot); // ⬅ Invoke skill to continue memorizing
  bot.webhook.quickResponse("An email with decreasing frequency");
  bot.webhook.respond();
});

Skills With Side-Effects

Skills that accept the mailbots object may automatically alter requests, reply to webhooks or take other action automatically behind the scenes.

⚠️ When building sharable skills, use this "automatic" method with caution. Invoking functionality with one line can be both magical and confusingly opaque. The second example below shows an alternative that keeps code more self-documenting and testable.

// May automatically handle requets (ex, render settings pages, send emails)
var handleEverything = require("mailbots-handle-everything");
handleEverything(mailbot);
// or
require("handleEverything")(mailbot);

Alternatively, skills can export components (middleware, one-bot functions, etc) for you to explicitly use in your handlers.

👍 A more self-documenting and testable method.

// These types of skills offer components to your handlers
var {
  someMiddleware,
  renderSomething
} = require("mailbots-provide-components");

mailbots.app.use(someMiddleware);

mailbots.onCommand("foo", function(bot) {
  doSomething(bot);
});

Different approaches work well for different circumstances. For example, a bot analytics system would be well suited for automatic activation. A toolkit for integrating with a CRM, on the other hand, might make sense as an exported collection of useful middleware and functions.

Welcoming New Users

When a new user installs your MailBot, they are directed to a settings page with the welcome namespace. Render a custom welcome message for your user by creating a settings page that targets this namespace.

mailbot.onSettingsViewed(function(bot)  {
    const welcomeSettings = bot.webhook.settingsPage({
      namespace: "welcome", // MailBots sends new users to this namespace automatically
      menuTitle: "Welcome"
    });

    welcomeSettings.text(`
# Welcome To My MailBot
_Markdown_ works here.`);
)}

Note: While in dev_mode, the MailBot owner is instead redirected to the sandbox.

Your bot receives the mailbot.installed webhook which can be used to schedule a series of welcome emails to the user.

Connecting 3rd Party Services

Connecting a 3rd party system ( CRMs, todo lists, etc) usually requires an access token or similar information from the 3rd party. The easiest way to connect is to ask the user to copy / paste some generated key, token or URL into their settings page. A more friendly user experience is to use OAuth.

OAuth

Use MailBot's internal Express app to provide begin the OAuth process and receive the OAuth callback. Save information on the user's account using the setMailBotData method (as shown below).

Note: The user's Bearer token is saved as a cookie on your bot's URL when the user first authorizes your MailBot. This allows you to use the MailBots SDK when you send a user to a specific URL. (Keep in mind security XSRF implications if you are doing something sensitve here, like deleting an account). For example:

mailbot.app.get("/do-something-for-user", (req, res) => {
  // user's cookies, bearer token, etc are available here
  // redirect elsewhere
});

OAuth Example

Here is is an example OAuth flow, minus the provider-specific logic:

// 1. start OAuth handshake, set OAuth state, etc
mailbot.app.get("/connect_provider", (req, res) => {
  res.redirect(providerRedirectUri);
});

After the user authorizes with the 3rd party service (todo list, repo, CRM, etc) they are redirected to a callback URL, which does most the work, saves the access token, then redirects the user to their settings page with a success message.

// 2. Handle callback after user authorizes on 3rd party site
mailbot.app.get("/provider_callback", async (req, res) => {
  // verify state
  // exchange auth code for token from provider
  // authorize the client SDK and save user's new auth code
  const MailBotsClient = require("@mailbots/mailbots-sdk");
  const mbClient = new MailBotsClient();
  await mbClient.setAccessToken(req.cookies.access_token);
  res.redirect(`${res.locals.bot.config.mailbotSettingsUrl}/success`);
});

The work is performed only on your MailBot's url, but to a user, they just click an "Authorize" button on their MailBots settings, connect a 3rd party account and ended up back on their settings page. Service connected, minimal hassle.

Testing

Export a testable instance of your MailBot by calling mailbot.exportApp() instead of calling mailbot.listen(). Below is an example of testing the exported app with Supertest.

For a sample request (the ./_fixtures/task.created.json file below), fire a request and go to the sandbox. Click the "copy" icon that appears when you over over the the top-level key the request JSON.

Note: Set NODE_ENV to testing to disable webhook validation.

const request = require("supertest");
const mocha = require("mocha");
const expect = require("chai").expect;
const MailBotsApp = require("mailbots");
let mailbot; // re-instantiated before each test

// Utility function to send webhook to our app
function sendWebhook({ exportedApp, webhookJson }) {
  return request(exportedApp)
    .post("/webhooks")
    .set("Accept", "application/json")
    .send(webhookJson);
}

describe("integration tests", function() {
  beforeEach(function() {
    mailbot = new MailBotsApp({ clientId: "foo", clientSecret: "bar" });
  });

  it("responds correctly to a webhook", async function() {
    const mailbot = new MailBotsApp({ clientId: "foo", clientSecret: "bar" });

    // set up handlers
    mailbot.onCommand("memorize", bot => {
      bot.webhook.quickReply("I dig email");
      bot.webhook.respond();
    });

    const exportedApp = mailbot.exportApp();
    const webhookJson = require("./_fixtures/task.created.json"); // Copy request from Sandbox
    let { body } = await sendWebhook({ exportedApp, webhookJson });

    // Test our webhook response
    expect(body.send_messages[0].subject).to.equal("I dig email");
  });
});

Installing

The setup process on mailbots.com creates a pre-installed instance of your MailBot using Glitch.

For local development or production deployments:

1. Install

  • mkdir my-bot
  • npm init -y
  • npm install mailbots
  • touch app.js

2. Add Setup Code

const MailBot = require("mailbots");
const mailbot = new MailBot({
  clientId: "your_client_id",
  clientSecret: "your_secret",
  mailbotUrl: "http://your_bot_url"
});
// NOTE: The recommended way to set up your MailBot is to set
// CLIENT_ID, CLIENT_SECRET and MAILBOT_URL in env and use dotenv

mailbot.onCommand("hello", bot => {
  bot.webhook.quickReply("world");
  bot.webhook.respond();
});

mailbot.listen();
  1. Use ngrok to set up a public-facing url that MailBots.com can reach.

  2. Create a MailBot at mailbots.com. Click "Manual Setup" at the bottom and follow instructions from there.

Contributions

Contributions are welcome in the form of PRs and / or Github tickets for issues, bugs, ideas and feature requests. Please follow the MailBot naming conventions. We use ngrok to mock API requests, included in the test package here. This can be disabled to test against the live API (see package.json).

bot

"Bot" is the helpful object that are given to event handlers. It contains information about the request and lots helpful methods to complete the request.

Bots have utilities to handle webhooks WebHookHelpers

mailbot.onCommand(function(bot) { // <-- This is the "bot"
  bot.webhook.quickReply("hi");
  bot.webhook.respond();
});

One of the webhook helpers lets you create a settings form. This is its own class: SettingsPage

  const mySettingsPage = bot.webhook.settingsPage({
  namespace: "todo",
  title: "ToDo Settings",
  menuTitle: "To Do"
  });

bot

"Bot" is the helpful object that are given to event handlers. It contains information about the request and lots helpful methods to complete the request.

Bots have utilities to handle webhooks WebHookHelpers

mailbot.onCommand(function(bot) { // <-- This is the "bot"
  bot.webhook.quickReply("hi");
  bot.webhook.respond();
});

One of the webhook helpers lets you create a settings form. This is its own class: SettingsPage

  const mySettingsPage = bot.webhook.settingsPage({
  namespace: "todo",
  title: "ToDo Settings",
  menuTitle: "To Do"
  });

mailbots

A MailBot is an instance of the MailBots class. This class exposes handlers that are used to respond to specific events. (See readme.md for more).

  var MailBots = require("mailbots");
  var mailbot = new MailBots({
    clientId: "foo",
    clientSecret: "bar"
  });

  mailbot.onCommand("hi", function(bot) {
    bot.webhook.quickReply("Hi back!");
    bot.webhook.respond();
  });

  mailbot.listen();

mailbots

A MailBot is an instance of the MailBots class. This class exposes handlers that are used to respond to specific events. (See readme.md for more).

  var MailBots = require("mailbots");
  var mailbot = new MailBots({
    clientId: "foo",
    clientSecret: "bar"
  });

  mailbot.onCommand("hi", function(bot) {
    bot.webhook.quickReply("Hi back!");
    bot.webhook.respond();
  });

  mailbot.listen();

constructor

Class constructor.

constructor(instanceConfig: any)
Parameters
instanceConfig (any)

listen

Loads final skills and start http server. Anything posted to /webhooks route is automatically handled. Other routes must be created as usual with the Express App object. For example: mailbots.app.get("my-route/", (req, res) => {});

listen()

exportApp

Export app for automated testing

exportApp()

loadSkill

Load MailBots skills from a directory, non-recursively. This can be called more than once to load skills in order. Skills loaded this method are preceeded by loadFirstCoreSkills, succeeded by loadLastCoreSkills. This can also receive a path to a file.

loadSkill(skill: any, config: object, skillPath: string)
Deprecated: This is deprecated.
Parameters
skill (any)
config (object) optional skill configuration object
skillPath (string) path to skills directory

on

Add handler for a given webhook event. Executes only the first handler for a matching event. The first parameter can be either a string (the named webhook event) or a function that is passed the webhook and returns true or false to indicate if the handler should be run. Example: controller.on('task.created', (bot, req, res) => { }); Example: controller.on((webhook) => webhook.event === 'task.created', cb)

on(triggerCondition: any, cb: function, opts: any, event: (string | function | RegExp))
Parameters
triggerCondition (any)
cb (function) Handler function
opts (any)
event ((string | function | RegExp)) A webhook event string (ex: task.created). Or a function receives the webhook as a param, which returns a boolean value.

onCommand

Captures only 'task.created' events where the command string matches

onCommand(commandSearch: (string | RegExp), cb: any)
Parameters
commandSearch ((string | RegExp))
cb (any)

onTrigger

Captures only 'task.triggered' events where the command string matches

onTrigger(commandSearch: (string | RegExp), cb: any)
Parameters
commandSearch ((string | RegExp))
cb (any)

onEvent

Captures only 'mailbot.event_received' events Note: This "Event" refers to the 3rd party webhooks that are posted to the MailBot.

onEvent(eventSearch: (string | RegExp), cb: any)
Parameters
eventSearch ((string | RegExp))
cb (any)

onAction

Captures only 'task.action_received' events where the action string matches

onAction(actionSearch: (string | RegExp), cb: any)
Parameters
actionSearch ((string | RegExp))
cb (any)

onTaskViewed

Captures only 'task.viewed' events where the command string matches

onTaskViewed(commandSearch: (string | RexExp), cb: any)
Parameters
commandSearch ((string | RexExp))
cb (any)

onSettingsViewed

Handle webhook that fires when user is viewing MailBot. ALL onSettingsViewed handlers fire when this webhook arrives. Each handler can add and read data to and from its own namespace.

onSettingsViewed(cb: function)
Parameters
cb (function) Callback function that receives the bot object

onSettingsSubmit

Handle webhook that fires after a user hits "save" on their MailBot settings. Newly saved settings arrive at the top-level settings object. Existing settings are still in mailbot.stored_data. Return mailbot and user data to save data (as with other webhooks). ALL onSettingsSubmit handlers fire.

onSettingsSubmit(cb: function)
Parameters
cb (function) Callback function that receives the bot object

initSdkApiClient

Sets an authenticated insteance of MailBots SDK client https://github.com/mailbots/mailbots-sdk-js for use in events and middleware under bot.api. Works both with webhook + web request

initSdkApiClient(req: any, res: any, next: any)
Parameters
req (any)
res (any)
next (any)

bot.webhook

The 'bot' object passed into the handlers comes with a number of helpers to handle the request.

NOTE: This class is automatically instantiated with an instance of BotRequest and made available under bot.webhook.

bot.webhook
Parameters
botRequest (object) An instnace of BotRequest
Example
bot.webhook.getMailBotData('memorize.settings');

get

Get the current value of a key. If a value has already been been set on the responseJson object, it return that value, otherwise it returns the value in the request object.

get(key: string, defaultValue: mixed, responseJsonOnly: boolean)
Parameters
key (string) JSON path to key
defaultValue (mixed) Default if key is falsy
responseJsonOnly (boolean) Only get values that are set on the responseJson object
Example
bot.webhook.get('source.from'); // sender's email address
bot.get('source.from'); // this method is also aliased directly on bot

set

Set attributes on the this.responseJson object. If existing value and new value are both objects (not Arrays) they are shallow-merged. If new or old data are not objects, the value of the key is replaced entirely.

set(key: string, value: any, merge: boolean)
Parameters
key (string) JSON Path to object within responseJson object
value (any) Value
merge (boolean) Objets are shallow-merged by default. Set this to false to force the replacement of an object.
Example
bot.set('task.reference_email.subject', "New Subject!");
bot.set('task.reference_email', { subject: "New Subject!"}); // Same effect as above

getTaskData

Get data for current task

getTaskData(key: string, defaultValue: any)
Parameters
key (string) JSON Path to key within task.stored_data
defaultValue (any) If there is no key, return this

setTaskData

Set data for current task Takes either an object or a lodash _.set path as the first param, optionally a value as the second parameter. Objects are shallow-merged if new and old data are objects. (Arrays and other types are replaced).

setTaskData(args: ...any)
Parameters
args (...any)
Example
setTaskData('my_bot.magic_number', 42);

getMailBotData

Get data stored in mailbot.private_data

getMailBotData(key: string, defaultValue: any)
Parameters
key (string) JSON Path to data from mailbot
defaultValue (any) If value is undefined, use this value instead
Example
bot.webhook.getMailBotData('my_bot.setting', 42);

setMailBotData

Set data stored in mailbot.private_data. Objects are shallow merged if existing data is present. All other values (including arrays) are replaced.

This method has two signatures. It can take an object as its only param which will be shallowly merged into mailbot.private_data.

It can also take a lodash _.set path as its first parameter and a value as the second.

setMailBotData(args: ...any, param: (string | object), value: any?, merge: any?)
Parameters
args (...any)
param ((string | object)) Either a lodash set param or an object to be shallowly merged into mailbot.private_data
value (any?) When passing lodash set path string as first
merge (any?) When passing lodash set path string as first param, this specifies if the data should be shallowly merged or replaced (defaults to true).
Example
bot.webhook.setMailBotData({key: "value"});
bot.webhook.setMailBotData('foo.bar', "value");

getReferenceEmail

Get the reference email – the abstract, user-editable instance of the email associated with this task. Note this is not the same as the source email (which is the exact email received).

getReferenceEmail()
Example
bot.webhook.getReferenceEmail();

setReferenceEmail

Set reference email Shallowly merges refernece email fields, allowing for easy partial updates

setReferenceEmail(referenceEmail: object)
Parameters
referenceEmail (object)
Name Description
referenceEmail.to array Array of email address strings
referenceEmail.cc array Array of email address strings
referenceEmail.bcc array Array of email address strings
referenceEmail.subject string Email subject
referenceEmail.reply_to string Email address or action-email
referenceEmail.html string html HTML content of email
referenceEmail.text string html text-only content of email
Example
bot.webhook.setReferenceEmail({
  to: "test@gmail.com",
  subject: "A new subject",
  html: "This new content replaces the old"
});

getReplyTo

Get replyto email, taking into this value being edited after the original email was sent.

getReplyTo(): string
Returns
string:
Example
bot.webhook.getReplyTo();

getEmailMethod

Determine if the email command for the current task was placed in the 'to', 'cc' or 'bcc' fields. This is done by comparing the email command with the email address in the and 'to', 'cc' fields. The 'bcc' is hidden from the message envelope but still present in the command.

Note that if the identical email command is in both 'to', 'cc' and 'bcc' it will only show up as the 'to' method.

getEmailMethod()

sendEmail

Sends an email by adding an email message object to the "send_messages" array. Multiple emails can be sent.

sendEmail(email: object): object
Parameters
email (object) email object
Name Description
email.to string comma separated emails
email.cc string comma separated emails
email.bcc string comma separated emails
email.from string from name only (message-envelope is always from mailbots)
email.reply_to string email address or action-email
email.subject string email subject
email.body array Array of ui objects https://docs.mailbots.com/docs/email-ui-reference
Returns
object: A reference to the email object for additional changes
Example
const email = bot.webhook.sendEmail({
  to: bot.get('source.from'),
  subject: "A Subject",
  body: [
  {
   type: 'html',
   text: '<h1>An email</h1>'
  }];
})

email.from = "New Sender"; // still updatable

quickReply

Shorthand method to send a quick reply back to the "from" address of the incoming email. This accepts either a string or object. If passing a strong only, the subject and body share the same text. Pass and object iwth {subject, body} to explicitly set the subject and body

quickReply(message: (string | object))
Parameters
message ((string | object)) Content
Name Description
message.subject string? passing an object
message.body string? If passing an object
Example
bot.webhook.quickReply("Got it!");
botRequest.webhook.quickReply({
   subject: "Quick reply subject",
   body: [{
     type: "title",
     text: "Welcome",
   },
   {
     type: "button",
     behavior: "url",
     text: "Press Me",
     url: "google.com"
   }]
 });

setTriggerTime

Set trigger time for a task using natural language

setTriggerTime(time: string)
Parameters
time (string) FollowUpThen-style time description
Example
bot.webhook.setTriggerTime("monday");
bot.webhook.setTriggerTime("every2ndWeds");
bot.webhook.setTriggerTime("everyTuesday2pm");

setTriggerTimestamp

Set trigger time for a task using a unix timestamp

setTriggerTimestamp(timestamp: int)
Parameters
timestamp (int) Unix timestamp of trigger time
Example
bot.webhook.setTriggerTimestamp(1546139899);

invite

Trigger MailBots to invite the given email address(es) to use a bot

invite(invitees: array)
Parameters
invitees (array) Array of email addresses
Example
bot.webhook.invite(['user1@email.com','newUser2@gmail.com']);

completeTask

Mark task a completed. The task will be archived until permenantly deleted.

completeTask()
Example
bot.webhook.completeTask();

discardTask

By default, MailBots creates a task for every command that is visible to the user in their Tasks list. When this is not desireable (for example, a simple one-time email command) you can use this method to immediately discard the task.

discardTask()
Example
bot.webhook.discardTask();

getAllContacts

Fetch all contacts associated with the task's reference_email. User's address and MailBots addresses are excluded.

Note: This uses reference_email, allowing a user to add or remove additional recipients from the task.

getAllContacts(): array
Returns
array: Email addresses
Example
bot.webhook.getAllContacts();

respond

Respond to (and end) the webhook response with the JSON provided. Multi-fire handlers (like onSettingsViewed) automatically respond. In the case where an error-condition causes an early return / response calling this method prevents the handler from trying to return a second time. Provided JSON is shallowly merged into response object.

respond(json: object)
Parameters
json (object) Response JSON
Example
bot.webhook.respond();

settingsPage

Creates a new settings page, rendered in the WebhookHelpers Settings section of the Admin UI. See SettingsPage docs for details

settingsPage(params: object)
Parameters
params (object)
Name Description
params.namespace string Namespace used on mailbot.private_data
params.title string (default "") Page title
params.menuTitle menuTitle Menu item title
Example
const.settingsPage = bot.webhook.settingsPage({
  namespace: "mem",
  title: "Memorization Settings",
  menuTitle: "Memorization"
});

getSource

Get bot request source request object. Usually this is an email. It can also be an API request.

getSource(): IWebhookSourceEmail
Returns
IWebhookSourceEmail:
Example
const source = bot.webhook.getSource()

getTrigger

Get information about the event that triggered this webhook.

getTrigger(): IWebhookTrigger
Returns
IWebhookTrigger:
Example
const trigger = bot.webhook.getTrigger()

getUser

Get data about the user for which the webhook was posted.

getUser(): IWebhookUser
Returns
IWebhookUser:
Example
const user = bot.webhook.getUser()

getTask

Get task data associated with the webhook.

getTask(): IWebhookTask
Returns
IWebhookTask:
Example
const task = bot.webhook.getTask()

getMailBot

Get mailbot data sent over with each webhook.

getMailBot(): IWebHookMailBot
Returns
IWebHookMailBot:
Example
const mailbot = bot.webhook.getMailBot()

setWebhookStatus

Set webhook status message. If the user is on the web ui, this message flashes ont eh screen. It also appears in the extension logs.

setWebhookStatus($0: Object): void
Parameters
$0 (Object)
Name Description
$0.level any (default "info")
$0.message any
Returns
void:
Example
setWebhookStatus({message: "Something worked!"});

addFutUiBlocks

Add FUT Email UI Block Respond with a partial UI element to be injected into an email response. the response schema of responseJson is different than the other webhooks. It is sent via the Core API directly back to the fut mailbot, which merges it into its own response webhook response

addFutUiBlocks(uiBlocks: any)
Parameters
uiBlocks (any)

skillMarkedForRemoval

In the taskUpdated method, this method is used to check if this skill has been marked for removal so cleanup options can be optionally performed. It must reply

skillMarkedForRemoval(data_namespace: any, bot: any)
Parameters
data_namespace (any) data_namespace of this mailbot
bot (any)

addSearchKeys

Helper for a FUT skill to add a new search key. Idempotent.

addSearchKeys(keys: any)
Parameters
keys (any)

removeSearchKeys

Helper for FUT Skill to Remove search keys from a task. Merging is taken care of at next level.

removeSearchKeys(keysToRemove: any)
Parameters
keysToRemove (any)

hasSearchKey

Check if current task has a search key

hasSearchKey(key: any)
Parameters
key (any)

getSettingsUrl

Get this mailbot's settings URL

getSettingsUrl()

getWebAppUrl

Get this mailbot's settings URL

getWebAppUrl()

bot.webook.settingsPage

Reference for bot.webhook.settingsPage()

Bots and bot skills can create settings settings pages which live in responseJson.settings[namespace]. This class represents one such settings form. Each settings form lives in its own namespace. Instantiating a new SettingsPage adds a new namespace to responseJson.settings key and populates it with React JSON Form Schema JSON. https://github.com/mozilla-services/react-jsonschema-form The MailBots Admin UI loops through namespaces, rendering forms appropriately.

NOTE: This class directly mutates MailBot's response JSON to add settings pages and form fields.

bot.webook.settingsPage
Example
const mySettingsPage = bot.webhook.settingsPage({
 namespace: "memorize",
 title: "Memorization Settings",
 menuTitle: "Memorization"
});

// Add elements to the page
mySettingsPage.input({ name: "first_name"}); // renders text input

getSettingsFormJSON

Retrieve the JSON Form Schema for this form

getSettingsFormJSON(): object
Returns
object: JSON Form Schema
Example
const thisJsonSchema = settingsForm.getSettingsFormJSON();

insert

Low-level function to insert raw JSONSchema and uiSchema to create custom form elements. Mozilla's React JSON Schema Form manages the form JSON and UI JSON as two separate objects to keep the form JSON prestine and compliant. This method lets us manage a form element in one place. Ref: https://github.com/mozilla-services/react-jsonschema-form

insert(params: object)
Parameters
params (object)
Name Description
params.name object Namespace
params.JSONSchema object JSON Schema
params.uiSchema object Corresponding uiSchema

This example is taken from https://mozilla-services.github.io/react-jsonschema-form/

Example
formPage.insert({
name: "first_name",
JSONSchema: { type: "string", title: "First name" }, // JSON Schema
uiSchema: { "ui:autofocus": true, "ui:emptyValue": "" } // UI Schema
});

input

Add text input field

input(params: Object)
Parameters
params (Object)
Name Description
params.name string
params.title string
params.description string
params.helpText string
params.placeholder string
params.defaultValue string
Example
formPage.input({
  name: "first_name",
  title:"First Name"
  description: "Type your first name",
  helpText: "Hopefully you don't need help with this",
  placeholder: "(Ex: Bruce Lee)",
  defaultValue: "John Doe",
});

textarea

Add textarea input

textarea(params: Object)
Parameters
params (Object)
Name Description
params.name string
params.title string
params.description string
params.helpText string
params.placeholder string
params.defaultValue string
Example
formPage.textarea({
  name: "life_story",
  title:"Life Story",
  description: "This is a long story",
  helpText: "Just start writing",
  placeholder: "Once upon a time...",
  defaultValue: "I was born, some time passed, now I'm here"
});

checkbox

Add checkbox field

checkbox(params: Object)
Parameters
params (Object)
Name Description
params.name string
params.title string
params.description string
params.helpText string
params.defaultValue string
Example
formPage.checkbox({ name:"confirmation_emails", title:"Confirmation Emails"});

select

Add select dropdown

select(params: Object)
Parameters
params (Object)
Name Description
params.name string
params.title string
params.description string
params.helpText string
params.options array Array of options
params.placeholder string // when no option is selected
params.defaultValue string
Example
settingsPage.select({
  name: "favorite_color",
  title: "Favorite Color",
  description: "Which color do you like the best?",
  helpText: "Choose a color",
  options: ["red", "green", "blue"],
  defaultValue: "blue",
  placeholder: "Pick one!"
 });

alert

Adds a custom dialog at the top of the display form that can be used for interrupt-messaging.

alert(params: object)
Parameters
params (object)
Name Description
params.title string Alert box title
params.text string Alert box text
params.buttons array (default []) Array of Button objects
Example
formPage.alert({
  title: "Connect",
  text: "Connect GitHub",
  buttons: [{ text: "Submit", type: "Submit "}]
})

video

Insert Video. Only YouTube supported for now.

video(params: object)
Parameters
params (object)
Name Description
params.url string (default "") URL of video
params.type any (default "youtube")
Example
formPage.video({
  url: "https://www.youtube.com/watch?v=y1GyXuU2J5k",
  type: "youtube"
})

button

Insert custom button

button(params: object)
Parameters
params (object)
Name Description
params.url string URL of video
params.text any
params.href any (default "")
params.urlParams any
params.className any
params.type any
Example
formPage.button({
  text: "Google"
  href: "https://www.google.com"
});

setUrlParams

Set URL params in the onSettingsSubmit webhook and onSettingsViewed hook (ex: ?success=true to show a success dialog) This provides for an accurate mental model, but internally jumps through a number of hoops to get this URL to appear in the admin UI. NOTE: To get current URL params, use bot.get("url_params")

setUrlParams(urlParams: any, params: any)
Parameters
urlParams (any)
params (any) key value url params

text

Add a text block. Markdown supported!

text(text: string)
Parameters
text (string) – Text with optional markdown

Note: This field is whitespace sensitive. New lines cannot have leading spaces.

Example
formPage.text(`--------------
## ️⚠️ Connect Github
This is a text block here. Leading spaces can break this.
And this is a new line.

[Connect Github](http://www.google.com)

------------------
`);

hiddenInput

Render a hidden input field. Useful for submitting values behind the scenes.

hiddenInput(params: object)
Parameters
params (object)
Name Description
params.name string Field name
params.value string Field value
params.defaultValue any
Example
formPage.hiddenInput({
  name: "key",
  value: "value"
});

submitButton

Render a submit button for the form. (Submit button is not included automatically). URL params can optionally be passed which makes them available to the next handler. Make sure to validate URL input.

submitButton(params: object)
Parameters
params (object = {})
Name Description
params.submitText string
params.urlParams object Key value of url params
Example
formPage.submitButton({
  submitText: "Save Settings",
  urlParams: { key: "value" }
});

populate

Populate form data, overwriting default values with those newly passed. MailBots JSON form data for this namespace can be passed directly to this method to populate the form values.

populate(formData: object)
Parameters
formData (object) JSON object containing form values.
Example
const storedData = mailbots.webhook.getMailBotData("mem", {});
formPage.populate(storedData);