slack_sender 0.1.0 → 0.1.1

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.
@@ -1,160 +0,0 @@
1
- # Troubleshooting & FAQ
2
-
3
- [← Back to README](../README.md)
4
-
5
- ## Common Issues
6
-
7
- ### Messages Not Being Sent
8
-
9
- **Check the following:**
10
-
11
- 1. Ensure `SlackSender.config.enabled` is `true` (default)
12
- 2. Verify your profile is registered: `SlackSender.profile(:default)`
13
- 3. Check `SlackSender.config.sandbox_mode?` -- if `true`, check configured [`sandbox.behavior`](./configuration.md#sandbox-mode)
14
- 4. Check that an async backend is available if using `call` (not necessary for `call!`)
15
- 5. Verify your Slack token is valid and has the required scopes
16
-
17
- ### Messages Work in Production but Not in Development
18
-
19
- When `SlackSender.config.sandbox_mode?` is enabled (by default, if `Rails.env.production?` is false), the configured [`sandbox.behavior`](./configuration.md#sandbox-mode) will apply. This likely means logging but skipping send (if `:noop`) or redirecting to a specific channel.
20
-
21
- **If messages are not received in the sandbox channel, check:**
22
-
23
- 1. `SlackSender.config.sandbox_mode?` — should be `true` in development
24
- 2. Your `sandbox.channel.replace_with` channel ID is correct
25
- 3. The bot is invited to the sandbox channel
26
-
27
- ### "NotInChannel" Errors
28
-
29
- The bot must be invited to the channel.
30
-
31
- **Resolution:**
32
-
33
- * Invite the bot to the channel (see [stackoverflow](https://stackoverflow.com/a/68475477))
34
-
35
- ### `missing_scope` Errors
36
-
37
- Your Slack app is missing required OAuth scopes. The error message will tell you which scope is needed:
38
-
39
- ```
40
- Slack API missing_scope error: required scope 'files:write' is not granted.
41
- Add this scope to your Slack app at https://api.slack.com/apps and reinstall the app.
42
- ```
43
-
44
- **To fix:**
45
-
46
- 1. Go to https://api.slack.com/apps and select your app
47
- 2. Navigate to **OAuth & Permissions** → **Bot Token Scopes**
48
- 3. Add the missing scope (e.g., `files:write`)
49
- 4. Reinstall the app to your workspace
50
-
51
- See [Required Slack Scopes](configuration.md#required-slack-scopes) for a complete list.
52
-
53
- ### File Uploads Fail with "channel ID required" Error
54
-
55
- Slack's file upload APIs require channel IDs, not usernames or channel names:
56
-
57
- ```ruby
58
- # These don't work for file uploads
59
- SlackSender.call!(channel: "@username", file: file)
60
- SlackSender.call!(channel: "#general", file: file)
61
-
62
- # Use channel IDs instead
63
- SlackSender.call!(channel: "C024BE91L", file: file) # Public channel
64
- SlackSender.call!(channel: "D032AC32T", file: file) # DM channel
65
- ```
66
-
67
- For DMs, find the DM channel ID (starts with `D`) from Slack's URL when viewing the conversation.
68
-
69
- ### File Uploads Fail with Async Delivery
70
-
71
- File uploads with async delivery (`call`) are supported. Files are uploaded to Slack synchronously, then shared via the background job. Total file size cannot exceed `max_async_file_upload_size` (default 25 MB).
72
-
73
- **If you're hitting the async size limit:**
74
-
75
- ```ruby
76
- # Try increasing the async limit
77
- SlackSender.config.max_async_file_upload_size = 100_000_000 # 100 MB
78
- ```
79
-
80
- ---
81
-
82
- ## FAQ
83
-
84
- ### How do I disable SlackSender temporarily?
85
-
86
- Set `SlackSender.config.enabled = false`. All `call` and `call!` methods will return `false` without sending messages.
87
-
88
- ### Can I send to multiple channels at once?
89
-
90
- Yes, use `channels:` (plural) with async delivery:
91
-
92
- ```ruby
93
- SlackSender.call(channels: [:alerts, :ops], text: "Broadcast message")
94
- ```
95
-
96
- Multi-channel is only supported for async (`call`). Sync (`call!`) requires sending to each channel individually (to avoid confusion about how to report on partial failures). Files are uploaded once and shared to all channels efficiently.
97
-
98
- ### Can I use multiple Slack workspaces?
99
-
100
- Yes, register multiple profiles:
101
-
102
- ```ruby
103
- SlackSender.register(:workspace1, token: TOKEN1, channels: {...})
104
- SlackSender.register(:workspace2, token: TOKEN2, channels: {...})
105
-
106
- SlackSender.profile(:workspace1).call(...)
107
- SlackSender.profile(:workspace2).call(...)
108
- ```
109
-
110
- ### How are rate limits handled?
111
-
112
- When sent async, SlackSender automatically detects rate limit errors and retries with the delay specified in Slack's `Retry-After` header. Retries happen up to 5 times before giving up.
113
-
114
- ### What errors are retried vs discarded?
115
-
116
- **Retried:**
117
- - Rate limit errors (with `Retry-After` delay)
118
-
119
- **Discarded immediately (no retry):**
120
- - `NotInChannel` — Bot not in channel
121
- - `ChannelNotFound` — Channel doesn't exist
122
- - `IsArchived` — Channel is archived
123
-
124
- ### How do I silence archived channel exceptions?
125
-
126
- ```ruby
127
- SlackSender.config.silence_archived_channel_exceptions = true
128
- ```
129
-
130
- This will log the error but not raise an exception.
131
-
132
- ### What's the difference between `call` and `call!`?
133
-
134
- | Method | Delivery | Return Value | Retries |
135
- |--------|----------|--------------|---------|
136
- | `call` | Async (background job) | `true` or `false` | Yes (automatic) |
137
- | `call!` | Sync (immediate) | Thread timestamp or `false` | No |
138
-
139
- Use `call` by default. Use `call!` when you need the `thread_ts` for threading.
140
-
141
- ### How do I test SlackSender in my specs?
142
-
143
- Stub at the profile level:
144
-
145
- ```ruby
146
- allow(SlackSender.profile(:default)).to receive(:call).and_return(true)
147
- allow(SlackSender.profile(:default)).to receive(:call!).and_return("1234567890.123456")
148
- ```
149
-
150
- ---
151
-
152
- ## Compatibility
153
-
154
- - **Ruby**: >= 3.2.1
155
- - **Dependencies**:
156
- - `axn` (>= 0.1.0-alpha.4.1, < 0.2.0)
157
- - `slack-ruby-client` (latest)
158
- - **Optional dependencies**:
159
- - `sidekiq` or `active_job` (for async delivery)
160
- - `active_storage` (for ActiveStorage::Attachment file support)
data/docs/usage.md DELETED
@@ -1,382 +0,0 @@
1
- # Usage Guide
2
-
3
- [← Back to README](../README.md)
4
-
5
- This guide covers sending messages, file uploads, threading, and advanced delivery patterns.
6
-
7
- ## Basic Messages
8
-
9
- ```ruby
10
- # Simple text message
11
- SlackSender.call(
12
- channel: :ops_alerts,
13
- text: ":warning: Redis latency is *elevated*"
14
- )
15
- ```
16
-
17
- Text is parsed as [Slack mrkdwn](https://api.slack.com/reference/surfaces/formatting) by default.
18
-
19
- ### Sync vs Async Delivery
20
-
21
- | Method | Delivery | Return Value | Use When |
22
- |--------|----------|--------------|----------|
23
- | `call(...)` | Async (background job) | `true` or `false` | Default; enables auto-retry for rate limits |
24
- | `call!(...)` | Sync (immediate) | Thread timestamp or `false` | You need the `thread_ts` return value |
25
-
26
- ```ruby
27
- # Async delivery (recommended) - uses Sidekiq or ActiveJob
28
- SlackSender.call(channel: :ops_alerts, text: "Alert")
29
-
30
- # Synchronous delivery (returns thread timestamp)
31
- thread_ts = SlackSender.call!(channel: :deployments, text: "Deploy finished")
32
- ```
33
-
34
- **Note:** If `text:` is explicitly provided but blank (and you did not provide `blocks`, `attachments`, or `files`), SlackSender treats it as a no-op and returns `false`.
35
-
36
- ---
37
-
38
- ## Channel Resolution
39
-
40
- Channels can be specified as symbols (resolved from profile config) or channel IDs:
41
-
42
- ```ruby
43
- # Using symbol (resolved from channels hash)
44
- SlackSender.call(channel: :ops_alerts, text: "Alert")
45
-
46
- # Using channel ID directly
47
- SlackSender.call(channel: "C1234567890", text: "Alert")
48
- ```
49
-
50
- ### Default Channel
51
-
52
- Configure a default channel for a profile to avoid passing `channel:` on every call:
53
-
54
- ```ruby
55
- SlackSender.register(
56
- token: ENV['SLACK_BOT_TOKEN'],
57
- default_channel: :ops_alerts,
58
- channels: {
59
- ops_alerts: 'C1111111111',
60
- deployments: 'C2222222222',
61
- }
62
- )
63
-
64
- # These are equivalent:
65
- SlackSender.call(text: "Alert!") # Uses default_channel
66
- SlackSender.call(channel: :ops_alerts, text: "Alert!") # Explicit channel
67
- ```
68
-
69
- ### Sandbox Mode
70
-
71
- To prevent accidental notifications in development or staging environments, you can enable sandbox mode. When active, all messages—regardless of their target channel—are redirected to a single "sandbox" channel. This ensures you can test notifications safely without spamming real users.
72
-
73
- ---
74
-
75
- ## Rich Messages
76
-
77
- SlackSender passes blocks, attachments, and icon_emoji through [directly to slack-ruby-client](https://www.rubydoc.info/gems/slack-ruby-client/Slack/Web/Api/Endpoints/Chat#chat_postMessage-instance_method).
78
-
79
-
80
- ### Blocks
81
-
82
- ```ruby
83
- SlackSender.call(
84
- channel: :deployments,
85
- blocks: [
86
- {
87
- type: "section",
88
- text: { type: "mrkdwn", text: ":rocket: *Deploy finished* for `my-app`" }
89
- }
90
- ]
91
- )
92
- ```
93
-
94
- ### Attachments
95
-
96
-
97
- ```ruby
98
- SlackSender.call(
99
- channel: :ops_alerts,
100
- attachments: [
101
- {
102
- color: "good",
103
- text: "Autoscaling event completed successfully"
104
- }
105
- ]
106
- )
107
- ```
108
-
109
- ### Custom Emoji
110
-
111
-
112
- ```ruby
113
- SlackSender.call(
114
- channel: :ops_alerts,
115
- text: "Background job queue is healthy",
116
- icon_emoji: "robot"
117
- )
118
- ```
119
-
120
- ---
121
-
122
- ## File Uploads
123
-
124
- File uploads are supported with both synchronous (`call!`) and async (`call`) delivery.
125
-
126
- ```ruby
127
- # Single file - use file: (singular)
128
- SlackSender.call!(
129
- channel: :reports,
130
- text: "Daily ops report attached",
131
- file: File.open("report.pdf")
132
- )
133
-
134
- # Multiple files - use files: (plural)
135
- SlackSender.call!(
136
- channel: :reports,
137
- text: "Daily ops report (details + raw export)",
138
- files: [
139
- File.open("report.pdf"),
140
- File.open("data.csv")
141
- ]
142
- )
143
-
144
- # Async delivery (background job handles sharing)
145
- SlackSender.call(
146
- channel: :alerts,
147
- text: "Multiple files attached",
148
- files: [File.open("report.pdf"), File.open("data.csv")]
149
- )
150
- ```
151
-
152
- ### ⚠️ Channel ID Required
153
-
154
- Unlike normal message sending, Slack's `files_upload_v2` API requires channel _IDs_ (e.g., `C024BE91L`, `D032AC32T`) and does _not_ support usernames or channel names:
155
-
156
- ```ruby
157
- # Works - using channel ID from profile
158
- SlackSender.call!(channel: :alerts, file: file)
159
-
160
- # Works - using channel ID directly
161
- SlackSender.call!(channel: "C024BE91L", file: file)
162
-
163
- # Fails - @username not supported for file uploads
164
- SlackSender.call!(channel: "@username", file: file)
165
-
166
- # Fails - #channel-name not supported for file uploads
167
- SlackSender.call!(channel: "#general", file: file)
168
- ```
169
-
170
- To send files as a DM, use the DM channel ID (starts with `D`) from Slack's URL.
171
-
172
- ### Async File Upload Behavior
173
-
174
- Files are uploaded to Slack's servers synchronously before the background job is enqueued. The job then shares the uploaded files to the channel. This means `call` with files may block briefly during the upload phase.
175
-
176
- ### Size Limits
177
-
178
- - Individual files cannot exceed **1 GB** (Slack's hard limit)
179
- - Total file size for async uploads is limited by `max_async_file_upload_size` (default 25 MB)
180
- - Use `call!` for synchronous upload when you need to upload files larger than `max_async_file_upload_size`
181
-
182
- ### Supported File Types
183
-
184
- - `File` objects
185
- - `Tempfile` objects
186
- - `StringIO` objects
187
- - `ActiveStorage::Attachment` objects (if ActiveStorage is available)
188
- - String file paths (will be opened automatically)
189
- - Any object that responds to `read` and has `original_filename` or `path`
190
-
191
- ---
192
-
193
- ## Threading
194
-
195
- ```ruby
196
- # Get thread timestamp from initial message
197
- thread_ts = SlackSender.call!(
198
- channel: :ops_alerts,
199
- text: ":rotating_light: Elevated 500s detected on /checkout"
200
- )
201
- # thread_ts => "1234567890.123456"
202
-
203
- # Reply to a thread
204
- SlackSender.call(
205
- channel: :ops_alerts,
206
- text: "Mitigation: rolled back to previous release",
207
- thread_ts: thread_ts
208
- )
209
- ```
210
-
211
- ---
212
-
213
- ## Multi-Channel Delivery
214
-
215
- Send the same message to multiple channels with a single call using `channels:` (plural):
216
-
217
- ```ruby
218
- # Async delivery to multiple channels
219
- SlackSender.call(
220
- channels: [:ops_alerts, :deployments],
221
- text: ":rocket: Deploy finished for my-app"
222
- )
223
- ```
224
-
225
- **Key behaviors:**
226
- - Multi-channel delivery is only supported via async (`call`). Using `call!` with `channels:` raises an error (due to complexity in handling partial failures)
227
- - Files are uploaded once and shared to all channels efficiently
228
- - Each channel receives a separate background job with independent retry handling
229
- - Single-element arrays (e.g., `channels: [:ops_alerts]`) are normalized to `channel:`
230
-
231
- ```ruby
232
- # Sync multi-channel not supported
233
- SlackSender.call!(channels: [:a, :b], text: "...") # Raises ArgumentError
234
-
235
- # Use async instead
236
- SlackSender.call(channels: [:a, :b], text: "...")
237
-
238
- # Or send individually if you need sync (but beware :b may fail with e.g. a ratelimit error after :a has already been delivered)
239
- [:a, :b].each { |ch| SlackSender.call!(channel: ch, text: "...") }
240
- ```
241
-
242
- ---
243
-
244
- ## User Group Mentions
245
-
246
- Format user group mentions with `SlackSender.group_link`. This is sandbox-aware and supports symbol keys from your profile's `user_groups` registry:
247
-
248
- ```ruby
249
- SlackSender.group_link(:on_call)
250
- # => "<!subteam^S1234567890>"
251
- ```
252
-
253
- If `sandbox.user_group.replace_with` is configured and the app is in sandbox mode, `group_link` will replace the requested group with the sandbox user_group instead:
254
-
255
- ```ruby
256
- SlackSender.register(
257
- token: ENV['SLACK_BOT_TOKEN'],
258
- user_groups: { engineers: 'S1234567890' },
259
- sandbox: {
260
- user_group: { replace_with: 'S_DEV_GROUP' }
261
- }
262
- )
263
-
264
- # In sandbox mode, this returns the sandbox user_group mention
265
- SlackSender.group_link(:engineers)
266
- # => "<!subteam^S_DEV_GROUP>"
267
- ```
268
-
269
- ### Other Formatting Helpers
270
-
271
- For user mentions, channels, links, and other formatting, use the `Slack::Messages::Formatting` helpers provided by the underlying [slack-ruby-client](https://github.com/slack-ruby/slack-ruby-client#message-formatting):
272
-
273
- ```ruby
274
- SlackSender.call(
275
- channel: :ops_alerts,
276
- text: [
277
- ":rotating_light: Incident acknowledged by #{Slack::Messages::Formatting.user_link(user.slack_id)}",
278
- Slack::Messages::Formatting.url_link('Incident timeline', 'https://status.example.com/incidents/123'),
279
- ].join("\n")
280
- )
281
- ```
282
-
283
- ---
284
-
285
- ## Rate Limiting & Retries
286
-
287
- When using async delivery, SlackSender automatically:
288
-
289
- - Detects rate limit errors from Slack API responses
290
- - Extracts `Retry-After` header value
291
- - Schedules retry with appropriate delay (+ jitter)
292
- - Retries up to 5 times before giving up
293
-
294
- Rate limit handling works with both Sidekiq and ActiveJob backends.
295
-
296
- ---
297
-
298
- ## Error Handling
299
-
300
- The following errors will log warning but are _not_ retried (discarded immediately):
301
- - `NotInChannel` - Bot not in channel
302
- - `ChannelNotFound` - Channel doesn't exist
303
- - `IsArchived` - Channel is archived (NOTE: skips warning if `config.silence_archived_channel_exceptions = true`)
304
-
305
- Other Slack API errors will be re-raised (so will retry if sent via background processor).
306
-
307
- ---
308
-
309
- ## Examples
310
-
311
- ### Deployment Notifications
312
-
313
- ```ruby
314
- SlackSender.call(
315
- channel: :deployments,
316
- text: ":rocket: Deploy finished for `my-app` (#{Rails.env})",
317
- blocks: [
318
- {
319
- type: "section",
320
- fields: [
321
- { type: "mrkdwn", text: "*Environment:*\n#{Rails.env}" },
322
- { type: "mrkdwn", text: "*Version:*\n#{ENV['APP_VERSION']}" }
323
- ]
324
- }
325
- ]
326
- )
327
- ```
328
-
329
- ### Error Alerts
330
-
331
- ```ruby
332
- SlackSender.call(
333
- channel: :ops_alerts,
334
- text: ":rotating_light: Payment processing error",
335
- attachments: [
336
- {
337
- color: "danger",
338
- fields: [
339
- { title: "Error", value: error.message, short: false },
340
- { title: "User", value: user.email, short: true }
341
- ]
342
- }
343
- ]
344
- )
345
- ```
346
-
347
- ### Scheduled Reports with File Upload
348
-
349
- ```ruby
350
- # Generate and send report (synchronous for thread_ts)
351
- report = generate_daily_report
352
- thread_ts = SlackSender.call!(
353
- channel: :reports,
354
- text: "Daily Report - #{Date.today}",
355
- file: report.to_file
356
- )
357
-
358
- # Follow up in thread
359
- SlackSender.call(
360
- channel: :reports,
361
- text: "Summary: no SEV incidents; deploys are healthy",
362
- thread_ts: thread_ts
363
- )
364
- ```
365
-
366
- ### Dedicated Notifiers
367
-
368
- For complex notifications, you can simplify your code by creating dedicated notifier classes using `SlackSender::Notifier`. This allows you to encapsulate logic, use callbacks, and keep your business logic clean. See documentation on [SlackSender::Notifier Base Class](axn_integration.md#slacksendernotifier-base-class) for details.
369
-
370
- ```ruby
371
- class DeployNotifier < SlackSender::Notifier
372
- expects :service_name
373
-
374
- notify do
375
- channel :deployments
376
- text { ":rocket: #{service_name} deployed successfully!" }
377
- end
378
- end
379
-
380
- # Usage
381
- DeployNotifier.call(service_name: "payment-service")
382
- ```