SlackSandbox
A Swoosh-style local mailbox, but for Slack.
Point your app's Slack client at SlackSandbox in dev/test instead of the
real Slack API. Every post_message/2 and send_dm/2 call is captured
in-memory and pushed live to SlackSandbox.InboxLive, so you can watch
outbound messages arrive without a Slack workspace, webhook tunnel, or bot
token.
Installation
def deps do
[
{:slack_sandbox, "~> 0.1.0"}
]
end
Setup
1. Add the inbox to your supervision tree, pointed at a PubSub already running in your app:
children = [
MyApp.PubSub,
{SlackSandbox.Inbox, pubsub: MyApp.PubSub},
MyAppWeb.Endpoint
]
2. Define a Slack adapter behaviour in your app (or reuse
SlackSandbox.Adapter) and point it at SlackSandbox in dev:
# config/dev.exs
config :my_app, :slack_adapter, SlackSandbox
# config/prod.exs
config :my_app, :slack_adapter, MyApp.Slack.HttpAdapter
# wherever your app sends Slack messages
adapter = Application.fetch_env!(:my_app, :slack_adapter)
adapter.post_message("Deploy finished", "#ops")
3. Mount the inbox page in your router:
live "/dev/slack-inbox", SlackSandbox.InboxLive
Visit /dev/slack-inbox and every captured message shows up in real time,
pushed over PubSub — no refresh needed. The "Clear" button resets the
inbox for everyone watching.
Why not just log it?
Logs scroll past. A live inbox gives you a running list you can leave open
in a second tab while you click through the flow that triggers Slack
messages — the same workflow Swoosh.Adapters.Local gives you for email.
Multiple inboxes / testing
SlackSandbox.Inbox is a plain GenServer — pass name: nil to start an
unregistered instance (handy in tests where you don't want a single shared
process across async cases):
{:ok, inbox} = SlackSandbox.Inbox.start_link(pubsub: MyApp.PubSub, name: nil)
SlackSandbox.Inbox.push(%{type: :message, text: "hi", channel: "C1"}, inbox)
SlackSandbox.Inbox.list(inbox)
License
MIT