Build a serverless Slack bot using AWS Lambda and Salesforce

I’ve been really interested recently about the serverless architecture and how I could leverage it to build a Slack bot. Driven by my curiosity, I decided to challenge myself to build one on AWS Lambda. For the sake of this tutorial, I will walk-through a sample use case that you could replicate at your company.

Use Case

A salesperson is actively interacting with colleagues on Slack. She would like to be aware of any new opportunities created in her Salesforce organization. She could open her browser every day to see for new material, but she’d prefer to be notified proactively so she can remain focused on high priority tasks.


As a developer, you think that Slack could be a good channel to post new opportunities, considering the sales team is interacting on it on a daily basis.

Network Diagram

This article will walk-through the steps to create your first Slack bot integration. The bot will be a serverless app hosted as an AWS Lambda function, leveraging API Gateway to route the HTTP requests to the function and passively listening to outbound messages from Salesforce.

0. Prerequisites

In order to follow the tutorial, you will need to have the following installed:

  • NodeJS, a JavaScript runtime.
  • TypeScript, static type-checking for JavaScript.
  • yarn, a fast and reliable dependency manager.
  • up, a serverless app orchestration toolset.

Considering NodeJS and TypeScript are quite common, we’ll skip their installation process and jump straight to Up.

1. Install & configure the orchestration tool

If you do not have the Up CLI toolset already, installing it is really easy - simply copy the following in your shell:

curl -sf | sh

Make sure you have your AWS credentials in your ~/.aws/credentials file and have an IAM Policy setup.

For more information, refer to the documentation on how to setup your AWS Credentials.

2. Get the repository & dependencies

Before we get started, clone the Github repository and get all the dependencies using yarn:

$ git clone
$ cd serverless-slack-bot
$ yarn

3. Review Codebase

Project Tree

├── src
│   ├── app.ts
│   ├── dataServices
│   │   └── slack.ts
│   └── typings
│       ├── global.d.ts
│       └── slack.d.ts
├── package.json
├── tsconfig.json
├── up.json
└── webpack.config.js

app.ts - the application server

The app.ts file contains the server code that will be listening for incoming requests. For this example, we are using Koa.js, a Web framework designed by the team behind Express. It will parse the SOAP request and post its content to Slack.

import * as cheerio from 'cheerio';
import * as Koa from 'koa';
import * as bodyParser from 'koa-bodyparser';
import { URL } from 'url';

import { sendOpportunity } from './dataServices/slack';

const app = new Koa();

// The HTTP server is listening on the PORT, provided by Up
const { PORT = 6666 } = process.env;

// SOAP response sent back on request to Salesforce
`<soapenv:Envelope xmlns:soapenv="">
    <notificationsResponse xmlns="">

// Setting up Koa to parse as text the text/xml body sent by the Outbound Message
  enableTypes: ['text'],
  extendTypes: { text: ['text/xml'] }

 * Server handler - this is where we are processing requests to the server
 * @param ctx Request context
const handler = (ctx: Koa.Context) => {
  const { rawBody } = ctx.request;

  if (!rawBody) return;

    .then(response => {
      ctx.type = 'text/xml'
      ctx.body = SOAP_RESPONSE;

      console.log('Message posted successfully!');
    .catch(err => {
      ctx.type = 'text/xml'
      ctx.body = SOAP_RESPONSE;

      console.log('Error when posting message:', err.message);

 * Parsing the Outbound Message
 * @param rawBody
const parseRequest = (rawBody: string): Options => {
  const $ = cheerio.load(rawBody);
  const url = $('EnterpriseUrl').text();
  const instanceUrl = new URL(url).origin;

  const parseField = (field: string) => $(`sf\\:${field}`).text();

  return {
    id: parseField('Id'),
    name: parseField('Name'),
    closeDate: parseField('CloseDate'),
    amount: parseField('Amount'),


app.listen(PORT, () => {
  console.log(`Server listening on port ${PORT}...`);

slack.ts - the Slack data service

The slack.ts file contains the logic interacting with the Slack API.

import { WebClient } from '@slack/client';

const { SLACK_CHANNEL, SLACK_TOKEN } = process.env;

const web = new WebClient(SLACK_TOKEN);

 * Sending the Opportunity to the Slack channel
 * @param options Params required to post the message to Slack.
export const sendOpportunity = (options: Options): Promise<any> => {
  return, `📣 *New Opportunity*`, {
    "channel": SLACK_CHANNEL,
    "username": 'MavensBot',
    "icon_url": "",
    "attachments": [
        "fallback": "Opportunity Details",
        "fields": [{
          "title": "Name",
          "short": true
          "title": "Amount",
          "value": `$ ${options.amount}`,
          "short": true
          "title": "Close Date",
          "value": options.closeDate,
          "short": true
        "actions": [
            "name": "View Opportunity",
            "type": "button",
            "text": "View Opportunity 💵",
            "style": "primary",
            "value": `${options.instanceUrl}/${}`

Other files

  • src/typings/global.d.ts - global type definition
  • src/typings/slack.d.ts - Slack API’s type definition
  • package.json - your NodeJS package file
  • tsconfig.json - your TypeScript configuration file
  • up.json - the configuration file for Up (this is where environment variables are set)
  • webpack.config.js - the compiling config file for Webpack

4. Setup Slack

You’ll have to install custom bots and create one to get your own Slack auth token. Head over to:


Generate your first bot configuration and copy the API token to your clipboard. Then, open your up.json file and replace the placeholder API token with the one in your clipboard.

  "name": "salesforce-slack-bot-api",
  "profile": "salesforce-slack-bot-api",
  "environment": {
    "SLACK_TOKEN": "[my-api-token]",
    "SLACK_CHANNEL": "sales"

Now that this is all set, login into your Slack instance and create the #sales Slack channel - this is where our Slack bot will post new Salesforce opportunities.

5. Deploy & Retrieve HTTP Endpoint URL

Now that you have your codebase ready for deployment and Up configured, run from your terminal the following command:

$ up

This will do all the magic of building your app, deploy as a Lambda function and setup AWS API Gateway for it. No extra work required!

Finally, to get the staging URL of your HTTP server exposed on API Gateway, execute:

$ up url --copy

6. Set Workflow Rule & Outbound Message

Now that we have our Slack bot ready on AWS Lambda, it’s now time to configure Salesforce to send new opportunities to our serverless application. Since our serverless application is ready to process Outbound Messages coming from Salesforce, we’ll create a Workflow Rule and an Outbound Message.

Open the Workflow Rules wizard page in your Salesforce environment and click on New Rule.

Build > Create> Workflow & Approvals > Workflow Rules

Copy the following settings:

  • Salesforce Object: Opportunity
  • Rule Name: On Insert - Opportunity
  • Evaluation Criteria: Created
  • Rule Criteria: Formula evaluates to true, with true as the formula.

Then, create an Outbound Message - it should look like this:

Outbound Message

Make sure to replace the Endpoint URL with your API Gateway URL.

When you are done setting up the Outbound Message, save it and Activate it!

7. Test it!

Everything should be ready to give it a shot. Spin up the Opportunity tab in your Salesforce organization and create a new one. Make sure to populate the Name, Amount and Close Date. On save, you should get a nicely crafted message straight into your #sales channel.

Slack bot message

Next Steps

Congratulations - you’ve successfully setup your serverless Slack bot! At this point, anything is possible. You could push any other type of records to Slack using outbound messages or trigger some more complex messages using Apex Triggers and API calls.

For any question on how to get this to work, ping me on Twitter @jpmonette.