Skip to main content

Documentation Index

Fetch the complete documentation index at: https://acaas.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

Some people send every Slack message in ALL CAPS!!!! This walkthrough builds a small Python tool that detects those messages, runs them through /v1/whisper to lowercase them, and emits a calmer version. You can drop the result into a chat display, an internal feed, or a late-night digest where everyone is too tired for shouting. Two paths are covered:
  • Batch mode. Read a Slack export (or any list of messages) from JSON, rewrite the shouty ones, write the result back. Runnable without any Slack credentials.
  • Live mode. A Slack Bolt app that listens to channel messages and posts a calmed reply in-thread when shoutiness crosses a threshold.

What you will build

Given an input file messages.json:
[
  {"user": "U01ALEX", "ts": "1714060800.000100", "text": "GOOD MORNING TEAM!!! WHO IS READY TO SHIP?!?!"},
  {"user": "U02JANE", "ts": "1714060860.000200", "text": "morning! coffee first, ship second"},
  {"user": "U01ALEX", "ts": "1714060920.000300", "text": "I AM ALREADY ON MY THIRD CUP LET'S GOOOOO"},
  {"user": "U03SAM",  "ts": "1714061040.000400", "text": "the staging deploy is green, fyi"}
]
The tool produces quiet.json:
[
  {"user": "U01ALEX", "ts": "1714060800.000100", "text": "good morning team!!! who is ready to ship?!?!", "calmed": true},
  {"user": "U02JANE", "ts": "1714060860.000200", "text": "morning! coffee first, ship second", "calmed": false},
  {"user": "U01ALEX", "ts": "1714060920.000300", "text": "i am already on my third cup let's gooooo", "calmed": true},
  {"user": "U03SAM",  "ts": "1714061040.000400", "text": "the staging deploy is green, fyi", "calmed": false}
]
Lowercase messages pass through unchanged. Only the screamers get whispered.

Prerequisites

  • Python 3.10 or newer.
  • pip install requests for batch mode.
  • pip install slack-bolt for live mode (optional).
  • An ACAAS API key in ACAAS_API_KEY. The demo key works.
  • For live mode: a Slack workspace where you can install a bot, plus a bot token (SLACK_BOT_TOKEN) and an app-level token (SLACK_APP_TOKEN).
export ACAAS_API_KEY="demo"

Step 1: Decide what counts as shouty

Before calling the API, decide which messages deserve a whisper. A simple heuristic combines two signals: how much of the message is uppercase, and how many exclamation marks it carries.
shoutiness.py
import string


def uppercase_ratio(text: str) -> float:
    letters = [c for c in text if c in string.ascii_letters]
    if not letters:
        return 0.0
    upper = sum(1 for c in letters if c.isupper())
    return upper / len(letters)


def exclamation_density(text: str) -> float:
    if not text:
        return 0.0
    return text.count("!") / len(text)


def is_shouty(text: str, *, min_chars: int = 8) -> bool:
    if len(text) < min_chars:
        return False  # Single words are allowed to be loud.
    return uppercase_ratio(text) > 0.6 or exclamation_density(text) > 0.05
The thresholds are deliberately conservative. A message is only flagged as shouty if more than 60% of its letters are uppercase or if exclamation marks make up more than 5% of the text. Tune them to taste — a more permissive 0.4 ratio also catches mixed-case yelling like “Why Are We Doing This Again.” A quick sanity check:
>>> is_shouty("good morning team")
False
>>> is_shouty("GOOD MORNING TEAM!!!")
True
>>> is_shouty("ok")
False
>>> is_shouty("the staging deploy is green, fyi")
False

Step 2: Whisper a single message

Wrap /v1/whisper in a function. Reuse the Client pattern from the release notes cookbook, or use this minimal inline version:
whisperer.py
import os

import requests

BASE_URL = "https://api.acaas.example.com"


def whisper(text: str) -> str:
    api_key = os.environ["ACAAS_API_KEY"]
    resp = requests.post(
        f"{BASE_URL}/v1/whisper",
        headers={"X-API-Key": api_key, "Content-Type": "application/json"},
        json={"text": text},
        timeout=10,
    )
    resp.raise_for_status()
    return resp.json()["result"]
For production code, fold in retry handling for 429 and 5xx as shown in the errors reference. For this walkthrough, the bare call keeps the focus on the use case.

Step 3: Process a batch

Wire the heuristic and the whisperer together. The function below preserves message order and metadata, only mutating text when shoutiness fires.
calm_batch.py
import json
from pathlib import Path

from shoutiness import is_shouty
from whisperer import whisper


def calm_messages(input_path: Path, output_path: Path) -> None:
    messages = json.loads(input_path.read_text())
    calmed = []

    for message in messages:
        if is_shouty(message["text"]):
            calmed.append({**message, "text": whisper(message["text"]), "calmed": True})
        else:
            calmed.append({**message, "calmed": False})

    output_path.write_text(json.dumps(calmed, indent=2) + "\n")


if __name__ == "__main__":
    calm_messages(Path("messages.json"), Path("quiet.json"))
Run it:
python calm_batch.py
The shouty messages now read at conversational volume. Original timestamps, user IDs, and any other fields you carry through are preserved untouched — only text is rewritten and a calmed boolean is added so downstream code can highlight or count rewrites.

Step 4: Wire it to a live Slack channel

Skip this section if batch processing is enough. For real-time calming, use Slack Bolt for Python with Socket Mode so you do not need a public URL.
quiet_bot.py
import os

from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler

from shoutiness import is_shouty
from whisperer import whisper

app = App(token=os.environ["SLACK_BOT_TOKEN"])


@app.event("message")
def calm_if_needed(event, client):
    if event.get("subtype") or event.get("bot_id"):
        return  # Ignore edits, joins, and the bot's own posts.

    text = event.get("text", "")
    if not is_shouty(text):
        return

    client.chat_postMessage(
        channel=event["channel"],
        thread_ts=event["ts"],
        text=f"_at conversational volume:_\n> {whisper(text)}",
    )


if __name__ == "__main__":
    SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start()
A few choices worth calling out:
  • Reply in-thread, do not edit. Slack’s API only lets a bot edit messages it posted itself. Posting a calmed reply in-thread keeps the original message intact and lets the author see the contrast.
  • Skip subtypes and bot messages. Channel joins, edits, and the bot’s own posts arrive as the same message event. Filtering them prevents loops and noise.
  • Italics for the prefix. _at conversational volume:_ reads as a gentle aside in Slack, not a callout.
Required scopes for the bot: chat:write, channels:history, groups:history, im:history, mpim:history, plus the corresponding message.* event subscriptions. Install the app to a test channel before turning it loose on #general.
Auto-rewriting other people’s messages — even as a thread reply — can feel passive-aggressive. Pilot the bot in an opt-in channel first, and consider a slash command (/whisper) as a less invasive alternative.

Step 5: Smoke-test the heuristic

Before turning the bot on, run the heuristic against a varied corpus to make sure the threshold is tuned for your team. The included fixture covers the usual cases:
smoke.py
import json
from pathlib import Path

from shoutiness import is_shouty

messages = json.loads(Path("messages.json").read_text())
for m in messages:
    flag = "SHOUTY" if is_shouty(m["text"]) else "ok"
    print(f"{flag:>6}  {m['text']}")
Expected output on the sample fixture:
SHOUTY  GOOD MORNING TEAM!!! WHO IS READY TO SHIP?!?!
    ok  morning! coffee first, ship second
SHOUTY  I AM ALREADY ON MY THIRD CUP LET'S GOOOOO
    ok  the staging deploy is green, fyi
Adjust thresholds in shoutiness.py until your own corpus produces sensible flags before connecting to a live workspace.

What you learned

  • Heuristic before API call. A cheap local check (is_shouty) keeps most messages out of the request path entirely. Only the loud ones cost a quota slot.
  • Preserve metadata, mutate only what you must. Carrying user, ts, and any other fields through unchanged makes the calmed output a drop-in replacement for the original.
  • Reply, do not rewrite. Posting a thread reply respects authorship and Slack’s API permissions.
  • Pilot before you scale. Auto-rewriting tone is a social intervention as much as a technical one. Start in a small, consenting channel.

Next steps

Release notes amplifier

The companion cookbook — uses scream and emphasize end-to-end.

Whisper reference

Full request and response schema for /v1/whisper.

Errors

Add retry handling once you move past the demo key.

Rate limiting

Pace the bot if your channel is genuinely chatty.