I’m a huge fan of automating working processes. I try to automate everything and save my coworkers time. This article I will show you how to improve the community onboarding journey with a custom Slack bot using the Slack Events API, hosted on AWS Lambda. Motivation We used to have a greeting new members in the . It messages new members with an introduction to help get an overview of the Ory community and products. Since this way of onboarding new members is and so the Ory DevRel team was looking for ways to improve it by making it and : bot Ory Community Slack impersonal one-way personal two-way Message each new member personally. Ask them what their biggest challenges or struggles are or what they want to achieve by joining the Ory community. Point them to the right place or people who can help with that challenge or help them achieve their goals. To keep the workload manageable, the initial message is pre-composed. A bot would either notify the team when new members join or message new members from a "human" account. So the options are: Ping the Ory DevRel team and notify them to greet a user. Send a greeting message from a Slack user. We landed on the second option and let the bot use a to send messages as a user. In the next part of the blog post, I’ll tell you how to build this bot. User OAuth Token Using Slack API Slack offers you a few examples of using APIs: Realtime API Webhook integration Events API I built Slack bots before, and the following two chapters are about the difference between integration and the . Just for convenience, let’s say that the Events API and webhook integration work almost the same way. WebSockets Events API Websockets Websocket is an excellent protocol. It's easy to implement, developer-friendly, and works on top of HTTP1/1. The protocol was first described in 2008, and in 2010 there was broad browser support. Websockets made the developer experience building real-time web applications much more enjoyable back in the day because you don't need to deal with and of HTTP1/1. Furthermore, new architectural patterns and paradigms such as and were described. BOSH chunked encoding Event-Driven Architecture Event-Driven SOA Despite the benefits described above, WebSockets have drawbacks. One of the most significant drawbacks is that it's built on-top-of HTTP/1.1 and keeps the connection open. It leads to scaling problems when you have more users for your application. We have two ways to scale our application: Vertical scaling. You add more resources such as RAM/disk/CPU or buy a new server. Horizontal scaling. You add more servers. You can move a database, webserver, or something else to a different server. Sometimes you need to scale your application horizontally. Horizontal scaling requires load balancing between the servers. Balancing HTTP traffic is easier than WebSocket traffic, and almost all load balancers such as Nginx, Envoy, or Istio balance traffic on L4 of the . It would be best to implement load balancing mechanisms on L7 when you work with WebSockets to have evenly loaded servers. OSI model Furthermore, it would be best to keep in mind that networks are unreliable or third parties may close connections and your application needs to handle it gracefully. In addition, you need to have a persistent listener of incoming events, which leads to the situation when you need to have EC2/VPS or any other server to run your code, but this is a cost-ineffective solution. Events API The Slack Events API works differently. You need to have a web server that is be publicly accessible and an event handler implemented in your app. According to the documentation of the Slack Events API, you need to implement the following steps: Use their signing tokens for incoming requests. Handle the URL verification challenge by your app. The app needs to sign the incoming request and return the incoming challenge parameter. The only thing we need to have is a web server with a simple REST API endpoint. It allows us to use various cloud features to run our bot and save money. For instance, we can use AWS Lambda, trigger it with API Gateway. The best part of this is that we can use the Free tier plan. That's why I'm a huge fan of the Events API from Slack.  Let's build our bot, huh? Implementing the bot We will use the Go programming language to build our bot. What we need: Configuration for the bot using 12factors.net. In that case we will use the package because of its simplicity. envconfig package for local development and testing. net/http to make our web app accessible from the internet. Ngrok . AWS Go SDK Implementing configuration Let's create a package config package config import "github.com/kelseyhightower/envconfig" const ( // EnvProduction is a production environment EnvProduction = "production" // EnvDevelopment is a development environment EnvDevelopment = "development" ) // BotConfig is a struct that stores configuration parsed by `envconfig` // environment variables type BotConfig struct { Env string `envconfig:"ENV" default:"development"` BindAddr string `envconfig:"BIND_ADDR" default:":12022"` SigningSecret string `envconfig:"SLACK_SIGNING_SECRET"` SlackBotToken string `envconfig:"SLACK_BOT_TOKEN"` WelcomeMessage string `envconfig:"WELCOME_MESSAGE"` } // Parse parses and returns BotConfig structure func Parse() (*BotConfig, error) { var c BotConfig err := envconfig.Process("", &c) return &c, err } It's always a good idea to have some tests. package config import ( "os" "testing" "github.com/stretchr/testify/assert" ) func TestParse(t *testing.T) { os.Setenv("SLACK_SIGNING_SECRET", "something") os.Setenv("SLACK_BOT_TOKEN", "amazing") os.Setenv("WELCOME_MESSAGE", "i guess") c, err := Parse() assert.NoError(t, err) assert.Equal(t, "something", c.SigningSecret) assert.Equal(t, "amazing", c.SlackBotToken) assert.Equal(t, "i guess", c.WelcomeMessage) assert.Equal(t, EnvDevelopment, c.Env) assert.Equal(t, ":12022", c.BindAddr) } We can check it by simply running go test ./… Slack events API handler We can start with the example taken from the because of its simplicity. We need to extend this example with the following parts: Slack Go SDK Make it configurable what message we want to send as welcome. Support of AWS Lambda since it has its SDK. To get support for Go we can use the library. Check out the code below. algnhsa package app import ( "encoding/json" "greeter_bot/config" "io/ioutil" "net/http" "strings" "github.com/akrylysov/algnhsa" "github.com/slack-go/slack" "github.com/slack-go/slack/slackevents" ) type ( // Handler is an interface for the webserver that handles // incoming requests from Slack events API // // You can add support of any cloud provider by implementing this interface Handler interface { Init(c *config.BotConfig) Start() error } // HTTPHandler is an implementation of webserver for local development/testing HTTPHandler struct { Handler config *config.BotConfig } ) // NewHandler creates slack events api handler // It creates HTTPHandler for development environment // and LambdaHandler for production env func NewHandler(c *config.BotConfig) Handler { var h Handler h = &HTTPHandler{} h.Init(c) return h } // Init initializes handler func (h *HTTPHandler) Init(c *config.BotConfig) { h.config = c http.HandleFunc("/", h.handle) } // handle handles incoming data from func (h *HTTPHandler) handle(w http.ResponseWriter, r *http.Request) { var api = slack.New(h.config.SlackBotToken) body, err := ioutil.ReadAll(r.Body) if err != nil { w.WriteHeader(http.StatusBadRequest) return } sv, err := slack.NewSecretsVerifier(r.Header, h.config.SigningSecret) if err != nil { w.WriteHeader(http.StatusBadRequest) return } if _, err := sv.Write(body); err != nil { w.WriteHeader(http.StatusInternalServerError) return } if err := sv.Ensure(); err != nil { w.WriteHeader(http.StatusUnauthorized) return } eventsAPIEvent, err := slackevents.ParseEvent(json.RawMessage(body), slackevents.OptionNoVerifyToken()) if err != nil { w.WriteHeader(http.StatusInternalServerError) return } if eventsAPIEvent.Type == slackevents.URLVerification { var r *slackevents.ChallengeResponse err := json.Unmarshal([]byte(body), &r) if err != nil { w.WriteHeader(http.StatusInternalServerError) return } w.Header().Set("Content-Type", "text") w.Write([]byte(r.Challenge)) } if eventsAPIEvent.Type == slackevents.CallbackEvent { innerEvent := eventsAPIEvent.InnerEvent switch ev := innerEvent.Data.(type) { case *slackevents.TeamJoinEvent: text := strings.Replace(h.config.WelcomeMessage, "\\n", "\n", -1) api.PostMessage(ev.User.ID, slack.MsgOptionText(text, false)) } } } // Start starts the server func (h *HTTPHandler) Start() error { if h.config.Env == config.EnvDevelopment { return http.ListenAndServe(h.config.BindAddr, nil) } algnhsa.ListenAndServe(http.DefaultServeMux, nil) return nil } To start everything up we need : cmd/mrrobot/main.go package main import ( "greeter_bot/app" "greeter_bot/config" "log" ) func main() { c, err := config.Parse() if err != nil { log.Fatal(err) } handler := app.NewHandler(c) log.Fatal(handler.Start()) } Creating Slack bot Press button on . Create New App Slack apps page Click on and then input your bot name and choose a workspace to install the bot. From scratch Copy the and store it somewhere. You'll need it to configure AWS Lambda function. Signing Secret Go to the and generate access tokens under . You need the when you want to send message from a user and the for the bot user. You'll need it to configure AWS Lambda. OAuth & Permissions Oauth Tokens for YOur Workspace User Oauth Token Bot User Oauth Token Under the scopes add for both the and scopes. chat:write Bot Token User token Deploying to AWS Lambda Sign in to . AWS Console Go to the Lambda section and press button. Create function Choose input the name of the function. Choose for the runtime and architecture and then create a function. Author from scratch Go 1.x x86_64 Open -> . Configuration Environment variables Set your to variable. Signing Token SLACK_SIGNING_SECRET Set your welcome message to variable. WELCOME_MESSAGE Set your bot token to variable. SLACK_BOT_TOKEN Set to variable. production ENV Go to the section and scroll down to and change from to . Code Runtime settings Handler hello mrrobot Configuring AWS Gateway Create a new REST API. In the "Resources" section create a new method to handle requests to (check "Use Lambda Proxy Integration"). ANY / Add a catch-all resource to handle requests to every other path (check "Configure as proxy resource"). {proxy+} Final steps You need to upload the zip archive with compiled binary to the AWS lambda. In that case, can help us - allows us to automate operations since 1976. Also it always a great idea to have them self-documented: make make .PHONY: help help: @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' lint: ## Runs linter against the code golangci-lint run ./... test: ## Run tests locally go test ./... build_docker: ## Build docker image docker build -t mrrobot . build_linux: ## Build executable for linux system GOOS=linux GOARCH=amd64 go build -o mrrobot cmd/mrrobot/main.go zip: build_linux ## Build and create a zip archive for deploying to AWS lambda zip main.zip mrrobot Run . make zip Open your function on . AWS Lambda Choose under the source code section. Upload from Upload it and run. Choose your application on on Slack, click on and choose . Apps page Add features and functionality Event subscriptions Insert URL of your API Gateway to the field, pass the verification step, and click . Request URL Save Open and choose event and then press . Subscribe to bot events team_join Save That's it. You can now invite a new member to your Slack and test the bot. Conclusion and takeaways As you can see the Slack Events API helps you to build bots and automate your routine. Also, if you just build a simple bot you don’t need to spend money on hosting. Also Published Here