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