Automating virtual status syncs
Tags: productivity, management, agile
Published: 2020-03-27 15:08:39

Status meetings can be a very helpful tool for a lot of teams. It's a chance for the team to identify and tackle issues that come up in the day-to-day of work. But sometimes a meeting can be a bit much, or other logistics get in the way.

I've had good success with virtual status reporting over a couple of projects, and especially lately, I've had some folks asking how I do some of this. This post is just a quick summary of how I like to set things up using [Microsoft Teams](https://products.office.com/microsoft-teams/group-chat-software), [Power Automate](https://flow.microsoft.com/), and the [Microsoft Graph](https://graph.microsoft.com).

> Disclaimers: I work at Microsoft and will be talking about Microsoft tech (although none that I work on), but the principles here should be applicable to other systems. However, it's also worth calling out that I'm speaking from a tech engineering background, which is not the only place these kinds of meetings happen, and this process may not translate to other industries.

# The threaded status discussion

A simple but effective approach to virtual status is just using threads in team-chat where people post what they would otherwise say in an in-person status meeting. This can work surprisingly well, but one thing to caution is that it encourages posting without reading. At least in a meeting context, people are forced to listen to each other. That's a question of your team culture, though, and it's important to evaluate how you extract value from these status reports.

Regardless, we need to start that thread. Generally speaking, you may want a designated channel for statuses, but it could be mixed into another channel. You can designate someone to create that initial post, but I much prefer to automate the process. 

**The goal:** Every workday, automatically start a thread in team-chat where participants can leave/read status info, and notify the channel so that they remember to post. Oh, and we should do this without code because why maintain that if I don't have to (I work in serverless tech, after all).

This is the end result we're after:

![A new thread in teams starting a status conversation for the day](https://dev-to-uploads.s3.amazonaws.com/i/ns4ap8eshrhxwyxx0s2p.PNG)

# Using Power Automate to create a thread in Teams

My team uses Microsoft Teams, and for this kind of automation, I really like Power Automate (formerly called "Microsoft Flow"), as I can get a workflow up and running in no time. This post assumes some basic familiarity with Power Automate, but nothing you can't get by playing around with it for a few minutes.

There's really only two pieces to the workflow we're exploring today:

- Have it kick off every weekday
- Create a new thread in the channel, with an @channel mention

Enabling that mention means that our flow will have a few more constituent steps, but overall it will still be relatively simple. By the time we're done, the overall structure of the flow should look something like this:

![Flow structure showing a recurrence, followed by 3 variable initializations, followed by a conditional, the "yes" fork of which has a composition action followed by the HTTP request with Azure AD](https://dev-to-uploads.s3.amazonaws.com/i/3xanm2buu92lvfnidht3.png)

## Scheduling the post

Flows can be set to run on a schedule, using the [Recurrence trigger](https://docs.microsoft.com/power-automate/run-scheduled-tasks). I want mine to run every weekday, typically at something like 7:00am PT, which still puts it in morning for pretty much all timezones we have team members in. If the target time is early for any team members though, just be mindful about mobile notifications that could wake someone up - the Teams app built-in quiet hours feature is something I heartily recommend.

It would seem natural to set the "Frequency" field to "Day" here, but then weekends are included and you'd need an odd conditional check (
`@contains('12345', string(dayOfWeek(addHours(utcNow(), -8))))`). Better is to set the frequency to "Week,"  and under the "Advanced options" section, you can set "On these days" to to just the days you care about.

![Screenshot of the recurrence configuration](https://dev-to-uploads.s3.amazonaws.com/i/xygc7zd7xgw6lm7nfm64.PNG)

## Calling Teams from Power Automate

Now, your first instinct may be to use the [built-in Teams action](https://flow.microsoft.com/connectors/shared_teams/microsoft-teams/) for creating a post. By all means do; I always advocate for owning and maintaining less custom stuff. However, for my needs, I _really_ wanted the @mention capability so that there is a notification in the channel. The connector can't do that yet, but the [Microsoft Graph API](https://docs.microsoft.com/graph/api/channel-post-messages) can. Fortunately, it's actually relatively easy to make an authenticated call to the Microsoft Graph using the [HTTP with Azure AD connector](https://flow.microsoft.com/connectors/shared_webcontents/http-with-azure-ad/).

> HTTP with Azure AD is a "Premium" connector, meaning it's not available in the free version of Power Automate

So now all we need to do is figure out how to use that Teams API. The docs help a little bit, but I find I learn APIs best when I can play with them. It's time to break out the Graph Explorer.

### Gathering your Teams configuration

The [Graph Explorer](https://developer.microsoft.com/graph/graph-explorer) is a really nice environment for exploring the Microsoft Graph, either against sample data or against your own tenant. I used it to figure out the details of the create message API, but I've captured those throughout these steps so you don't have to (although you could certainly test out the payload using the Explorer).

If you're planning to put this into practice, though, you still need some configuration values: the ID of your team and the ID/name of the channel you're creating the post in. I believe you can parse this information out of a channel link, but for me it's easier to get through the APIs, so here are the steps using Graph Explorer.

1. Within the Graph Explorer, hit the "Sign in with Microsoft" button and log in so you're working against your actual data.
2. Run a GET against `https://graph.microsoft.com/v1.0/me/joinedTeams` and search through the output for the team you're interested in. Make note of the `id` field.
3. Similarly, run a GET against `https://graph.microsoft.com/v1.0/teams/{team-id}/channels`, replacing `{team-id}` with the value you got in the previous step. As before, search through the output for the channel you want to post in, and grab the corresponding `id` and `displayName` fields.

Now we have our values, but we need to get them into the workflow. We'll create some variables at the top of the workflow. Right after the recurrence, add three "Initialize Variable" actions:

- One with the name "Team ID" with the "string" type, it's value being the team `id` you collected earlier
- One with the name "Channel ID" with the "string" type, it's value being the channel `id` you collected earlier
- One with the name "Channel Name" with the "string" type, it's value being the channel `displayName` you collected earlier.

Note that the "Name" fields matter here as they are referenced later. Although the title of the card doesn't matter, I recommend renaming them for clarity (e.g., "Set Team ID"). 

For safety, I like to I add a conditional check to make sure the variables are all set to non-null values. Then I put the actions from the following section in the "yes" branch, and I can do some error logging in the "no" branch. This is optional, though, and you can just add the post creation as the next set of actions if you like living dangerously.

![Screenshot of variable initialization and the conditional check](https://dev-to-uploads.s3.amazonaws.com/i/phwrq73ndldtv9w7wdg1.PNG)

### Creating the post

To keep things a little simpler, I prefer to separate out the construction of the request body for our API call from the actual request action. To do that, we use the Compose action, which sets a new dynamic content we can reference later. Rename the action "Compose message" for the later lookup, and then set the "Inputs" field to the following:

```json
{
  "subject": "@{variables('Channel Name')} status for @{formatDateTime(utcnow(),'yyyy/MM/dd')} (Automated post)",
  "body": {
    "content": "<p><at id=\"0\">@{variables('Channel Name')}</at> Post what you've been working on and what you're working on today.<br/><br/>For conversations, create a new thread in the channel. Try not to actually \"reply\" to others in this thread to keep it easy to see. Extra flair: • (bullet) - work item; 🏃 - on-going work; 💪- stretch goal</p>",
    "contentType": "html"
  },
  "mentions": [
    {
      "id": 0,
      "mentionText": "@{variables('Channel Name')}",
      "mentioned": {
        "conversation": {
          "id": "@{variables('Channel ID')}",
          "displayName": "@{variables('Channel Name')}",
          "@{concat('conversationIdentityType@','odata.type')}": "#Microsoft.Teams.GraphSvc.conversationIdentityType",
          "conversationIdentityType": "channel"
        }
      }
    }
  ]
}
```

> You may notice a `concat` of static values in one of the keys there. This was me attempting to get around the value looking like an email. PowerAutomate won't let you share a workflow if it thinks there's an email in it. If that's a concern for you, note that the channelID resembles an email, too, so you may need to make a similar adjustment earlier on.

Lastly, we need to use the HTTP with Azure AD connector to send that message. Create an action for that connector, and sign in as prompted. This creates a "connection," which is the identity used for the call. That means the post in Teams will show up under your name. Fill out the card as follows:

| Field name | Value |
|------------|-------|
|Method      | `Post`  |
|Url of the request| `https://graph.microsoft.com/beta/teams/@{variables('Team ID')}/channels/@{variables('Channel ID')}/messages` |
|Headers| Key: `Content-Type`, Value: `application/json` |
|Body of the request| `@{outputs('Compose_message')}` |

![A screenshot of the HTTP with Azure AD card, filled in with the values from the table above](https://dev-to-uploads.s3.amazonaws.com/i/vwpmny66ig4r9h1jchil.png)

And with that, the flow is complete. A post will land in the channel daily that people can respond to with their status.

# How it's working for us

I'd say this process has been going well, overall. It's by no means perfect, but the consensus from my team seems to be that this helps. I've found good success with using both threads and meetings when possible. I use a thread to capture things in advance from folks who may not be able to make a meeting, and then that's read out during the designated meeting time. For smaller teams where everyone is always at the meeting, the thread isn't that valuable day-to-day, but occasionally it helps us react to a sudden change or follow up on discussed topics.

The threads have also had broader readership than I expected. For example, devs on my team will read the status thread that the PMs use. I should be clear that we'd never have a PM standup meeting, but we still want to communicate and coordinate with each other, and this fits the bill. The unintended side effect is that non-participants have felt better aware of what we're doing by being able to check in on what those threads are looking like.

Looking forward, I'm playing with the idea of using "channel meetings," which is what I'm calling the Teams "Meet Now" feature. What's nice is that the meeting is visible in the channel, and anyone with access to it can just join. I'm looking into this for office hours and other applications, too, but it could be interesting in a status context.

I hope that some of this helps or was interesting! Every team is different, and you'll have to find what works best for you. The process I've described here is a little more complicated than it needs to be just because I wanted the @mentions, and as soon as the Teams connector supports those, I'll be moving to it. Regardless, the key here was identifying a process that can work for the team, and then putting it into practice quickly with the tools I had on hand.

Be well, and happy status reporting!