Reducing Context Switching with Scheduled Slack Messages in Go
Can ChatGPT solve this? tl;dr No.
By Drew Rothstein, President
Recently we had a pretty neat idea / use case for using scheduled slack messages vs. a standard point-in-time message.
Global crypto networks never sleep, but humans do. We monitor many networks 24x7 with tiers of alerts: from urgent-waking pages to synchronous alerts and recently introduced a new tier of scheduled-slack alerts that have increased our ability to process important but non-urgent information.
As a context illustration, if you keep a close eye on networks, you are likely aware that some generate extensive communications (spam or otherwise) for which point-in-time messaging is not ideal. If implemented as a realtime firehose, this could regularly trigger 10s per hour becoming fairly useless and a constant distraction. And generally, most such communications do not require real time review or action. Scheduling slack messages offers a solution to efficiently and appropriately manage non-urgent, but important alerts.
As an illustration of this idea, the Eisenhower Matrix visualize this concept:
The Eisenhower Matrix of Alerting
Some examples of these non-urgent but important alerts:
- Scheduled Upgrades: Where networks are not patching an urgent security or other issue, they often don’t need to be processed immediately.
- Proposals: Most networks provide some extended period (days > weeks) for different proposals they may put forward, they often don’t need urgent attention.
- Testnet Actions: While testnets may require urgent action, they should generally not affirmatively context switch us away from more important production projects.
Survey The Landscape
Before we write a single line of code, we typically take a look at what exists out there already and see what we might be able to build from.
Taking a look at the canonical golang library (github.com/slack-go/slack), there are no examples for scheduled messages.
Next, let’s take a look at existing source code / usage in the wild on GitHub (via Sourcegraph). Crazy, no usage! We tried a couple patterns and found no obvious usage.
Last but not least, what does ChatGPT think about this?
Sadly, ChatGPT doesn’t seem to have properly understood the struct
, due to the fact that it can’t be sent as a webhook
, etc.
This is a bit of a head scratcher - we know that many folks programmatically utilize scheduled messages, so we know there’s an opportunity here to unlock new capabilities.
Let’s Investigate
It should be pretty easy to get a prototype working after taking a look at the canonical library. Note that this is community maintained without an official release, so we should not expect it to be complete.
The ScheduleMessage function is as follows:
func (api *Client) ScheduleMessage(channelID, postAt string, options ...MsgOption) (string, string, error) {
return api.ScheduleMessageContext(context.Background(), channelID, postAt, options...)
}
And associated ScheduledMessage struct:
type ScheduledMessage struct {
ID string `json:"id"`
Channel string `json:"channel_id"`
PostAt int `json:"post_at"`
DateCreated int `json:"date_created"`
Text string `json:"text"`
}
This looks easy enough to work with. Interesting that the postAt
is a string
in ScheduleMessage
and an int
in the struct
but that seems fine.
Days and Times
Probably the harder part is thinking through how tactically we want this to work. As usual, dates / times are the hardest part of the problem.
For our use case of non-urgent but important messages, we probably want to schedule all messages to be sent at a specific time, let’s say 7pm local (regardless of daylight savings). So if we get a message to queue up for a 7pm posting at 2pm, it would send at 7pm, seems easy. What happens though if we get a message at 8pm? Well, we would probably want that to queue up for 7pm the next day.
What about weekends? Do we care about delaying all those messages for Monday at 7pm or do we want those to post on Saturday and Sunday? For our use case, we do want them on the weekend (network communication flows generally don’t seem to stop on the weekends). But, you may want to delay further if it is the weekend.
Starting to put hand to keyboard, this might look something like this:
var (
timeToScheduleMessagesHour = 19 // 7pm
)
type Clock struct {
MockTime time.Time
}
func (c Clock) Now() time.Time {
if c.MockTime.IsZero() {
return time.Now()
} else {
return c.MockTime
}
}
// Get a standard evening time to schedule Slack messages
func eveningTimeTodayorTomorrow(c Clock) (sevenPMInMilliseconds string) {
// get the current date and time in the local timezone (UTC)
now := c.Now()
// get the location (timezone) of the local time
newYork, err := time.LoadLocation("America/New_York")
if err != nil {
logger.CheckError(fmt.Errorf("unable to load local TZ: %w", err))
}
// create a time object for 7pm today in the local timezone
todaySevenPM := time.Date(now.Year(), now.Month(), now.Day(), timeToScheduleMessagesHour, 0, 0, 0, newYork)
// if the current time is after 7pm, add 24 hours to get 7pm tomorrow
if now.After(todaySevenPM) {
todaySevenPM = todaySevenPM.Add(24 * time.Hour)
}
// get the number of milliseconds since the Unix epoch for 7pm today/tomorrow
sevenPMInMilliseconds = fmt.Sprint(int(todaySevenPM.UnixNano() / int64(time.Millisecond)))
return
}
This looks pretty good to start from and seems to do what we intend. Given that we are manipulating date / time, it is always a good idea to write some tests to confirm that this works as you expect. In the Full Example below, a test for this function is included.
Slack Scheduled Message Implementation
Reading through the library it looks pretty reasonable. We create an instance of the slack api, and would call ScheduleMessage
with a few parameters as discussed above.
In our case, we would get the posting time from the previously written function. We would obviously pass it some text as well.
This might look something like this:
// Slack a message at a scheduled time
func ScheduledSlack(text string) {
api := slack.New(viper.GetString("slack_url"))
// Set Defaults
slackChannel := viper.GetString("slack_channel")
// Post
channel, timestamp, err := api.ScheduleMessage(
slackChannel,
eveningTimeTodayorTomorrow(Clock{}),
slack.MsgOptionText(text, false),
)
if err != nil {
fmt.Printf("%s\n", err)
logger.CheckError(fmt.Errorf("error posting scheduled message to slack channel: %s, at timestamp: %s, error: %w", channel, timestamp, err))
return
}
}
This is a pretty good start and obviously implies a couple things: you have a slack token
, you are using viper
, and have defined some sort of logging
library that checks for errors.
Putting it All Together
Most usage glosses over the, “go get a slack token” which often becomes the most confusing stumbling block to bringing your slack utility to life with appropriate permissions – so we’ll illustrate it here.
Depending on the organization, you may want to limit who can do such a thing (only a few specific admins). Those admins would perform something like the following:
- Head over to https://api.slack.com/apps and select Create New App.
- They would likely select From scratch.
- Give the App a Name, and a Workspace, and Create App.
- Under App Home, Review Scopes to Add.
- Under Scopes > Bot Token Scopes > Add an OAuth Scope, add
chat:write
. - Install App > Allow, copy the Bot User OAuth Token.
Optionally, you might consider giving your application some polish with a logo, description, etc. but none of that is required for getting started and testing this out.
Then, you may want to store such a token securely in a Secret Manager of choice where your application can access it.
Launch It
With a bit more testing / integration, this became pretty easy to implement across a variety of networks. The outcome is a nicely formatted / scheduled message:
Evmos Testnet Illustration
Sommelier Mainnet Illustration
Full Example
Full Example
If you would like to see a full example (see input variables), please see the GitHub Gist:
Conclusion
Scheduling Slack messages via Golang with the community maintained library is pretty reasonable and can certainly be helpful for solving delayed / informational messaging from various networks.
We already have a few other ideas for managing low priority messages using this same idea vs. real time as we continue to tune and refine.
For us, it is operationally important to manage context switching (via real time messaging) to help make sure it is not an annoyance but valuable. Sometimes this requires creative solutions to pause and group messages that are not critical. It’d be super nice if more tools understood and built in features for this vs. assuming everyone wants to receive everything now.
If this type of problem solving is interesting to you, we have several positions open right now and would be happy to chat: unit410.com/#jobs.