From 31b8244805955968a128e3779e0e64636d6c52b4 Mon Sep 17 00:00:00 2001 From: happybell80 Date: Sat, 21 Feb 2026 20:24:08 +0900 Subject: [PATCH] docs: add Company-X slack message send research note --- ..._companyx_slack_메시지_전송_방법.md | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 journey/research/260221_companyx_slack_메시지_전송_방법.md diff --git a/journey/research/260221_companyx_slack_메시지_전송_방법.md b/journey/research/260221_companyx_slack_메시지_전송_방법.md new file mode 100644 index 0000000..80f84a4 --- /dev/null +++ b/journey/research/260221_companyx_slack_메시지_전송_방법.md @@ -0,0 +1,84 @@ +# Company-X Slack 메시지 전송 방법 (운영 검증) + +## 작성일 +- 2026-02-21 (KST) + +## 목적 +- Company-X 팀의 로빙 채널에 테스트 메시지를 안전하게 전송하는 방법을 기록한다. +- Gateway 경유 실패 시, 원인 분리용 직접 전송 절차를 남긴다. + +## 대상 +- Slack Team: `T09C98KB933` (Company-X Team) +- Slack Channel: `C09CP4MDX71` (robeing-team) + +## 확인된 전송 경로 +- 기본 경로: `Slack -> robeing-gateway(/slack/events) -> rb8001(/api/slack/events) -> skill-slack -> Slack` +- 우회 경로(원인 분리): `slack_workspace.bot_token` 조회 후 `chat.postMessage` 직접 호출 + +## 실측 결과 (2026-02-21) +- Gateway `/slack/events` 경유: `500 Internal Server Error` +- 직접 `chat.postMessage` 호출: 성공 (`ok: true`) +- 메시지: `hello world` +- Slack `ts`: `1771672969.619299` +- 시각: + - UTC: `2026-02-21 11:22:49` + - KST: `2026-02-21 20:22:49` + +## 재현 절차 +1. Gateway 헬스 확인 +```bash +curl -i -sS -m 5 http://127.0.0.1:8100/healthz | sed -n '1,8p' +``` + +2. Company-X 팀 토큰 존재 확인 (`slack_workspace`) +```bash +docker exec -i robeing-gateway python - <<'PY' +import asyncio, os, asyncpg +async def main(): + dsn = os.environ['DATABASE_URL'].replace('postgresql+asyncpg://', 'postgresql://') + conn = await asyncpg.connect(dsn) + row = await conn.fetchrow(""" + SELECT slack_team_id, left(bot_token,12) token_prefix + FROM slack_workspace + WHERE slack_team_id='T09C98KB933' + LIMIT 1 + """) + print(dict(row) if row else None) + await conn.close() +asyncio.run(main()) +PY +``` + +3. 채널 메시지 전송 (`chat.postMessage`) +```bash +docker exec -i robeing-gateway python - <<'PY' +import asyncio, os, asyncpg, requests +TEAM_ID='T09C98KB933' +CHANNEL='C09CP4MDX71' +TEXT='hello world' +async def get_token(): + dsn = os.environ['DATABASE_URL'].replace('postgresql+asyncpg://', 'postgresql://') + conn = await asyncpg.connect(dsn) + row = await conn.fetchrow('SELECT bot_token FROM slack_workspace WHERE slack_team_id=$1 LIMIT 1', TEAM_ID) + await conn.close() + return row['bot_token'] if row else None +token = asyncio.run(get_token()) +resp = requests.post( + 'https://slack.com/api/chat.postMessage', + headers={'Authorization': f'Bearer {token}', 'Content-Type': 'application/json; charset=utf-8'}, + json={'channel': CHANNEL, 'text': TEXT}, + timeout=10, +) +print(resp.status_code) +print(resp.text) +PY +``` + +## 운영 주의사항 +- 봇 토큰 평문을 문서/로그/커밋에 남기지 않는다. +- 실패 원인 분리는 다음 순서로 한다. + 1. Gateway health + 2. `slack_workspace` 팀/토큰 매핑 + 3. Slack 사용자 UUID 매핑 + 4. rb8001 `/api/slack/events` 응답코드 +- 직접 `chat.postMessage`는 장애 분리용 검증으로만 사용하고, 정상 운영은 Gateway 경유 경로를 우선한다.