Skip to content

Modern Swift package for Mattermost Incoming Webhooks with attachments, props.card, and message priority support

License

Notifications You must be signed in to change notification settings

diegotl/MattermostKit

Repository files navigation

MattermostKit

MattermostKit Logo

CI Version License Platform Swift Dependencies

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.

Features

  • 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.card and message priority
  • Conditional Logic - Native if/else and for-in support in builders
  • Swift 6 - Built with Swift 6, async/await, and strict concurrency

Requirements

  • macOS 12.0+
  • iOS 15.0+
  • tvOS 15.0+
  • watchOS 8.0+
  • Swift 6.0+

Installation

Swift Package Manager

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:

  1. File → Add Package Dependencies
  2. Enter the repository URL
  3. Select the version rule

Quick Start

import MattermostKit

// Create a webhook client
let client = try MattermostWebhookClient.create(
    webhookURLString: "https://mattermost.server.com/hooks/YOUR/WEBHOOK/URL"
)

// Send a simple message
try await client.send(Message(text: "Hello, Mattermost!"))

Result Builder API

Simple Message

let message = Message(text: "Deployment completed successfully!")
try await client.send(message)

Message with Attachments

let message = 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")
    }
}
try await client.send(message)

Multiple Attachments

let message = 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")
    }
}

Conditional Attachments

let hasWarnings = true
let hasErrors = false

let message = 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")
        }
    }
}

Message with Actions

let message = 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)
    }
}

Dynamic Fields with Loops

let testResults = [
    ("TestLogin", "passed"),
    ("TestAPI", "passed"),
    ("TestUI", "failed")
]

let message = Message {
    Attachment(title: "Test Results") {
        for (name, result) in testResults {
            Field(name, value: result, short: true)
        }
    }
}

Message with Card Props

// Simple card with static text
let message = 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
let message = Message(
    text: "Deal updated!",
    props: Props(card: "Deal Information") {
        Property("amount", value: 300020)
        Property("stage", value: "Proposal")
        Property("is_closed", value: false)
    }
)

// Conditional properties
let includeDetails = true
let message = 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)
        }
    }
)

Message with Priority

// Urgent priority with acknowledgment
let message = Message(
    text: "Critical incident!",
    priority: Priority(
        priority: .urgent,
        requestedAck: true,
        persistentNotifications: true
    )
)

// Important priority
let message = Message(
    text: "Important announcement",
    priority: Priority(priority: .important)
)

Confirmation Dialogs for Actions

// Simple confirmation
let confirm = Confirmation(
    title: "Are you sure?",
    text: "This will deploy to production",
    confirmText: "Deploy",
    denyText: "Cancel"
)

// Using result builder for custom buttons
let confirm = Confirmation(
    title: "Confirm Deployment",
    text: "This action cannot be undone"
) {
    ConfirmButton(text: "Yes, Deploy", style: "primary")
    DenyButton(text: "No, Cancel")
}

let message = Message {
    Attachment.actions {
        Button(text: "Deploy", style: "primary", url: deployURL, confirm: confirm)
        Button(text: "Cancel", style: "danger", url: cancelURL)
    }
}

Complex Message with All Features

let message = 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)
    }
}

Confirmation Dialogs for Actions

let confirm = Confirmation(
    title: "Are you sure?",
    text: "This will deploy to production",
    confirmText: "Deploy",
    denyText: "Cancel"
)

let message = Message {
    Attachment.actions {
        Button(text: "Deploy", style: "primary", url: deployURL, confirm: confirm)
        Button(text: "Cancel", style: "danger", url: cancelURL)
    }
}

Builder API Reference

Message Builder

Message(
    text: "Message text",
    username: "Bot Name",
    iconEmoji: ":robot_face:"
) {
    // Attachments via @AttachmentBuilder
    Attachment(title: "Title") { ... }
}

Attachment Builder

Attachment(
    color: "#36a64f",
    title: "Title",
    text: "Description"
) {
    // Fields via @FieldBuilder
    Field("Key", value: "Value")
}

Actions Builder

Attachment.actions {
    // Actions via @ActionBuilder
    Button(text: "Click", url: url)
}

Props Builder

Props(card: "Card content") {
    // Properties via @PropsBuilder
    Property("key", value: "value")
    Property("number", value: 42)
}

Confirmation Builder

Confirmation(
    title: "Confirm?",
    text: "Are you sure?"
) {
    // Components via @ConfirmationBuilder
    ConfirmButton(text: "Yes", style: "primary")
    DenyButton(text: "No")
}

Convenience Functions

// 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)
}

Error Handling

do {
    try await client.send(message)
} catch MattermostError.invalidURL(let url) {
    print("Invalid URL: \(url)")
} catch MattermostError.invalidResponse(let code, let body) {
    print("HTTP \(code): \(body ?? "No body")")
} catch MattermostError.encodingError(let error) {
    print("Failed to encode message: \(error)")
} catch MattermostError.networkError(let error) {
    print("Network error: \(error)")
}

API Reference

Message

public struct Message: Sendable, Codable {
    public var text: String?              // Markdown-formatted message
    public var channel: String?           // Override default channel
    public var username: String?          // Override bot username
    public var iconEmoji: String?         // e.g., ":rocket:"
    public var iconURL: String?           // Override bot icon with image URL
    public var attachments: [Attachment]?  // Message attachments
    public var props: Props?              // Mattermost metadata
    public var type: String?              // Post type (must begin with "custom_")
    public var priority: Priority?        // Message priority
}

Attachment

public struct Attachment: Sendable, Codable {
    public var fallback: String?          // Plain-text summary
    public var color: String?             // Hex color code
    public var pretext: String?           // Text above attachment
    public var authorName: String?        // Author name
    public var authorLink: String?        // Author link
    public var authorIcon: String?        // Author icon URL
    public var title: String?             // Attachment title
    public var titleLink: String?         // Title link
    public var text: String?              // Attachment text (Markdown)
    public var fields: [AttachmentField]? // Attachment fields
    public var imageURL: String?          // Image URL
    public var thumbURL: String?          // Thumbnail URL
    public var footer: String?            // Footer text
    public var footerIcon: String?        // Footer icon URL
    public var actions: [Action]?         // Interactive buttons
}

Props (Mattermost-Specific)

public struct Props: Sendable, Codable {
    public var card: String?              // RHS sidebar content
    public var additionalProperties: [String: AnyCodable]?
}

Priority (Mattermost-Specific)

public struct Priority: Sendable, Codable {
    public enum Level: String, Codable {
        case important                    // Important message
        case urgent                       // Urgent message
    }

    public var priority: Level            // Priority level
    public var requestedAck: Bool?        // Request acknowledgment
    public var persistentNotifications: Bool?  // Persistent notifications
}

Differences from Slack

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.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

Resources

About

Modern Swift package for Mattermost Incoming Webhooks with attachments, props.card, and message priority support

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages