Slack Web API client for Elixir.
Req-based, explicit-client design: build a Noizu.Slack.Client and pass it to every
call. Slack's {"ok": ...} envelope is decoded for you — every call returns
{:ok, map} or {:error, %Noizu.Slack.Error{}} (never raised). HTTP 429 honours
Retry-After inside a bounded retry (3 attempts). Includes cursor streams, the
external file-upload flow, incoming webhooks, request-signature verification,
Block Kit builders, and a GitHub-markdown → mrkdwn converter.
Installation
def deps do
[
{:noizu_slack, "~> 0.1.0"}
]
endRuntime dependencies: req, jason.
Configuration
| Setting | Source (highest wins) | Default |
|---|---|---|
| token | Client.new(token: …) → config :noizu_slack, :client, token: … → SLACK_BOT_TOKEN env var | none (required at request time) |
| base URL | :base_url opt → app env | https://slack.com/api |
| user agent | :user_agent opt → app env | noizu_slack/0.1.0 (+repo url) |
| Req passthrough | :req_options opt → app env | [] |
| retry | :retry opt (max_attempts, base_backoff_ms, max_retry_after_ms, sleep_fn) | 3 attempts, 500 ms backoff, 30 s clamp |
# config/runtime.exs
config :noizu_slack, :client, token: System.get_env("SLACK_BOT_TOKEN")Signing secrets (for Noizu.Slack.Signature) and webhook URLs are passed explicitly;
the library never reads them from the environment. Treat both as secrets.
Usage
client = Noizu.Slack.client()
{:ok, %{"user_id" => _bot}} = Noizu.Slack.Auth.test(client)
# Messages
{:ok, %{"ts" => ts}} = Noizu.Slack.Chat.post_message(client, "C0123456", "Deploy *finished*")
{:ok, _} = Noizu.Slack.Chat.post_message(client, "C0123456", "reply", thread_ts: ts)
{:ok, _} = Noizu.Slack.Chat.update(client, "C0123456", ts, "Deploy *finished* :white_check_mark:")
{:ok, _} = Noizu.Slack.Reactions.add(client, "C0123456", ts, "rocket")
# Block Kit
alias Noizu.Slack.Blocks
{:ok, blocks} =
Blocks.validate([
Blocks.header("Release 1.4.0"),
Blocks.section("*api* and *web* rolled out", fields: ["*Env*\nprod", "*By*\n<@U123>"]),
Blocks.actions([Blocks.button("Open run", "open_run", url: "https://example.com/run/1")])
])
{:ok, _} = Noizu.Slack.Chat.post_message(client, "C0123456", %{text: "Release 1.4.0", blocks: blocks})
# Markdown → mrkdwn / blocks
Noizu.Slack.Mrkdwn.from_markdown("## Notes\n- **bold** and [link](https://x.io)")
#=> "*Notes*\n• *bold* and <https://x.io|link>"
blocks = Noizu.Slack.Mrkdwn.to_blocks(long_markdown)
# Reading (one page, or lazy streams across pages)
{:ok, %{"channels" => _}} = Noizu.Slack.Conversations.list(client, types: ["public_channel"])
Noizu.Slack.Conversations.history_stream(client, "C0123456", limit: 200) |> Enum.take(500)
Noizu.Slack.Users.list_stream(client) |> Stream.reject(& &1["is_bot"]) |> Enum.to_list()
# Search needs a user token — override per call
{:ok, _} = Noizu.Slack.Search.messages(client, "in:#deploys failed", token: user_token)
# Files (getUploadURLExternal → bytes → completeUploadExternal)
{:ok, _} = Noizu.Slack.Files.upload(client, "C0123456", %{filename: "report.csv", content: csv, title: "Report"})
# Incoming webhook (no client)
{:ok, "ok"} = Noizu.Slack.Webhook.post(webhook_url, %{text: "hello"})
# Verify an inbound Events API / slash-command request (raw body!)
:ok = Noizu.Slack.Signature.verify(signing_secret, ts_header, raw_body, sig_header)Any unwrapped Web API method is reachable through the engine directly:
Noizu.Slack.Request.post(client, "bookmarks.add", %{channel_id: "C1", title: "Runbook", type: "link", link: "https://…"})
Noizu.Slack.Request.get(client, "team.info")Errors
%Noizu.Slack.Error{tag:, reason:, status:, body:, message:} — tag is one of
:api (ok:false, reason = Slack error string), :rate_limited, :http,
:invalid_response, :transport, :config, :upload, :webhook, :signature,
:blocks. Slack warnings on successful calls are collected under
body["_meta"]["warnings"].
Testing
Tests stub HTTP with Req.Test-style plugs (req_options: [plug: fun]); no network.
mix deps.get
mix test
License
MIT — see LICENSE.