Skip to content
  • Services
  • About
  • Partners
  • Work
  • Insights
  • Careers
  • Contact
  • Services
  • About
  • Partners
  • Work
  • Insights
  • Careers
  • Contact
April 17, 2017

Promoting New Blog Entries with AWS Lambda and Slack

AWS, DevOps, JavaScript, Tools

Here at Modus Create, we’re avid users of Slack as a team collaboration tool and have been working on ways to use it more as we go about our daily business. We also maintain a regularly updated company blog. Recently, we decided that it would be a fun learning exercise to combine these things and build a small bot that posts new blog entry announcements to Slack.

“But wait!”, you say… “Doesn’t your blog have an RSS feed… Slack already supports those as a data source!” You’d be right to say this: we do publish an RSS feed, and Slack is able to notify users of new blog posts using out of the box functionality (you can read about this in the Slack documentation). We decided we’d have a go at building something similar ourselves primarily as a learning exercise for a different and more feature-rich bot we plan to develop. This approach also allows us to add our own logic later should we need to go beyond Slack’s built-in RSS support that cannot be customized.

Basic Lifecycle

To make this work with any RSS feed, we decided to use a polling approach rather than rely on having admin access to our blogging platform and modifying that to ping Slack every time a new article is published. This meant that our “lifecycle” basically needed to include the following:

  • A timer event occurs
  • Some code is executed which:
    • Reads the RSS feed URL for the blog
    • Determines if any new posts have been added since the last time it ran
    • If there are new posts, creates nicely formatted message payload containing links to each new post and pushes the resulting message into Slack
  • Slack displays the message in a specific channel
  • Team members read that there’s a new blog post, check it out over a coffee and maybe share it with their social networks
  • Repeat

Implementation Choices

One way of implementing the desired lifecycle would be to run a server with a cron job that runs some sort of script every so often to check the feed and post to Slack if needed. The downside of this is that it requires a server to be running all the time — somewhat overkill for such a small task.

Somewhere to Execute Logic

To avoid the need to run a server continuously, we chose AWS Lambda where we only incur costs when our code is running and has a generous amount of free tier usage. Lambda supports writing code in Java, Python or JavaScript. (Node.js) We’d previously used Lambda with Python to create an Alexa Skill. This time we selected Node.js instead to learn about its programming model in the Lambda environment. Since we want to be able to read a URL, process the RSS feed XML document from it and post to a Slack URL if needed, any of the three languages that Lambda supports would have been a sensible choice since they all support each step.

A Trigger Mechanism

We need to be able to run our code periodically at configurable intervals. In the AWS environment, there’s a sufficiently capable solution in the CloudWatch Events service. Normally this is used to trigger target code when an event happens to some other AWS resource, but it can also be setup to behave like cron (using a similar notation) and invoke a Lambda function on a schedule.

Our blog posts generally get published during working hours, US Eastern and US Pacific time, so we used a polling schedule that invokes a Lambda function once an hour around these times as shown:

Promoting Blog Entries with AWS Lambda and Slack


Note that CloudWatch Events run in UTC, hence the “early” and “late” hours. The “Resource name” maps to the Lambda function that will perform the next two steps in the process for us.

Determining if New Post(s) Exist

Once CloudWatch invokes our Lambda function, it needs to read the RSS feed for the blog, and work out if one or more posts were added since the last time it ran. As we’re working with the Node runtime in AWS Lambda, we can simply use the popular request package to get the blog RSS feed, and parse the entries from XML to JSON with the xml2js package.

Because Lambda functions don’t retain state between executions, we can’t as such “remember” when the function was last invoked. To keep things simple, we opted not to store the last execution time or the ID of the newest post from the prior run in a database. Instead we will assume anything newer than the polling interval configured in CloudWatch Events could be classed as a “new” post that needs to be sent to Slack, and add details of it to an array of objects:

request(blogFeedUrl, (err, res, body) => {
  if (! err && res.statusCode == 200) {
    parseXMLString(body, (e, r) => {
      if (! err && r) {
        let newBlogEntries = [],
            currentTime = new Date();

        // Loop over entries until we find one that is too old.
        for (let blogEntry of r.rss.channel[0].item) {          
          // Work out if the blog entry is new since last 
          // time we looked...
          let latestBlogTime = new Date(blogEntry.pubDate[0]);
                        
          if ((currentTime.getTime() - latestBlogTime.getTime()) <= 
              (runInterval * ONE_MINUTE)) {
            // Post was published since the last check...
            newBlogEntries.push({
              author: blogEntry['dc:creator'][0],
              title: blogEntry.title[0],
              link: blogEntry.link[0]
            });              
          } else {
            // We are done as blog entries are in date order with 
            // most recent first.
            break;
          }
        }
        ...

Once we’ve determined that there’s at least one new post, we need…

A Way to Push Messages into Slack

In order to send a message to Slack, we require:

  • A webhook URL (configured in Slack so that Slack knows where to send incoming messages)
  • Code to format a message string for Slack and post it to the webhook URL

Setting up a webhook URL is handled on the admin site for your Slack team, sign in at:

https://<YOUR_SLACK_TEAM>.slack.com/apps/manage/custom-integrations

New incoming webhooks are configured here, and Slack will need to know the following in order to configure one:

  • The channel that you want the messages to post to (e.g. “#general”)
  • The name that you want your messages to post as (e.g. “modus-blog”)
  • Optionally: an image or emoji that you want to use for each message (e.g. “:newspaper:”)

The Slack admin page will then generate a webhook URL for you, which looks something like:

https://hooks.slack.com/services/T02XXXXXX/BXXXXXXS3/SnTXh5LxxxXXXXEG8rdXXGr

Formatting the message for Slack is simple, just send a message body object with a text key containing Slack formatted markup to link to the URL of the blog entry, and make a bullet list item for each new entry:

let messageBody = `{"text": "*New blog entr${newBlogEntries.length === 1 ? 'y' : 'ies'} published:*\n\n`;

for (let blogEntry of newBlogEntries) {
  messageBody += `•  _by ${blogEntry.author}_\n`;
}

messageBody += '", "unfurl_links": true}';

Sending the message to Slack is then as simple as POSTing the message to the Slack webhook URL:

request.post({
  url: process.env.SLACK_HOOK_URL,
  body: messageBody
});

The End Result

When Slack’s API receives the message payload at the webhook URL, it publishes a message to the channel associated with that URL. As our payload contain links, and we asked Slack to unfurl links, it will attempt to create a preview view, resulting in a message that looks like this (screenshot from the Slack client for Mac OS):

Promoting Blog Entries with AWS Lambda and Slack


Team members can then click the link, and enjoy reading the latest post(s).

Try it Out

If you want to use this with your own Slack team or extend it to meet your organization’s needs, feel free! We’ve published our code in full on GitHub, along with a guide to setting it up and getting it running. We’d also love to see what you’ve built with Slack that keeps your team members more in the loop — let us know via the comments.

Posted in AWS, DevOps, JavaScript, Tools
Share this

Simon Prickett

Simon Prickett is a pragmatic technology enthusiast with broad experience of technical consultancy, product and software development, technical training, software architecture, development and system analysis. Simon has developed and led solutions for clients including MTV Networks, Coca-Cola and USA Today. Simon graduated top of his class from Aston University. During his time with Modus Create, Simon filled the role of Architect.
Follow

Related Posts

  • Redirects Requests to a Domain with AWS Lambda
    Redirect Requests To A Domain With AWS Lambda

    Lambda is a serverless computing platform that allowscoding in C# (.NET Core), Go, Java, Node.Js,…

  • Build-an-Alexa-Skill-with-Python-and-AWS-Lambda
    Build an Alexa Skill with Python and AWS Lambda

    ul { list-style: circle outside; } Introduced in 2015, Amazon Echo is a wireless speaker…

Subscribe to the Modus Newsletter

Receive the latest blog articles and insights every month from the Modus team.

Let's Chat

If forms aren’t your thing, you can always call us (+1-855-721-7223).

Modus-Logo-Primary-White.svg
  • Services
  • About
    • Newsroom
  • Partners
  • Work
  • Insights
    • Blog
    • Modus Labs
  • Careers
Virginia (US)

12100 Sunset Hills Road
Suite 150
Reston, Virginia, 20190
Tel: +1-855-721-7223

California (US)
12130 Millennium Dr

Los Angeles, CA 90094

Missouri (US)
609 E High St

Jefferson City, MO 65101

Romania

Str. Mihai Veliciu, no. 17
Cluj-Napoca, Romania
Tel: +40-0786-887-444

Costa Rica

2nd Floor, Plaza Koros, Av 3
San José, Santa Ana, Costa Rica

© 2021 Modus. All Rights Reserved.

Privacy Policy | Accessibility Statement | Sitemap

This website uses cookies.
These cookies are used to collect information about how you interact with our website and allow us to remember you. We use this information in order to improve and customize your browsing experience, and for analytics and metrics about our visitors both on this website and other media. To find out more about the cookies we use, see our Privacy Policy.

Accept
Privacy & Cookies Policy

Privacy Overview

This website uses cookies to improve your experience while you navigate through the website. Out of these cookies, the cookies that are categorized as necessary are stored on your browser as they are essential for the working of basic functionalities of the website. We also use third-party cookies that help us analyze and understand how you use this website. These cookies will be stored in your browser only with your consent. You also have the option to opt-out of these cookies. But opting out of some of these cookies may have an effect on your browsing experience.
Necessary
Always Enabled

Necessary cookies are absolutely essential for the website to function properly. This category only includes cookies that ensures basic functionalities and security features of the website. These cookies do not store any personal information.

Non-necessary

Any cookies that may not be particularly necessary for the website to function and is used specifically to collect user personal data via analytics, ads, other embedded contents are termed as non-necessary cookies. It is mandatory to procure user consent prior to running these cookies on your website.

Scroll To Top