Skip to content

Send beautiful Slack messages from Swift. Webhooks, Block Kit, and async/await.

License

Notifications You must be signed in to change notification settings

diegotl/SlackKit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SlackKit

SlackKit Logo

CI Version License Platform Swift Dependencies

Swift package for sending messages to Slack via Incoming Webhooks with full support for Block Kit.

Also check out MattermostKit - A companion package for sending messages to Mattermost with Slack-compatible attachments.

Features

  • Modern Swift API - Built with Swift 6, async/await, and strict concurrency
  • Type-Safe - Full Codable support with compile-time safety
  • Block Kit - Complete support for Slack's Block Kit API
  • Flexible - Send simple text messages or rich interactive messages

Requirements

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

Installation

Swift Package Manager

Add SlackKit to your Package.swift file:

dependencies: [
    .package(url: "https://github.com/diegotl/SlackKit.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 SlackKit

// Create a webhook client
let client = try SlackWebhookClient.create(
    webhookURLString: "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
)

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

Convenience Builder API

SlackKit includes a builder API for cleaner, more readable message construction:

// Clean, declarative syntax
let message = Message {
    Header("Deployment Complete!")
    Section("Build *#123* was deployed to *production*")
    Divider()
    SectionBlock(
        fields: [
            .markdown("*Environment:*\nProduction"),
            .markdown("*Version:*\nv2.4.1")
        ]
    )
}
try await client.send(message)

Usage

Simple Text Message

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

Message with Blocks

let message = Message {
    Header("Deployment Complete!")
    Section(markdown: "Build *#123* was deployed to *production*")
    Divider()

    Section {
        Field.markdown("*Environment:*\nProduction")
        Field.markdown("*Version:*\nv2.4.1")
        Field.markdown("*Duration:*\n5m 32s")
        Field.markdown("*Status:*\n:white_check_mark: Success")
    }
}
try await client.send(message)

With custom username and icon:

let message = Message(
    username: "DeployBot",
    iconEmoji: ":rocket:"
) {
    Header("Deployment Complete!")
    Section("Build *#123* was deployed to *production*")
    Divider()
}
try await client.send(message)

Message with Actions

let message = Message(text: "Approval required for production deployment") {
    Section("Deploy to production?")
    Actions {
        ButtonElement(text: .plainText("Approve"), style: .primary, value: "approve")
        ButtonElement(text: .plainText("Reject"), style: .danger, value: "reject")
    }
}
try await client.send(message)

Message with Attachments (Legacy)

let message = Message(
    text: "Build results",
    attachments: [
        Attachment(
            color: "good",
            title: "Build #123",
            text: "Succeeded in 5m 32s",
            fields: [
                AttachmentField(title: "Branch", value: "main", short: true),
                AttachmentField(title: "Commit", value: "abc123", short: true)
            ]
        )
    ]
)
try await client.send(message)

Threaded Message

let message = Message(
    text: "This is a threaded reply",
    threadTimestamp: "1234567890.123456"
)
try await client.send(message)

Block Types

Section Block

Text sections with optional fields:

Section("Some *formatted* text")
// Or with markdown
Section(markdown: "Some *formatted* text")

With fields using the result builder:

Section {
    Field.markdown("*Field 1*\nValue 1")
    Field.plainText("Field 2")
}

Header Block

Large header text:

Header("Important Announcement")

Divider Block

Horizontal line divider:

Divider()

Image Block

Display an image:

Image(url: "https://example.com/image.png", altText: "An example image")

Actions Block

Interactive buttons:

Actions {
    ButtonElement(text: .plainText("Click Me"), actionID: "button_1", value: "button_value", style: .primary)
}

The builder also supports conditionals and loops:

Actions {
    ButtonElement(text: .plainText("Approve"), actionID: "approve", value: "yes")

    if needsReview {
        ButtonElement(text: .plainText("Request Review"), actionID: "review", value: "review")
    }

    for option in options {
        ButtonElement(text: .plainText(option), actionID: "opt_\(option)", value: option)
    }
}

Context Block

Contextual information with text and images:

// Simple text context
Context("Created by @john", "2 minutes ago")

// Or with elements using the builder
Context {
    TextContextElement(text: "Created by @john")
    ImageContextElement(imageURL: "https://example.com/avatar.png", altText: "Avatar")
}

Input Block (Modals)

Input blocks for collecting user input in modals:

Input(
    label: "Task description",
    element: PlainTextInputElement(placeholder: "Enter task details...", multiline: true)
)

Interactive Elements

Button

ButtonElement(
    text: .plainText("Click Me"),
    actionID: "button_1",
    value: "button_value",
    style: .primary
)

Select Menu

StaticSelectElement(placeholder: .plainText("Choose an option")) {
    Option(text: .plainText("Option 1"), value: "opt1")
    Option(text: .plainText("Option 2"), value: "opt2")
}

Multi-Select Menu

MultiStaticSelectElement(placeholder: .plainText("Select options"), maxSelectedItems: 3) {
    Option(text: .plainText("Option 1"), value: "opt1")
    Option(text: .plainText("Option 2"), value: "opt2")
}

Date Picker

DatePickerElement(
    actionID: "date_picker_1",
    placeholder: .plainText("Select a date")
)

Users Select

UsersSelectElement(
    placeholder: .plainText("Select a user"),
    initialUser: "U1234567890"
)

Conversations Select

ConversationsSelectElement(
    placeholder: .plainText("Select a conversation"),
    filter: ConversationFilter(
        include: [.public, .private],
        excludeBotUsers: true
    )
)

Text Objects

Plain Text

TextObject.plainText("Simple text", emoji: true)

Markdown

TextObject.markdown("*Bold* and `code`")

Error Handling

do {
    try await client.send(message)
} catch SlackError.invalidURL(let url) {
    print("Invalid URL: \(url)")
} catch SlackError.invalidResponse(let code, let body) {
    print("HTTP \(code): \(body ?? "No body")")
} catch SlackError.rateLimitExceeded(let retryAfter) {
    print("Rate limited. Retry after \(retryAfter) seconds")
} catch SlackError.encodingError(let error) {
    print("Failed to encode message: \(error)")
} catch SlackError.networkError(let error) {
    print("Network error: \(error)")
}

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

Send beautiful Slack messages from Swift. Webhooks, Block Kit, and async/await.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages