ChatGPT Header

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

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

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?

ChatGPT Question

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

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:

  1. Head over to https://api.slack.com/apps and select Create New App.
  2. They would likely select From scratch.
  3. Give the App a Name, and a Workspace, and Create App.
  4. Under App Home, Review Scopes to Add.
  5. Under Scopes > Bot Token Scopes > Add an OAuth Scope, add chat:write.
  6. 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

Evmos Testnet Illustration

Sommelier Mainnet Illustration

Sommelier Mainnet Illustration

Full Example

Full Example

Full Example

If you would like to see a full example (see input variables), please see the GitHub Gist:

Conclusion

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.