3 min read

Building MS Teams Custom Connector with Azure Functions

Justin Yoo

In my previous post, we discussed how Azure Logic Apps, as a custom connector, can build up a message format in two different ways to send messages to Microsoft Teams. This post will discuss the same, but this time it will be using Azure Functions as a custom connector to send messages to Microsoft Teams.

Registering Custom Connector on Teams

First of all, we need to register the custom connector on our preferred Microsoft Teams channel. Like the previous post, we use the Incoming Webhook connector. Once the webhook is created, take a note for the webhook URL.

Building Actionable Message Card

As discussed in my previous post, Microsoft Teams custom connectors currently, at the time of this writing, only support the Actionable Message Card format, instead of the Adaptive Card format. Either way, they are basically a massive JSON object, which is a bit tricky to build the object from scratch. Fortunately, there's a community effort that makes our lives easier at NuGet. We just use it. With this library, the card generation part might look like:

private async Task ProcessAsync(string webhookUri, string summary, string title = null, string text = null, string themeColor = null, string sections = null, string actions = null)
var card = new MessageCard()
Title = title,
Summary = summary,
Text = text,
ThemeColor = themeColor,
Sections = ParseCollection<Section>(sections),
Actions = ParseCollection<BaseAction>(actions)
var converted = JsonConvert.SerializeObject(card, settings);
var message = (string)null;
var requestUri = webhookUri;
using (var client = new HttpClient())
using (var content = new StringContent(converted, Encoding.UTF8, "application/json"))
using (var response = await client.PostAsync(requestUri, content).ConfigureAwait(false))
private List<T> ParseCollection<T>(string value)
var parsed = string.IsNullOrWhiteSpace(value)
? null
: JsonConvert.DeserializeObject<List<T>>(value, settings);
return parsed;

Except both webhookUri and summary parameters, others are all optional, as you can see. And both sections and actions parameters accept a stringified JSON array, which is deserialised within the code. Of course, depending on your situation, you can handle both parameters differently.

In general, both sections and actions structure might be unique and have a pre-formatted structure for each organisation, so the code above might need to be accommodated to individual organisation's situation.

Sending Messages via Azure Functions

In fact, the concept code above is pretty generic so that any application can send messages to a Microsoft Teams channel. If we use Azure Functions, the code might look like:

public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "teams/send")] HttpRequest req,
ILogger log)
log.LogInformation("C# HTTP trigger function processed a request.");
var body = await new StreamReader(req.Body).ReadToEndAsync().ConfigureAwait(false);
dynamic payload = JsonConvert.DeserializeObject<object>(body);
await ProcessAsync(
return new OkResult();
view raw function-app.cs hosted with ❤ by GitHub

Sending Messages via Console App

What if we send the same messages through a console app? If we use another library like CommandLineParser, the parameter will be way elegant, but we just use the args array, and the console app might look like:

public static void Main(string[] args)
var webhookUri = args[0];
var summary = args[1];
var title = args[2];
var text = args[3];
var themeColor = args[4];
var sections = args[5];
var actions = args[6];
view raw console-app.cs hosted with ❤ by GitHub

So far, we've discussed how to send messages to a Microsoft Teams channel through Azure Functions and console app. As the PoC code is, like I mentioned earlier, very generic, it can be used anywhere, including Azure Functions, which is just a container of the code. Let's see some other use cases in the next post.