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.NETusingMattermost;conststringserver="https://mm.your-server.com";conststringtoken="your-personal-access-token-or-bot-token";conststringchannelId="target-channel-id";usingvarclient=newMattermostClient(server,token);awaitclient.CreatePostAsync(channelId,"Hello from Mattermost.NET!");usingMattermost;conststringserver="https://mm.your-server.com";conststringtoken="your-personal-access-token-or-bot-token";usingvarclient=newMattermostClient(server,token);varme=awaitclient.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.
usingMattermost;conststringserver="https://mm.your-server.com";usingvarclient=newMattermostClient(server);varme=awaitclient.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.
usingMattermost;conststringserver="https://mm.your-server.com";conststringtoken="your-personal-access-token-or-bot-token";usingvarclient=newMattermostClient(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)=>{stringtext=e.Message.Post.Text??string.Empty;if(string.Equals(text,"ping",StringComparison.OrdinalIgnoreCase)){_=e.Client.CreatePostAsync(e.Message.Post.ChannelId,"pong");}};awaitclient.StartReceivingAsync();Console.WriteLine("Bot is running. Press Enter to stop.");Console.ReadLine();awaitclient.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 =>{stringtext=e.Message.Post.Text??string.Empty;returntext.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.
usingMattermost;usingSystem.Net;usingSystem.Net.Http;conststringserver="https://mm.your-server.com";conststringtoken="your-personal-access-token-or-bot-token";varhandler=newHttpClientHandler{Proxy=newWebProxy("http://corp-proxy:8080")};usingvarhttpClient=newHttpClient(handler){Timeout=TimeSpan.FromSeconds(20)};usingvarclient=newMattermostClient(server,token,httpClient);varme=awaitclient.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:
newMattermostClient();newMattermostClient(stringserverUrl);newMattermostClient(UriserverUri);newMattermostClient(stringserverUrl,stringapiKey);newMattermostClient(UriserverUri,stringapiKey);newMattermostClient(stringserverUrl,HttpClienthttpClient);newMattermostClient(UriserverUri,HttpClienthttpClient);newMattermostClient(stringserverUrl,stringapiKey,HttpClienthttpClient);newMattermostClient(UriserverUri,stringapiKey,HttpClienthttpClient);awaitclient.CreatePostAsync(channelId,"Hello, World!");usingMattermost.Enums;awaitclient.CreatePostAsync(channelId,"@username Please acknowledge this incident.",priority:MessagePriority.Urgent,requestedAck:true,persistentNotifications:true);Acknowledgement requests require Important or Urgent priority. Persistent notifications require Urgent priority and may also depend on the Mattermost server license, configuration, and mention rules.
awaitclient.CreatePostAsync(channelId:channelId,message:"Thread reply",replyToPostId:rootPostId);awaitclient.UpdatePostAsync(postId,"Updated message text");awaitclient.DeletePostAsync(postId);varfile=awaitclient.UploadFileAsync(channelId,"report.pdf",stream);awaitclient.CreatePostAsync(channelId:channelId,message:"Uploaded report",files:new[]{file.Id});varposts=awaitclient.GetChannelPostsAsync(channelId,perPage:60);foreach(varpostinposts.Posts.Values){Console.WriteLine(post.Text);}varme=awaitclient.GetMeAsync();Console.WriteLine(me.Username);varbyId=awaitclient.GetUserAsync(userId);varbyUsername=awaitclient.GetUserByUsernameAsync("username");varbyEmail=awaitclient.GetUserByEmailAsync("user@example.com");varchannel=awaitclient.GetChannelAsync(channelId);varfound=awaitclient.FindChannelByNameAsync(teamId,"town-square");vardirect=awaitclient.CreateDirectChannelAsync(userId);boolcallActive=awaitclient.GetCallActiveAsync(channelId);if(callActive){awaitclient.EndCallAsync(channelId);}These methods require the Mattermost Calls plugin. EndCallAsync expects a channel identifier, despite the route parameter being named call_id by the plugin. Ending a call also requires host permissions.
Mattermost.NET supports Mattermost post props, including attachments and interactive action metadata.
usingMattermost.Models.Posts;varprops=newPostProps();props.Attachments.Add(newPostPropsAttachment{Text="Attachment text"});awaitclient.CreatePostAsync(channelId:channelId,message:"Message with props",props:props);Raw props are also supported when you need to send a custom JSON property bag.
varrawProps=newDictionary<string,object>{["custom_key"]="custom value"};awaitclient.CreatePostWithRawPropsAsync(channelId:channelId,message:"Message with raw props",rawProps:rawProps);Message actions support Mattermost buttons and select menus.
usingMattermost.Models.Posts;usingSystem.Collections.Generic;varprops=newPostProps();props.Attachments.Add(newPostPropsAttachment{Text="Choose an option",Actions={newPostPropsSelectAction{Id="actionoptions",Name="Select an option...",DefaultOption="opt2",Integration=newIntegration{Url="https://example.com/actionoptions",Context={["action"]="do_something"}},Options=newList<PostActionOption>{newPostActionOption("Option1","opt1"),newPostActionOption("Option2","opt2"),newPostActionOption("Option3","opt3")}}}});awaitclient.CreatePostAsync(channelId,"Message with a select menu",props:props);For server-populated menus, set DataSource instead of Options:
newPostPropsSelectAction{Id="actionusers",Name="Select a user...",DataSource=PostActionDataSource.Users,Integration=newIntegration{Url="https://example.com/actionusers"}};When a user clicks a button or selects a menu option, Mattermost sends an HTTP POST request to the action's Integration.Url. Host that URL in your application and deserialize the JSON body with PostActionIntegrationRequest.
app.MapPost("/mattermost/actions",(PostActionIntegrationRequestrequest)=>{stringaction=request.Context["action"].GetString()??string.Empty;returnResults.Ok(new{ephemeral_text=$"Received {action}"});});Interactive message actions and slash commands can open Mattermost interactive dialogs. Use the action payload's trigger_id, build an InteractiveDialog, and call OpenInteractiveDialogAsync.
usingMattermost;usingMattermost.Models.Dialogs;usingMattermost.Models.Posts;usingSystem.Collections.Generic;app.MapPost("/mattermost/actions",async(PostActionIntegrationRequestrequest,IMattermostClientmattermostClient)=>{InteractiveDialogdialog=newInteractiveDialog{Title="Create ticket",Elements=newList<InteractiveDialogElement>{newInteractiveDialogElement{DisplayName="Summary",Name="summary",Type=InteractiveDialogElementType.Text}}};awaitmattermostClient.OpenInteractiveDialogAsync(request.TriggerId,"https://example.com/mattermost/dialogs/submit",dialog);returnResults.Ok();});The submit URL is your application endpoint. Deserialize the submitted payload with InteractiveDialogSubmissionRequest and return InteractiveDialogResponse when validation errors or multi-step form updates are needed.
usingMattermost.Models.Dialogs;usingSystem.Collections.Generic;usingSystem.Text.Json;app.MapPost("/mattermost/dialogs/submit",(InteractiveDialogSubmissionRequestrequest)=>{JsonElementsummaryElement;if(!request.Submission.TryGetValue("summary",outsummaryElement)||string.IsNullOrWhiteSpace(summaryElement.GetString())){returnResults.Ok(newInteractiveDialogResponse{Errors=newDictionary<string,string>{["summary"]="Summary is required."}});}returnResults.Ok(newInteractiveDialogResponse{Type="ok"});});For dynamic selects, set InteractiveDialogElement.DataSource to InteractiveDialogDataSource.Dynamic, set DataSourceUrl, and return InteractiveDialogLookupResponse from that lookup endpoint. For refresh or multi-step flows, set InteractiveDialog.SourceUrl and return InteractiveDialogResponse with Type = "form" and the replacement Form.
See Mattermost's interactive dialogs documentation for the full server-side flow and field behavior.
These types help you implement the HTTP endpoint for a custom slash command; they do not register the command in Mattermost. Configure the command's request URL and HTTP method in Mattermost, then use SlashCommandRequestParser to decode the URL-encoded POST body or GET query string and return a SlashCommandResponse as JSON.
For a POST command in an ASP.NET Core minimal API:
usingMattermost.Helpers;usingMattermost.Models.SlashCommands;usingSystem.IO;app.MapPost("/mattermost/commands/weather",async(HttpRequesthttpRequest)=>{usingStreamReaderreader=newStreamReader(httpRequest.Body);stringencodedParameters=awaitreader.ReadToEndAsync();SlashCommandRequestcommand=SlashCommandRequestParser.Parse(encodedParameters);// Validate the command token or Authorization header before processing the request.returnResults.Json(newSlashCommandResponse{ResponseType=SlashCommandResponseType.InChannel,Text=$"Weather request: {command.Text}"});});For a GET command, pass the raw query string to the same parser:
app.MapGet("/mattermost/commands/weather",(HttpRequesthttpRequest)=>{stringencodedParameters=httpRequest.QueryString.Value??string.Empty;SlashCommandRequestcommand=SlashCommandRequestParser.Parse(encodedParameters);returnResults.Json(newSlashCommandResponse{ResponseType=SlashCommandResponseType.Ephemeral,Text=$"Weather request: {command.Text}"});});SlashCommandRequest.RootId identifies the parent post when a command is invoked in a thread, while UserMentions and ChannelMentions map names in the command text to Mattermost identifiers. To return multiple immediate posts, add SlashCommandResponseItem values to ExtraResponses:
returnResults.Json(newSlashCommandResponse{ResponseType=SlashCommandResponseType.InChannel,Text="Weather report",ExtraResponses=newList<SlashCommandResponseItem>{newSlashCommandResponseItem{ResponseType=SlashCommandResponseType.Ephemeral,Text="Only the command author can see this detail."}}});For work that completes after the initial request, send a SlashCommandResponse to the request's ResponseUrl using an application-managed HttpClient:
awaithttpClient.PostAsJsonAsync(command.ResponseUrl,newSlashCommandResponse{ResponseType=SlashCommandResponseType.InChannel,Text="The delayed weather report is ready."});Validate the command token or authorization header before processing either request method. See Mattermost's custom slash commands documentation for command registration, request validation, and response behavior.
usingMattermost.Builders;usingMattermost.Enums;awaitnewPostBuilder().ToChannel(channelId).AddText("@username Please acknowledge this incident.").SetPriority(MessagePriority.Urgent,requestedAck:true,persistentNotifications:true).SendMessageAsync(client);usingMattermost.Builders;usingMattermost.Models.Enums;stringtable=newTableMarkdownBuilder(3,TableAlignment.Center).AddHeader("Name","Status","Score").AddRow("Build","OK",100).AddRow("Tests","OK",100).ToString();awaitclient.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, active call checks, and host call termination;
- 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.