slack_sender 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.husky/pre-commit +1 -0
- data/.lintstagedrc +1 -0
- data/CHANGELOG.md +7 -0
- data/README.md +97 -0
- data/Rakefile +15 -0
- data/docs/axn_integration.md +168 -0
- data/docs/configuration.md +252 -0
- data/docs/troubleshooting.md +160 -0
- data/docs/usage.md +382 -0
- data/lib/slack_sender/channel_normalizer.rb +72 -0
- data/lib/slack_sender/configuration.rb +121 -0
- data/lib/slack_sender/delivery_axn/async_configuration.rb +50 -0
- data/lib/slack_sender/delivery_axn/error_message_parsing.rb +75 -0
- data/lib/slack_sender/delivery_axn/exception_handlers.rb +36 -0
- data/lib/slack_sender/delivery_axn/validation.rb +37 -0
- data/lib/slack_sender/delivery_axn.rb +217 -0
- data/lib/slack_sender/error_messages.rb +45 -0
- data/lib/slack_sender/file_uploader.rb +64 -0
- data/lib/slack_sender/file_wrapper.rb +72 -0
- data/lib/slack_sender/multi_file_wrapper.rb +49 -0
- data/lib/slack_sender/notifier/notification_definition.rb +66 -0
- data/lib/slack_sender/notifier/notification_dsl.rb +59 -0
- data/lib/slack_sender/notifier.rb +75 -0
- data/lib/slack_sender/profile.rb +314 -0
- data/lib/slack_sender/profile_registry.rb +34 -0
- data/lib/slack_sender/rails/engine.rb +31 -0
- data/lib/slack_sender/strategy.rb +98 -0
- data/lib/slack_sender/util.rb +88 -0
- data/lib/slack_sender/version.rb +5 -0
- data/lib/slack_sender.rb +71 -0
- metadata +111 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 320590e57f220cb0e85688e3987c1fdee1b9f1bc014a9907099ec277568a06f6
|
|
4
|
+
data.tar.gz: b478553cd49da506001a5223673cc9a2ca0d2adf79cba6d6fc4e5a317f33db89
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a5153bb970a492024ac5ddc16f1fab9e6debe046636f87330c4ab19ebade650702123e2edc7a7d3473e9e31a84f51ac6e0830cae0be2add5c18542996ae89b8d
|
|
7
|
+
data.tar.gz: 5c5737bfa9581c9766d0eb66b4d20367501006f2b40acff2f41aafabc3455be2db2c9174b1fa3feff31f5d70db032124b4928a7c2d1fb2140975f792cd8a614c
|
data/.husky/pre-commit
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
npx lint-staged
|
data/.lintstagedrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"*.rb":["bundle exec rubocop -A -c .rubocop.yml --force-exclusion"]}
|
data/CHANGELOG.md
ADDED
data/README.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# SlackSender
|
|
2
|
+
|
|
3
|
+
**Reliable Slack messaging for Ruby — with automatic retries, rate-limit handling, and sandbox safety.**
|
|
4
|
+
|
|
5
|
+
SlackSender handles the plumbing so you can focus on your application: background dispatch, retry logic, multi-workspace support, and development environment redirects are all built in.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Add to your Gemfile:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
gem 'slack_sender'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Then run:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
bundle install
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Requirements:**
|
|
22
|
+
- Ruby >= 3.2.1
|
|
23
|
+
- A Slack Bot User OAuth Token with `chat:write` scope (see [Configuration](docs/configuration.md#required-slack-scopes) for full scope list)
|
|
24
|
+
- For async delivery: Sidekiq or ActiveJob (auto-detected)
|
|
25
|
+
|
|
26
|
+
## Quick Start - Minimal
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
SlackSender.register(token: ENV['SLACK_BOT_TOKEN'])
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
SlackSender.call!(channel: "some_channel_name", text: "Hi there")
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Quick Start - Realistic
|
|
37
|
+
|
|
38
|
+
### 1. Register a Profile
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
SlackSender.register(
|
|
42
|
+
token: ENV['SLACK_BOT_TOKEN'],
|
|
43
|
+
channels: {
|
|
44
|
+
ops_alerts: 'C1111111111',
|
|
45
|
+
deployments: 'C2222222222',
|
|
46
|
+
},
|
|
47
|
+
sandbox: {
|
|
48
|
+
channel: { replace_with: 'C_DEV_CHANNEL' } # Redirects in non-production
|
|
49
|
+
}
|
|
50
|
+
)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 2. Send Messages
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
# Async (recommended) — background job with automatic retries
|
|
57
|
+
SlackSender.call(channel: :ops_alerts, text: ":rotating_light: High error rate detected")
|
|
58
|
+
|
|
59
|
+
# Sync — when you need the thread timestamp
|
|
60
|
+
thread_ts = SlackSender.call!(channel: :deployments, text: ":rocket: Deploy started")
|
|
61
|
+
SlackSender.call(channel: :deployments, text: "Deploy complete!", thread_ts:)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
That's it. SlackSender handles rate limits, retries, and sandbox redirection (if configured, all messages sent from non-production environments will be delivered to your `replace_with` channel) automatically.
|
|
65
|
+
|
|
66
|
+
## Documentation
|
|
67
|
+
|
|
68
|
+
| Guide | Description |
|
|
69
|
+
|-------|-------------|
|
|
70
|
+
| [Usage Guide](docs/usage.md) | Messages, files, threading, multi-channel delivery |
|
|
71
|
+
| [Configuration](docs/configuration.md) | Profiles, sandbox mode, global settings |
|
|
72
|
+
| [Axn Integration](docs/axn_integration.md) | `use :slack` strategy and `SlackSender::Notifier` |
|
|
73
|
+
| [Troubleshooting](docs/troubleshooting.md) | Common errors and FAQ |
|
|
74
|
+
|
|
75
|
+
## Features
|
|
76
|
+
|
|
77
|
+
- **Background dispatch** with automatic rate-limit retries via Sidekiq or ActiveJob
|
|
78
|
+
- **Multi-channel delivery** — broadcast to multiple channels efficiently
|
|
79
|
+
- **Sandbox mode** — redirect or suppress messages in non-production environments
|
|
80
|
+
- **File uploads** — sync and async, with automatic size handling
|
|
81
|
+
- **Multiple profiles** — manage multiple Slack workspaces
|
|
82
|
+
- **Axn integration** — `use :slack` strategy and dedicated `Notifier` base class
|
|
83
|
+
|
|
84
|
+
## Development
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
bin/setup # Install dependencies
|
|
88
|
+
bundle exec rspec # Run tests
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Contributing
|
|
92
|
+
|
|
93
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/teamshares/slack_sender.
|
|
94
|
+
|
|
95
|
+
## License
|
|
96
|
+
|
|
97
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "rspec/core/rake_task"
|
|
5
|
+
require "rubocop/rake_task"
|
|
6
|
+
|
|
7
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
8
|
+
|
|
9
|
+
RuboCop::RakeTask.new
|
|
10
|
+
|
|
11
|
+
task default: %i[spec rubocop]
|
|
12
|
+
|
|
13
|
+
# Require default to pass before release. This relies on the default gem release task
|
|
14
|
+
# (from bundler/gem_tasks) depending on "build"; default runs before build, so before push.
|
|
15
|
+
Rake::Task["build"].enhance([:default])
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Axn Integration
|
|
2
|
+
|
|
3
|
+
[← Back to README](../README.md)
|
|
4
|
+
|
|
5
|
+
SlackSender provides deep integration with [Axn](https://teamshares.github.io/axn/) for building Slack-enabled actions and dedicated notifier classes.
|
|
6
|
+
|
|
7
|
+
## Slack Strategy for Axn Actions
|
|
8
|
+
|
|
9
|
+
Add Slack messaging capabilities to any Axn action using the `:slack` strategy:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
class Deployments::Finish
|
|
13
|
+
include Axn
|
|
14
|
+
use :slack, channel: :deployments # Default channel for all slack() calls
|
|
15
|
+
|
|
16
|
+
expects :deployment, type: Deployment
|
|
17
|
+
|
|
18
|
+
on_success { slack ":rocket: Deploy finished for `#{deployment.service}`" }
|
|
19
|
+
on_failure { slack ":x: Deploy failed for `#{deployment.service}`", channel: :ops_alerts }
|
|
20
|
+
|
|
21
|
+
def call
|
|
22
|
+
# slack() is async (background job) - recommended for fire-and-forget
|
|
23
|
+
slack "Finalizing deploy for `#{deployment.service}`..."
|
|
24
|
+
|
|
25
|
+
# slack!() is sync - use when you need the thread_ts
|
|
26
|
+
thread_ts = slack! "Starting rollout..."
|
|
27
|
+
# ... rollout / status checks / persistence ...
|
|
28
|
+
slack "Rollout complete!", thread_ts: thread_ts
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Strategy Configuration
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
use :slack, channel: :general # Default channel for all slack() calls
|
|
37
|
+
use :slack, channel: :general, profile: :support # Use a specific SlackSender profile
|
|
38
|
+
use :slack, channels: [:alerts, :ops] # Default to multiple channels (async only)
|
|
39
|
+
use :slack # No default channel (must pass channel: each time)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### The `slack(...)` and `slack!(...)` Methods
|
|
43
|
+
|
|
44
|
+
The strategy adds two instance methods for sending Slack messages:
|
|
45
|
+
|
|
46
|
+
| Method | Delivery | Return Value | Use When |
|
|
47
|
+
|--------|----------|--------------|----------|
|
|
48
|
+
| `slack(...)` | Async (background job) | `true` or `false` | Default; enables auto-retry for rate limits |
|
|
49
|
+
| `slack!(...)` | Sync (immediate) | Thread timestamp or `false` | You need the `thread_ts` return value |
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
# Async delivery (recommended) - uses Sidekiq or ActiveJob
|
|
53
|
+
slack "Hello world"
|
|
54
|
+
slack "Hello", channel: :other_channel
|
|
55
|
+
|
|
56
|
+
# Sync delivery - immediate execution, returns thread_ts
|
|
57
|
+
thread_ts = slack! "Starting deployment..."
|
|
58
|
+
slack! "Deployment finished", thread_ts: thread_ts
|
|
59
|
+
|
|
60
|
+
# Full kwargs work with both methods
|
|
61
|
+
slack text: "Hello", channel: :ops_alerts, icon_emoji: "robot"
|
|
62
|
+
slack! channel: :ops_alerts, blocks: [{ type: "section", text: { type: "mrkdwn", text: "*Bold*" } }]
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Note:** `slack(...)` requires an async backend to be configured (Sidekiq or ActiveJob). If no async backend is available, it raises `SlackSender::Error` with instructions to either use `slack!(...)` or configure an async backend.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## SlackSender::Notifier Base Class
|
|
70
|
+
|
|
71
|
+
For actions whose sole purpose is sending Slack notifications, inherit from `SlackSender::Notifier`. These are built on top of Axn (that's where the `expects` DSL comes from below), so you'll want to [familiarize yourself with that library](https://teamshares.github.io/axn/) before continuing:
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
# app/slack_notifiers/deployments/finished.rb
|
|
75
|
+
module SlackNotifiers
|
|
76
|
+
module Deployments
|
|
77
|
+
class Finished < SlackSender::Notifier
|
|
78
|
+
expects :deployment_id, type: Integer
|
|
79
|
+
|
|
80
|
+
# Post to the deployments channel for production releases
|
|
81
|
+
notify do
|
|
82
|
+
channel :deployments
|
|
83
|
+
only_if { production_release? }
|
|
84
|
+
text { ":rocket: *Deploy finished* for `#{deployment.service}` (#{deployment.environment})" }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Optionally also post in the incident channel if this deploy is related to an incident
|
|
88
|
+
notify do
|
|
89
|
+
channel :incident_channel_id
|
|
90
|
+
only_if { incident_channel_id.present? }
|
|
91
|
+
text { ":rocket: *Deploy finished* for `#{deployment.service}` (#{deployment.environment})" }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
private
|
|
95
|
+
|
|
96
|
+
def production_release? = deployment.environment.to_s == "production"
|
|
97
|
+
|
|
98
|
+
# Dynamic channel ID string (e.g., "C123...") sourced from your domain model
|
|
99
|
+
def incident_channel_id = deployment.incident_slack_channel_id
|
|
100
|
+
|
|
101
|
+
def deployment = @deployment ||= Deployment.find(deployment_id)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Call it like any Axn
|
|
107
|
+
SlackNotifiers::Deployments::Finished.call(deployment_id: 123)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## The `notify do ... end` DSL
|
|
113
|
+
|
|
114
|
+
The `notify` block groups all Slack message configuration together, keeping it visually separated from Axn declarations like `expects`:
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
notify do
|
|
118
|
+
channel :notifications # Single channel
|
|
119
|
+
text { "Hello!" } # Dynamic text (block)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
notify do
|
|
123
|
+
channels :ops_alerts, :ic # Multiple channels (files uploaded once, shared to all)
|
|
124
|
+
only_if { priority == :high } # Conditional send
|
|
125
|
+
text :message_text # Text from method
|
|
126
|
+
attachments :build_attachments # Attachments from method
|
|
127
|
+
end
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### DSL Options
|
|
131
|
+
|
|
132
|
+
| Option | Description |
|
|
133
|
+
|--------|-------------|
|
|
134
|
+
| `channel :sym` | Single channel (symbol resolved via profile, or method if defined) |
|
|
135
|
+
| `channels :a, :b` | Multiple channels |
|
|
136
|
+
| `text { ... }` | Text content (block evaluated in instance context) |
|
|
137
|
+
| `text :method` | Text from method |
|
|
138
|
+
| `text "static"` | Static text |
|
|
139
|
+
| `blocks { ... }` | Slack blocks |
|
|
140
|
+
| `attachments { ... }` | Slack attachments |
|
|
141
|
+
| `icon_emoji :emoji` | Custom emoji |
|
|
142
|
+
| `thread_ts :method` | Thread timestamp |
|
|
143
|
+
| `files { ... }` | File attachments |
|
|
144
|
+
| `only_if { ... }` | Condition (block) — only send if truthy |
|
|
145
|
+
| `only_if :method` | Condition (method) — only send if truthy |
|
|
146
|
+
| `profile :name` | Use a specific SlackSender profile |
|
|
147
|
+
|
|
148
|
+
### Value Resolution
|
|
149
|
+
|
|
150
|
+
For each field, values are resolved in this order:
|
|
151
|
+
1. **Block**: `text { "dynamic #{value}" }` — evaluated in instance context
|
|
152
|
+
2. **Symbol**: `text :my_method` — calls method if it exists, otherwise treated as literal
|
|
153
|
+
3. **Literal**: `text "static"` — used as-is
|
|
154
|
+
|
|
155
|
+
### Required Fields
|
|
156
|
+
|
|
157
|
+
- At least one `channel` or `channels`
|
|
158
|
+
- At least one payload field (`text`, `blocks`, `attachments`, or `files`)
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Notifier Features
|
|
163
|
+
|
|
164
|
+
Since `SlackSender::Notifier` inherits from Axn, you get:
|
|
165
|
+
- `expects` / `exposes` for input/output contracts
|
|
166
|
+
- Hooks (`before`, `after`, `on_success`, `on_failure`)
|
|
167
|
+
- Automatic logging and error handling
|
|
168
|
+
- Async execution with `call_async`
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
[← Back to README](../README.md)
|
|
4
|
+
|
|
5
|
+
This guide covers global configuration, profile registration, and sandbox mode settings.
|
|
6
|
+
|
|
7
|
+
## Global Configuration
|
|
8
|
+
|
|
9
|
+
Configure SlackSender behavior via `SlackSender.configure` (e.g. in a Rails initializer):
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
SlackSender.configure do |config|
|
|
13
|
+
# Set async backend (auto-detects Sidekiq or ActiveJob if available)
|
|
14
|
+
config.async_backend = :sidekiq # or :active_job
|
|
15
|
+
|
|
16
|
+
# Set sandbox mode (affects sandbox channel/user_group redirects)
|
|
17
|
+
# Defaults to true in non-production, false in production
|
|
18
|
+
config.sandbox_mode = !Rails.env.production?
|
|
19
|
+
|
|
20
|
+
# Set default sandbox behavior when sandbox_mode is true but profile
|
|
21
|
+
# doesn't specify a sandbox.mode or sandbox.channel.replace_with
|
|
22
|
+
# Options: :noop (default), :redirect, :passthrough
|
|
23
|
+
config.sandbox_default_behavior = :noop
|
|
24
|
+
|
|
25
|
+
# Enable/disable SlackSender globally (default: true)
|
|
26
|
+
config.enabled = ENV["DISABLE_SLACK"] != "1"
|
|
27
|
+
|
|
28
|
+
# Silence archived channel exceptions (default: false)
|
|
29
|
+
config.silence_archived_channel_exceptions = false
|
|
30
|
+
|
|
31
|
+
# Control autoloading namespace for app/slack_notifiers (default: true)
|
|
32
|
+
# When true: app/slack_notifiers/foo.rb -> SlackNotifiers::Foo
|
|
33
|
+
# When false: app/slack_notifiers/foo.rb -> Foo (standard Rails behavior)
|
|
34
|
+
config.use_slack_notifiers_namespace = true
|
|
35
|
+
end
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Global Options Reference
|
|
39
|
+
|
|
40
|
+
| Option | Type | Default | Description |
|
|
41
|
+
|--------|------|---------|-------------|
|
|
42
|
+
| `async_backend` | `Symbol` or `nil` | Auto-detected | Backend for async delivery. Supported: `:sidekiq`, `:active_job` |
|
|
43
|
+
| `sandbox_mode` | `Boolean` or `nil` | `!Rails.env.production?` if Rails available, else `true` | Whether app is in sandbox mode |
|
|
44
|
+
| `sandbox_default_behavior` | `Symbol` | `:noop` | Default behavior when in sandbox mode if profile doesn't specify. Options: `:noop`, `:redirect`, `:passthrough` |
|
|
45
|
+
| `enabled` | `Boolean` | `true` | Global enable/disable flag. When `false`, `call` and `call!` return `false` without sending |
|
|
46
|
+
| `silence_archived_channel_exceptions` | `Boolean` | `false` | If `true`, silently ignores `IsArchived` errors instead of reporting them |
|
|
47
|
+
| `max_async_file_upload_size` | `Integer` or `nil` | `26_214_400` (25 MB) | Max total file size for async uploads. Set to `nil` to disable |
|
|
48
|
+
| `use_slack_notifiers_namespace` | `Boolean` | `true` | When `true`, files in `app/slack_notifiers` are autoloaded under the `SlackNotifiers` namespace |
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Profile Registration
|
|
53
|
+
|
|
54
|
+
A **profile** represents a Slack workspace configuration. Register profiles with `SlackSender.register`:
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
SlackSender.register(
|
|
58
|
+
token: ENV['SLACK_BOT_TOKEN'],
|
|
59
|
+
default_channel: :ops_alerts,
|
|
60
|
+
channels: {
|
|
61
|
+
ops_alerts: 'C1111111111',
|
|
62
|
+
deployments: 'C2222222222',
|
|
63
|
+
reports: 'C3333333333',
|
|
64
|
+
},
|
|
65
|
+
user_groups: {
|
|
66
|
+
engineers: 'S1234567890',
|
|
67
|
+
},
|
|
68
|
+
sandbox: {
|
|
69
|
+
channel: {
|
|
70
|
+
replace_with: 'C1234567890',
|
|
71
|
+
message_prefix: ':construction: _This message would have been sent to %s in production_'
|
|
72
|
+
},
|
|
73
|
+
user_group: {
|
|
74
|
+
replace_with: 'S_DEV_GROUP'
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Profile Options Reference
|
|
81
|
+
|
|
82
|
+
| Option | Type | Default | Description |
|
|
83
|
+
|--------|------|---------|-------------|
|
|
84
|
+
| `token` | `String` or callable | Required | Slack Bot User OAuth Token. Can be a proc/lambda for dynamic fetching |
|
|
85
|
+
| `default_channel` | `Symbol`, `String`, or `nil` | `nil` | Default channel when none is specified in `call`/`call!` |
|
|
86
|
+
| `channels` | `Hash` | `{}` | Hash mapping symbol keys to channel IDs (e.g., `{ alerts: 'C123' }`) |
|
|
87
|
+
| `user_groups` | `Hash` | `{}` | Hash mapping symbol keys to user group IDs (e.g., `{ engineers: 'S123' }`) |
|
|
88
|
+
| `slack_client_config` | `Hash` | `{}` | Additional options passed to `Slack::Web::Client` constructor |
|
|
89
|
+
| `sandbox` | `Hash` | `{}` | Sandbox mode configuration (see below) |
|
|
90
|
+
|
|
91
|
+
### Dynamic Token
|
|
92
|
+
|
|
93
|
+
Use a callable for the token to fetch it dynamically:
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
SlackSender.register(
|
|
97
|
+
token: -> { SecretsManager.get_slack_token },
|
|
98
|
+
channels: { ops_alerts: 'C123' }
|
|
99
|
+
)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The token is memoized after first access.
|
|
103
|
+
|
|
104
|
+
### Multiple Profiles
|
|
105
|
+
|
|
106
|
+
Register multiple profiles for different Slack workspaces:
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
# Internal engineering workspace (default profile)
|
|
110
|
+
SlackSender.register(
|
|
111
|
+
token: ENV['SLACK_BOT_TOKEN'],
|
|
112
|
+
channels: { ops_alerts: 'C123', deployments: 'C234' }
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Customer support workspace
|
|
116
|
+
SlackSender.register(:support,
|
|
117
|
+
token: ENV['SUPPORT_SLACK_TOKEN'],
|
|
118
|
+
channels: { support_tickets: 'C456' }
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Use specific profile
|
|
122
|
+
SlackSender.profile(:support).call(
|
|
123
|
+
channel: :support_tickets,
|
|
124
|
+
text: "New high-priority ticket received"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Or use bracket notation
|
|
128
|
+
SlackSender[:support].call(channel: :support_tickets, text: "...")
|
|
129
|
+
|
|
130
|
+
# Or override default profile with profile parameter
|
|
131
|
+
SlackSender.call(profile: :support, channel: :support_tickets, text: "...")
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Sandbox Mode
|
|
137
|
+
|
|
138
|
+
When `config.sandbox_mode?` is true (default in non-production), SlackSender applies sandbox behavior based on the profile's `sandbox` configuration.
|
|
139
|
+
|
|
140
|
+
### Sandbox Options Reference
|
|
141
|
+
|
|
142
|
+
| Option | Type | Default | Description |
|
|
143
|
+
|--------|------|---------|-------------|
|
|
144
|
+
| `behavior` | `Symbol` or `nil` | Inferred | Explicit sandbox behavior: `:redirect`, `:noop`, or `:passthrough` |
|
|
145
|
+
| `channel.replace_with` | `String` or `nil` | `nil` | Channel ID to redirect all messages when behavior is `:redirect` |
|
|
146
|
+
| `channel.message_prefix` | `String` or `nil` | See below | Custom prefix for sandbox channel redirects. Use `%s` placeholder for channel name |
|
|
147
|
+
| `user_group.replace_with` | `String` or `nil` | `nil` | User group ID to replace all group mentions when in sandbox mode |
|
|
148
|
+
|
|
149
|
+
Default message prefix: `:construction: _This message would have been sent to %s in production_`
|
|
150
|
+
|
|
151
|
+
### Behavior Resolution
|
|
152
|
+
|
|
153
|
+
When `config.sandbox_mode?` is true, the effective sandbox behavior is determined by:
|
|
154
|
+
|
|
155
|
+
1. **Explicit `sandbox.behavior`** — if set, use it
|
|
156
|
+
2. **Inferred from `sandbox.channel.replace_with`** — if present, behavior is `:redirect`
|
|
157
|
+
3. **Global default** — `config.sandbox_default_behavior` (defaults to `:noop`)
|
|
158
|
+
|
|
159
|
+
| Behavior | Description |
|
|
160
|
+
|----------|-------------|
|
|
161
|
+
| `:redirect` | Redirect messages to `sandbox.channel.replace_with` (required). Adds message prefix. |
|
|
162
|
+
| `:noop` | Don't send anything. Logs what would have been sent. Returns `false`. |
|
|
163
|
+
| `:passthrough` | Send to real channel (explicit opt-out of sandbox safety). |
|
|
164
|
+
|
|
165
|
+
### Mode: Redirect
|
|
166
|
+
|
|
167
|
+
Redirect all messages to a sandbox channel:
|
|
168
|
+
|
|
169
|
+
```ruby
|
|
170
|
+
SlackSender.register(
|
|
171
|
+
token: ENV['SLACK_BOT_TOKEN'],
|
|
172
|
+
channels: { production_alerts: 'C9999999999' },
|
|
173
|
+
sandbox: {
|
|
174
|
+
behavior: :redirect, # Optional - inferred when channel.replace_with is set
|
|
175
|
+
channel: {
|
|
176
|
+
replace_with: 'C1234567890',
|
|
177
|
+
message_prefix: ':test_tube: Sandbox redirect from %s'
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# In sandbox mode, this goes to C1234567890 with a prefix
|
|
183
|
+
SlackSender.call(channel: :production_alerts, text: "Critical alert")
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Mode: Noop (Default)
|
|
187
|
+
|
|
188
|
+
Don't send anything, just log what would have been sent:
|
|
189
|
+
|
|
190
|
+
```ruby
|
|
191
|
+
SlackSender.register(
|
|
192
|
+
token: ENV['SLACK_BOT_TOKEN'],
|
|
193
|
+
channels: { alerts: 'C999' },
|
|
194
|
+
sandbox: { behavior: :noop }
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
# In sandbox mode, this logs the message but doesn't send to Slack
|
|
198
|
+
SlackSender.call(channel: :alerts, text: "Test message")
|
|
199
|
+
# => Logs: "[SANDBOX NOOP] Profile: default | Channel: <#C999> | Text: Test message"
|
|
200
|
+
# => Returns false
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Mode: Passthrough
|
|
204
|
+
|
|
205
|
+
Explicitly opt out of sandbox safety and send to real channels:
|
|
206
|
+
|
|
207
|
+
```ruby
|
|
208
|
+
SlackSender.register(
|
|
209
|
+
token: ENV['SLACK_BOT_TOKEN'],
|
|
210
|
+
channels: { alerts: 'C999' },
|
|
211
|
+
sandbox: { behavior: :passthrough }
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
# In sandbox mode, this still sends to the real channel
|
|
215
|
+
SlackSender.call(channel: :alerts, text: "This goes to production!")
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Required Slack Scopes
|
|
221
|
+
|
|
222
|
+
Your Slack app needs specific OAuth scopes depending on which features you use. Add these under **OAuth & Permissions** → **Bot Token Scopes** in your [Slack app settings](https://api.slack.com/apps).
|
|
223
|
+
|
|
224
|
+
**Minimum scopes for basic messaging:**
|
|
225
|
+
- `chat:write`
|
|
226
|
+
|
|
227
|
+
**Recommended scopes for full functionality:**
|
|
228
|
+
|
|
229
|
+
| Scope | Required For | Notes |
|
|
230
|
+
|-------|--------------|-------|
|
|
231
|
+
| `chat:write` | All messaging | Required for `chat.postMessage` |
|
|
232
|
+
| `chat:write.public` | Public channels | Post to public channels your bot hasn't been added to |
|
|
233
|
+
| `files:write` | File uploads | Required for `files.getUploadURLExternal` and `files.completeUploadExternal` |
|
|
234
|
+
| `files:read` | File metadata | Required if you need thread timestamps from file uploads |
|
|
235
|
+
|
|
236
|
+
After adding scopes, reinstall the app to your workspace to apply the changes.
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Exception Notifications
|
|
241
|
+
|
|
242
|
+
Exception notifications to error tracking services (e.g., Honeybadger) are handled via Axn's `on_exception` handler:
|
|
243
|
+
|
|
244
|
+
```ruby
|
|
245
|
+
Axn.configure do |c|
|
|
246
|
+
c.on_exception = proc do |e, action:, context:|
|
|
247
|
+
Honeybadger.notify(e, context: { axn_context: context })
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
See [Axn configuration documentation](https://teamshares.github.io/axn/reference/configuration#on_exception) for details.
|