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.
- 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
- macOS 12.0+
- iOS 15.0+
- tvOS 15.0+
- watchOS 8.0+
- Swift 6.0+
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:
- File → Add Package Dependencies
- Enter the repository URL
- Select the version rule
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!"))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)let message = Message(text: "Deployment completed successfully!")
try await client.send(message)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)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)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)let message = Message(
text: "This is a threaded reply",
threadTimestamp: "1234567890.123456"
)
try await client.send(message)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")
}Large header text:
Header("Important Announcement")Horizontal line divider:
Divider()Display an image:
Image(url: "https://example.com/image.png", altText: "An example image")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)
}
}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 blocks for collecting user input in modals:
Input(
label: "Task description",
element: PlainTextInputElement(placeholder: "Enter task details...", multiline: true)
)ButtonElement(
text: .plainText("Click Me"),
actionID: "button_1",
value: "button_value",
style: .primary
)StaticSelectElement(placeholder: .plainText("Choose an option")) {
Option(text: .plainText("Option 1"), value: "opt1")
Option(text: .plainText("Option 2"), value: "opt2")
}MultiStaticSelectElement(placeholder: .plainText("Select options"), maxSelectedItems: 3) {
Option(text: .plainText("Option 1"), value: "opt1")
Option(text: .plainText("Option 2"), value: "opt2")
}DatePickerElement(
actionID: "date_picker_1",
placeholder: .plainText("Select a date")
)UsersSelectElement(
placeholder: .plainText("Select a user"),
initialUser: "U1234567890"
)ConversationsSelectElement(
placeholder: .plainText("Select a conversation"),
filter: ConversationFilter(
include: [.public, .private],
excludeBotUsers: true
)
)TextObject.plainText("Simple text", emoji: true)TextObject.markdown("*Bold* and `code`")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)")
}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 Slack Block Kit
- Inspired by the official Slack SDKs
