Swift package for sending messages to Mattermost via Incoming Webhooks with full support for Slack-compatible attachments and Mattermost-specific features.
Also check out SlackKit - A companion package for sending messages to Slack with full Block Kit support.
- Modern Result Builder API - Declarative DSL for building messages with attachments
- Type-Safe - Full Codable support with compile-time safety
- Attachments - Slack-compatible attachment support for rich messages
- Mattermost-Specific - Support for
props.cardand message priority - Conditional Logic - Native if/else and for-in support in builders
- Swift 6 - Built with Swift 6, async/await, and strict concurrency
- macOS 12.0+
- iOS 15.0+
- tvOS 15.0+
- watchOS 8.0+
- Swift 6.0+
Add MattermostKit to your Package.swift file:
dependencies:[.package(url:"https://github.com/diegotl/MattermostKit.git", from:"1.0.0")]Or add it directly in Xcode:
- File → Add Package Dependencies
- Enter the repository URL
- Select the version rule
import MattermostKit
// Create a webhook client
letclient=tryMattermostWebhookClient.create(
webhookURLString:"https://mattermost.server.com/hooks/YOUR/WEBHOOK/URL")
// Send a simple message
tryawait client.send(Message(text:"Hello, Mattermost!"))letmessage=Message(text:"Deployment completed successfully!")tryawait client.send(message)letmessage=Message(
text:"Deployment complete!",
username:"DeployBot",
iconEmoji:":rocket:"){Attachment(color:"#36a64f", title:"Build #123", text:"Succeeded in 5m 32s"){Field("Branch", value:"main")Field("Commit", value:"abc123")Field("Duration", value:"5m 32s")Field("Status", value:":white_check_mark: Success")}}tryawait client.send(message)letmessage=Message(
text:"Deployment Summary",
username:"DeployBot",
iconEmoji:":rocket:"){Attachment(color:"#36a64f", title:"Success"){Field("Environment", value:"production")Field("Duration", value:"5m 32s")}Attachment(color:"#36a64f", title:"Build Info"){Field("Branch", value:"main")Field("Commit", value:"abc123")Field("Tests", value:"156 passed")}}lethasWarnings=truelethasErrors=falseletmessage=Message(username:"CIBot"){Attachment(color:"#36a64f", title:"Build Summary"){Field("Status", value:"Success")}if hasWarnings {Attachment(color:"#ffaa00", title:"Warnings"){Field("Count", value:"3")}}if hasErrors {Attachment(color:"#ff0000", title:"Errors"){Field("Count", value:"1")}}}letmessage=Message{Attachment(text:"Deploy to production?"){Field("Environment", value:"production")Field("Version", value:"v2.4.1")}Attachment.actions{Button(text:"Approve", style:"primary", url: approveURL)Button(text:"Reject", style:"danger", url: rejectURL)Button(text:"Defer", style:"default", url: deferURL)}}lettestResults=[("TestLogin","passed"),("TestAPI","passed"),("TestUI","failed")]letmessage=Message{Attachment(title:"Test Results"){for(name, result)in testResults {Field(name, value: result, short:true)}}}// Simple card with static text
letmessage=Message(
text:"We won a new deal!",
props:Props(card:""" Salesforce Opportunity Information: **Amount:** $300,020.00 **Close Date:** 2025-01-15 **Sales Rep:** John Doe"""))
// Card with dynamic properties using builder
letmessage=Message(
text:"Deal updated!",
props:Props(card:"Deal Information"){Property("amount", value:300020)Property("stage", value:"Proposal")Property("is_closed", value:false)})
// Conditional properties
letincludeDetails=trueletmessage=Message(
text:"Opportunity created",
props:Props(card:"Sales Info"){Property("opportunity_id", value:"12345")Property("account", value:"Acme Corp")if includeDetails {Property("estimated_value", value:50000)Property("probability", value:0.75)}})// Urgent priority with acknowledgment
letmessage=Message(
text:"Critical incident!",
priority:Priority(
priority:.urgent,
requestedAck:true,
persistentNotifications:true))
// Important priority
letmessage=Message(
text:"Important announcement",
priority:Priority(priority:.important))// Simple confirmation
letconfirm=Confirmation(
title:"Are you sure?",
text:"This will deploy to production",
confirmText:"Deploy",
denyText:"Cancel")
// Using result builder for custom buttons
letconfirm=Confirmation(
title:"Confirm Deployment",
text:"This action cannot be undone"){ConfirmButton(text:"Yes, Deploy", style:"primary")DenyButton(text:"No, Cancel")}letmessage=Message{Attachment.actions{Button(text:"Deploy", style:"primary", url: deployURL, confirm: confirm)Button(text:"Cancel", style:"danger", url: cancelURL)}}letmessage=Message(
username:"CI/CD Bot",
iconURL:"https://example.com/ci-icon.png",
text:"Build notification"){Attachment(
color:"#36a64f",
title:"Build #123",
pretext:"Build process completed",
authorName:"Jenkins",
authorLink:"https://jenkins.example.com",
authorIcon:"https://example.com/jenkins-icon.png",
titleLink:"https://jenkins.example.com/job/123",
imageURL:"https://example.com/build-graph.png",
thumbURL:"https://example.com/thumb.png",
footer:"Jenkins CI",
footerIcon:"https://example.com/jenkins.png"){Field("Branch", value:"feature/new-api", short:true)Field("Commit", value:"a1b2c3d", short:true)Field("Duration", value:"5m 32s", short:true)Field("Tests", value:"156 passed", short:true)}}letconfirm=Confirmation(
title:"Are you sure?",
text:"This will deploy to production",
confirmText:"Deploy",
denyText:"Cancel")letmessage=Message{Attachment.actions{Button(text:"Deploy", style:"primary", url: deployURL, confirm: confirm)Button(text:"Cancel", style:"danger", url: cancelURL)}}Message(
text:"Message text",
username:"Bot Name",
iconEmoji:":robot_face:"){
// Attachments via @AttachmentBuilder
Attachment(title:"Title"){...}}Attachment(
color:"#36a64f",
title:"Title",
text:"Description"){
// Fields via @FieldBuilder
Field("Key", value:"Value")}Attachment.actions{
// Actions via @ActionBuilder
Button(text:"Click", url: url)}Props(card:"Card content"){
// Properties via @PropsBuilder
Property("key", value:"value")Property("number", value:42)}Confirmation(
title:"Confirm?",
text:"Are you sure?"){
// Components via @ConfirmationBuilder
ConfirmButton(text:"Yes", style:"primary")DenyButton(text:"No")}// Field with short=true by default
Field("Branch", value:"main")
// Button action
Button(text:"Approve", style:"primary", url:"https://example.com")
// Properties (various types)
Property("name", value:"text")Property("count", value:42)Property("enabled", value:true)Property("metadata", value:["key":.string("value")])
// Confirmation components
ConfirmButton(text:"Yes", style:"primary")DenyButton(text:"No")
// Actions-only attachment
Attachment.actions{Button(text:"View", url: viewURL)Button(text:"Dismiss", style:"danger", url: dismissURL)}do{tryawait client.send(message)}catchMattermostError.invalidURL(let url){print("Invalid URL: \(url)")}catchMattermostError.invalidResponse(let code,let body){print("HTTP \(code): \(body ??"No body")")}catchMattermostError.encodingError(let error){print("Failed to encode message: \(error)")}catchMattermostError.networkError(let error){print("Network error: \(error)")}publicstructMessage:Sendable,Codable{publicvartext:String? // Markdown-formatted message
publicvarchannel:String? // Override default channel
publicvarusername:String? // Override bot username
publicvariconEmoji:String? // e.g., ":rocket:"
publicvariconURL:String? // Override bot icon with image URL
publicvarattachments:[Attachment]? // Message attachments
publicvarprops:Props? // Mattermost metadata
publicvartype:String? // Post type (must begin with "custom_")
publicvarpriority:Priority? // Message priority
}publicstructAttachment:Sendable,Codable{publicvarfallback:String? // Plain-text summary
publicvarcolor:String? // Hex color code
publicvarpretext:String? // Text above attachment
publicvarauthorName:String? // Author name
publicvarauthorLink:String? // Author link
publicvarauthorIcon:String? // Author icon URL
publicvartitle:String? // Attachment title
publicvartitleLink:String? // Title link
publicvartext:String? // Attachment text (Markdown)
publicvarfields:[AttachmentField]? // Attachment fields
publicvarimageURL:String? // Image URL
publicvarthumbURL:String? // Thumbnail URL
publicvarfooter:String? // Footer text
publicvarfooterIcon:String? // Footer icon URL
publicvaractions:[Action]? // Interactive buttons
}publicstructProps:Sendable,Codable{publicvarcard:String? // RHS sidebar content
publicvaradditionalProperties:[String:AnyCodable]?}publicstructPriority:Sendable,Codable{publicenumLevel:String,Codable{case important // Important message
case urgent // Urgent message
}publicvarpriority:Level // Priority level
publicvarrequestedAck:Bool? // Request acknowledgment
publicvarpersistentNotifications:Bool? // Persistent notifications
}| Feature | SlackKit | MattermostKit |
|---|---|---|
| Block Kit | ✅ Full support | ❌ NOT supported |
| Message Threading | ✅ thread_ts | ❌ Not available |
| Attachments | ✅ Slack format | ✅ Slack-compatible |
| Custom Metadata | ❌ No | ✅ props.card |
| Message Priority | ❌ No | ✅ priority |
Note: MattermostKit does NOT support Slack's Block Kit. Use Markdown formatting in the text field and attachments for rich messages.
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.
- Built with Swift
- Uses Mattermost Incoming Webhooks
- Inspired by SlackKit
