Mattermost.NET is a ready-to-use .NET Standard library for building Mattermost bots and integrations in C#.
It provides a clean, strongly typed wrapper around the Mattermost API, including messages, channels, users, file uploads, post props, and real-time WebSocket events. The client supports token-based authentication, username/password login, automatic WebSocket reconnects, custom HttpClient transport, and configurable incoming message filtering.
For a detailed endpoint map and implementation status, see API coverage.
Install the package from NuGet:
dotnet add package Mattermost.NETusing Mattermost;
const string server = "https://mm.your-server.com";
const string token = "your-personal-access-token-or-bot-token";
const string channelId = "target-channel-id";
using var client = new MattermostClient(server, token);
await client.CreatePostAsync(channelId, "Hello from Mattermost.NET!");using Mattermost;
const string server = "https://mm.your-server.com";
const string token = "your-personal-access-token-or-bot-token";
using var client = new MattermostClient(server, token);
var me = await client.GetMeAsync();
Console.WriteLine($"Authenticated as @{me.Username}");When a token is provided through the constructor, Mattermost.NET validates and caches the current user on the first authorized API call.
using Mattermost;
const string server = "https://mm.your-server.com";
using var client = new MattermostClient(server);
var me = await client.LoginAsync("username-or-email", "password");
Console.WriteLine($"Authenticated as @{me.Username}");You cannot use LoginAsync on a client that was constructed with an API token.
Call StartReceivingAsync to connect to the Mattermost WebSocket API and receive events.
using Mattermost;
const string server = "https://mm.your-server.com";
const string token = "your-personal-access-token-or-bot-token";
using var client = new MattermostClient(server, token);
client.OnConnected += (_, e) =>
{
Console.WriteLine($"Connected to {e.Uri}");
};
client.OnDisconnected += (_, e) =>
{
Console.WriteLine($"Disconnected: {e.CloseStatusDescription}");
};
client.OnLogMessage += (_, e) =>
{
Console.WriteLine(e.Message);
};
client.OnMessageReceived += (_, e) =>
{
string text = e.Message.Post.Text ?? string.Empty;
if (string.Equals(text, "ping", StringComparison.OrdinalIgnoreCase))
{
_ = e.Client.CreatePostAsync(e.Message.Post.ChannelId, "pong");
}
};
await client.StartReceivingAsync();
Console.WriteLine("Bot is running. Press Enter to stop.");
Console.ReadLine();
await client.StopReceivingAsync();The client automatically reconnects when the WebSocket connection is lost. You only need StartReceivingAsync when you want to receive WebSocket events; regular REST API calls work without it.
By default, MattermostClient ignores messages authored by the currently authorized user. This prevents common bot loops where a bot reacts to its own posts.
client.Options.IgnoreOwnMessages = true; // defaultTo receive the bot's own messages too, disable this option:
client.Options.IgnoreOwnMessages = false;MessageEventArgs.IsCurrentUser tells whether the received message was authored by the currently authorized user.
client.OnMessageReceived += (_, e) =>
{
if (e.IsCurrentUser)
{
Console.WriteLine("Received my own message.");
}
};You can also provide a custom incoming message filter. Return true to dispatch OnMessageReceived; return false to suppress the event.
client.Options.IncomingMessageFilter = e =>
{
string text = e.Message.Post.Text ?? string.Empty;
return text.StartsWith("!", StringComparison.Ordinal);
};The custom filter runs after the built-in own-message filter. If you want the custom filter to evaluate own messages, set IgnoreOwnMessages to false.
client.Options.IgnoreOwnMessages = false;
client.Options.IncomingMessageFilter = e => !e.IsCurrentUser;You can pass your own HttpClient when you need custom transport behavior, such as a proxy, timeout, custom handler, logging handler, or IHttpClientFactory integration.
using Mattermost;
using System.Net;
using System.Net.Http;
const string server = "https://mm.your-server.com";
const string token = "your-personal-access-token-or-bot-token";
var handler = new HttpClientHandler
{
Proxy = new WebProxy("http://corp-proxy:8080")
};
using var httpClient = new HttpClient(handler)
{
Timeout = TimeSpan.FromSeconds(20)
};
using var client = new MattermostClient(server, token, httpClient);
var me = await client.GetMeAsync();
Console.WriteLine($"Authenticated as @{me.Username}");When an external HttpClient is provided, Mattermost.NET uses it only as transport and does not dispose it. The Mattermost server URL still comes from server / serverUri; HttpClient.BaseAddress is not used as the Mattermost server identity.
Mattermost.NET sends authentication per request and does not mutate HttpClient.DefaultRequestHeaders.Authorization. Any default headers configured by the caller remain owned by the caller.
Available constructors:
new MattermostClient();
new MattermostClient(string serverUrl);
new MattermostClient(Uri serverUri);
new MattermostClient(string serverUrl, string apiKey);
new MattermostClient(Uri serverUri, string apiKey);
new MattermostClient(string serverUrl, HttpClient httpClient);
new MattermostClient(Uri serverUri, HttpClient httpClient);
new MattermostClient(string serverUrl, string apiKey, HttpClient httpClient);
new MattermostClient(Uri serverUri, string apiKey, HttpClient httpClient);await client.CreatePostAsync(channelId, "Hello, World!");await client.CreatePostAsync(
channelId: channelId,
message: "Thread reply",
replyToPostId: rootPostId);await client.UpdatePostAsync(postId, "Updated message text");await client.DeletePostAsync(postId);var file = await client.UploadFileAsync(channelId, "report.pdf", stream);
await client.CreatePostAsync(
channelId: channelId,
message: "Uploaded report",
files: new[] { file.Id });var posts = await client.GetChannelPostsAsync(channelId, perPage: 60);
foreach (var post in posts.Posts.Values)
{
Console.WriteLine(post.Text);
}var me = await client.GetMeAsync();
Console.WriteLine(me.Username);var byId = await client.GetUserAsync(userId);
var byUsername = await client.GetUserByUsernameAsync("username");
var byEmail = await client.GetUserByEmailAsync("user@example.com");var channel = await client.GetChannelAsync(channelId);
var found = await client.FindChannelByNameAsync(teamId, "town-square");
var direct = await client.CreateDirectChannelAsync(userId);Mattermost.NET supports Mattermost post props, including attachments and interactive action metadata.
using Mattermost.Models.Posts;
var props = new PostProps();
props.Attachments.Add(new PostPropsAttachment
{
Text = "Attachment text"
});
await client.CreatePostAsync(
channelId: channelId,
message: "Message with props",
props: props);Raw props are also supported when you need to send a custom JSON property bag.
var rawProps = new Dictionary<string, object>
{
["custom_key"] = "custom value"
};
await client.CreatePostWithRawPropsAsync(
channelId: channelId,
message: "Message with raw props",
rawProps: rawProps);Message actions support Mattermost buttons and select menus.
using Mattermost.Models.Posts;
using System.Collections.Generic;
var props = new PostProps();
props.Attachments.Add(new PostPropsAttachment
{
Text = "Choose an option",
Actions =
{
new PostPropsSelectAction
{
Id = "actionoptions",
Name = "Select an option...",
DefaultOption = "opt2",
Integration = new Integration
{
Url = "https://example.com/actionoptions",
Context =
{
["action"] = "do_something"
}
},
Options = new List<PostActionOption>
{
new PostActionOption("Option1", "opt1"),
new PostActionOption("Option2", "opt2"),
new PostActionOption("Option3", "opt3")
}
}
}
});
await client.CreatePostAsync(channelId, "Message with a select menu", props: props);For server-populated menus, set DataSource instead of Options:
new PostPropsSelectAction
{
Id = "actionusers",
Name = "Select a user...",
DataSource = PostActionDataSource.Users,
Integration = new Integration
{
Url = "https://example.com/actionusers"
}
};using Mattermost.Builders;
using Mattermost.Enums;
await new PostBuilder()
.ToChannel(channelId)
.AddText("Important message")
.SetPriority(MessagePriority.Important)
.SendMessageAsync(client);using Mattermost.Builders;
using Mattermost.Models.Enums;
string table = new TableMarkdownBuilder(3, TableAlignment.Center)
.AddHeader("Name", "Status", "Score")
.AddRow("Build", "OK", 100)
.AddRow("Tests", "OK", 100)
.ToString();
await client.CreatePostAsync(channelId, table);The public API is exposed through IMattermostClient and includes:
- authentication and logout;
- current user, users by id, username, or email;
- create, update, delete, read, and list posts;
- thread posts;
- channel lookup, creation, archiving, and membership changes;
- direct and group channels;
- file upload, download, streaming, and metadata;
- Calls plugin channel state;
- WebSocket events for messages, status changes, connection changes, and raw events.
See IMattermostClient for the full list of implemented methods.
Missing a Mattermost API method? Please open an issue with the exact Mattermost endpoint or scenario you need:
https://github.com/bvdcode/Mattermost.NET/issues/new?template=Blank+issue
Mattermost.NET targets both .NET Standard 2.0 and .NET Standard 2.1.
Distributed under the MIT License. See LICENSE.md for more information.