Vir

How to Build Your Own Slack Agent with OpenRouter and Railway

7 min read

A practical path from a local Slack bot to an always-on agent using Bolt for JavaScript, OpenRouter, DeepSeek V4 Flash, and Railway.

I wanted a Slack agent that I could shape myself. Not a packaged assistant, not a bot that only answers questions, and not an integration that hides the important parts. I wanted a small system I could understand, run locally, and eventually leave running in the cloud.

The useful mental model is simple:

Slack → Bolt app → agent runtime → OpenRouter model → tools → Slack

Slack is the interface. The Bolt app receives messages and sends replies. The model decides what to say or which tool to use. The tools are where the agent can search, draft, update, or take an action.

1. Start with a Slack app

Create an app at api.slack.com/apps. For a JavaScript agent, the Bolt framework gives you the event handling and Slack API client you need.

The first version only needs to respond when it is mentioned or when someone sends it a direct message. Keep the permission surface small. Add the bot to one private test channel before giving it access to the rest of the workspace.

For a local or long-running service, Socket Mode is convenient because Slack maintains an outbound connection to the app. Your app does not need a public webhook URL just to receive mentions. You will need:

  • A bot token beginning with xoxb-.
  • An app-level token beginning with xapp-, with the connections:write scope.
  • Event subscriptions such as app_mention and direct-message events.

The critical rule is to store these tokens as environment variables. Never paste them into a Slack channel, commit them to Git, or put them in a blog post.

2. Use an OpenAI-compatible model provider

You do not need to call OpenAI directly to use an OpenAI-style agent SDK. OpenRouter exposes an OpenAI-compatible Chat Completions endpoint, so the app can send requests to OpenRouter while using a model from another provider.

The environment configuration can be as small as:

OPENROUTER_API_KEY=your_key_here
OPENROUTER_BASE_URL=https://openrouter.ai/api/v1
OPENROUTER_MODEL=deepseek/deepseek-v4-flash

The model name is configuration, not business logic. That means you can change the model later without rewriting the Slack event handlers.

For tool-using agents, explicitly choose Chat Completions mode when the provider is OpenAI-compatible. Provider compatibility is not the same thing as endpoint compatibility: a model may support tool calls through Chat Completions even when it does not support the newer Responses API through a proxy.

3. Give the agent a narrow job

An agent becomes useful when its job is clear. A first private assistant might be instructed to:

Help me organize work, summarize information, draft messages, and prepare actions across my connected tools. Treat my information as confidential. Never send a message, modify a CRM record, create an event, or change a file without asking for approval first.

That last sentence matters. The agent should be able to prepare an action without being allowed to execute every action automatically.

Keep the first tool set small. Good first tools are read-only search and summarization. Add write actions later, one at a time, with an approval step between the proposed action and the actual API call.

4. Connect tools deliberately

The model alone cannot update HubSpot, create a calendar event, or send an email. Those capabilities come from tools that you explicitly connect to the runtime.

A safe progression looks like this:

  1. Read Slack messages in the current thread.
  2. Search a small, approved knowledge source.
  3. Draft an email or CRM update.
  4. Show the proposed action to a human.
  5. Execute only after explicit approval.

The agent should also report what it did, what it could not do, and which source it used. That makes failures recoverable instead of mysterious.

5. Run it locally first

Local development is useful because logs are close and iteration is fast. A typical JavaScript project has a start script such as:

{
  "scripts": {
    "start": "node app.js"
  }
}

Install dependencies, create a private .env file, and start the app:

npm install
npm start

Test one path at a time: direct message, channel mention, model response, then one safe tool call. If the app does not reply, inspect the logs before changing the prompt. A missing Slack app token, missing event subscription, missing channel membership, or invalid model key can all look like “the agent is not working.”

6. Move the worker to Railway

Once the local version works, deploy the directory containing the app, not the parent directory containing unrelated examples. Railway can build a Node project from package.json and keep the process running.

Use:

Build command: npm install
Start command: npm start

Add these variables to the Railway service:

OPENROUTER_API_KEY=...
OPENROUTER_BASE_URL=https://openrouter.ai/api/v1
OPENROUTER_MODEL=deepseek/deepseek-v4-flash
SLACK_BOT_TOKEN=xoxb-...
SLACK_APP_TOKEN=xapp-...

Socket Mode means the basic bot does not need a public Railway domain. Railway only needs to keep the Node process alive so it can maintain its connection to Slack.

After deployment, check the runtime logs for a clear startup message. Then stop the local copy before testing in Slack; running both copies can create duplicate replies or confusing connection behavior.

7. Keep the boundaries visible

An always-on agent is not automatically a trustworthy agent. Keep these boundaries explicit:

  • Secrets belong in a secret manager or deployment variables, never in prompts or messages.
  • Slack channel membership limits what the bot can see, but it is not a replacement for tool-level permissions.
  • A private assistant should not be placed in a general team channel.
  • External messages, CRM changes, calendar events, and file edits should require approval until the workflow has been tested.
  • Logs should be useful without recording full credentials or unnecessary private content.

The goal is not to make the agent autonomous on day one. The goal is to make its behavior observable, useful, and safe enough that you can add autonomy with evidence.

The smallest complete version

The first version can be surprisingly small:

  • Slack Bolt for receiving mentions and posting replies.
  • OpenRouter for model access.
  • DeepSeek V4 Flash for the default model.
  • One or two read-only tools.
  • Railway for the always-on process.
  • A human approval step before consequential actions.

That is enough to learn whether the agent actually improves your work. The architecture can grow later; the first job is to make one real Slack workflow reliable.

Sources: Slack Bolt for JavaScript, Slack Socket Mode, OpenRouter, and Railway.

© 2025 Vir.