Biz Telegram Payment Notify — Sepay가 입금을 수신하면 실시간 알림
이 스킬은 Next.js 프로젝트의 기존 Sepay 웹훅에 Telegram 봇 알림을 연결합니다. 고객이 Sepay에 연결된 은행 계좌로 송금하는 데 성공할 때마다 스킬은 Telegram 봇을 통해 소유자(또는 영업팀 그룹 채팅방)에게 고객 이름, 전화번호, 금액, 제품, 시간 정보가 포함된 실시간 메시지를 보냅니다.
철학: 고객용 이메일 자동 응답(Resend)과 소유자용 Telegram 알림은 서로 다른 두 대상에게 서비스를 제공합니다. 이메일은 고객을 위한 격식 있는 소통입니다. Telegram은 소유자를 위한 '핑(ping)'입니다. — 바깥에 나가 있을 때 Telegram을 열면 새 주문을 바로 확인할 수 있으며, 100개의 다른 이메일 중에서 찾기 위해 Gmail을 열 필요가 없습니다. Telegram 지연 시간은 2초 미만인 반면, 이메일은 30-60초입니다.
Slack/Discord/Zalo 대신 Telegram을 사용하는 이유: Telegram 봇 설정은 5분(토큰 1개 + chat_id 1개)이면 충분하며, 워크스페이스/서버 관리자가 필요 없고, 무제한 무료 메시지, 이메일보다 강력한 푸시 알림, iOS/Android/데스크톱 네이티브 앱을 제공합니다. Slack은 워크스페이스와 초대가 필요합니다. Discord는 서버와 권한 설정이 필요합니다. Zalo OA는 인증된 비즈니스 계정(승인까지 3-7일)과 월간 활성 사용자 요금이 필요합니다. Telegram은 소유자 측 알림을 위한 최소 저항 경로입니다.
결과물: 코드 패치 + 봇 설정 가이드(단계별) + 환경 변수 목록 + 3단계 테스트 계획. 스킬은 Telegram 전송을 자체 테스트하지 않습니다 — 사용자가 먼저 봇 토큰과 chat_id를 붙여넣도록 요구합니다. 이러한 자격 증명은 사용자의 자산이기 때문입니다.
이 스킬을 사용해야 하는 경우
- 사용자가 Sepay 웹훅 설정을 막 마쳤고, 고객이 송금할 때 Telegram을 통해 실시간 알림을 받고 싶어 합니다.
- 사용자가 이미
/api/payment-success(또는 이와 동등한)를 통해 Resend 이메일을 처리하고 있으며, 병렬로 Telegram 알림을 추가하고자 합니다. — 새 라우트를 만들거나 로직을 중복하지 않습니다. - 사용자가 "주문이 들어오면 알려주세요"라고 말했고, 컨텍스트가 Sepay 통합이 있는 판매 랜딩 페이지입니다.
다음과 같은 경우에는 이 스킬을 사용하지 마십시오:
- 사용자가 Slack/Discord 알림을 원하는 경우 → 다른 사용 사례입니다.
- 사용자가 Zalo OA 봇을 원하는 경우 → OA 인증된 비즈니스 + 다른 비용 구조가 필요합니다.
- 사용자가 Sepay 웹훅을 설정하지 않은 경우(은행 계좌가 Sepay에 연결되지 않았거나, 웹훅을 수신할 엔드포인트가 없는 경우) → 이 스킬은 Sepay를 처음부터 설정하지 않으며, 기존 웹훅에만 연결합니다.
- 사용자가 소유자에게 이메일만 보내기를 원하는 경우 →
biz-email-setup에 이미 Email B(소유자 알림)가 있습니다.
전체 워크플로우 (6단계)
Phase 0: 기존 Sepay 웹훅 라우트 감지 (경로 + 프레임워크 + 핸들러 형태)
↓
Phase 0.5: 리드 저장소 감지 (lib/leads-kv.ts from /biz-setup-sepay-payment)
→ 있음: 실제 KV 조회 사용
→ 없음: 2가지 옵션 제공 (원시 모드 또는 /biz-setup-sepay-payment 먼저 실행)
↓
Phase 1: 사용자에게 Telegram 봇 생성 안내 (BotFather → 토큰 → chat_id)
↓ [게이트 — 사용자가 토큰 + chat_id를 붙여넣을 때까지 대기]
↓
Phase 2: 메시지 형식 초안 작성 → 사용자에게 미리보기 표시 → 확인
↓
Phase 3: 코드 연결 (lib/telegram.ts 헬퍼 + 웹훅 핸들러에 통합 + 환경 변수)
↓
Phase 4: 테스트 계획 (curl 봇 직접 → 시뮬레이션된 Sepay 페이로드 → 프로덕션 확인)
Phase 1과 Phase 4에는 사용자 대기 게이트가 있습니다. 사용자가 수동 작업(토큰 가져오기, 프로덕션 확인)을 수행해야 하기 때문입니다. Phase 0/0.5/2/3은 스킬이 능동적으로 처리합니다.
Phase 0 — 기존 Sepay 웹훅 감지
이 스킬은 새 라우트를 만들지 않고 기존 웹훅에 연결합니다. 먼저 핸들러를 찾습니다.
웹훅 라우트 찾기 — 일반적인 키워드로 grep:
# 프로젝트 루트에서
grep -r "sepay" --include="*.ts" --include="*.js" --include="*.tsx" -l
grep -r "transferAmount\|transferType\|payment-success\|payment-webhook" --include="*.ts" --include="*.js" -l
3가지 일반적인 시나리오:
| 시나리오 | 기존 경로 | 조치 |
|---|---|---|
사용자가 biz-email-setup 파이프라인 사용 | app/api/payment-success/route.ts (또는 pages/api/payment-success.ts)에 sendCustomerEmail + sendOwnerEmail을 호출하는 Resend가 이미 있음 | 동일한 Promise.allSettled에 sendTelegramNotification 연결 |
| 사용자가 Sepay 전용 라우트를 가지고 있음 | app/api/sepay-webhook/route.ts 또는 app/api/webhook/sepay/route.ts | Sepay 페이로드를 파싱하고 검증한 후(transferType === 'in' 분기) 연결 |
| 어떤 라우트도 찾을 수 없음 | — | 중단. 사용자에게 알림: "Sepay를 수신하는 웹훅 라우트를 찾을 수 없습니다. Sepay 웹훅을 먼저 설정(은행을 Sepay에 연결 → 도메인을 가리키는 웹훅 URL 생성)한 후 돌아와서 Telegram을 연결해 주세요. 또는 웹훅이 다른 이름의 엔드포인트에 있다면 경로를 알려주세요." |
기존 핸들러 읽기 — 형태 이해:
- Sepay 요청을 인증합니까? (Authorization 헤더
Apikey또는 HMAC) id또는referenceCode를 기준으로 중복 제거합니까? (Sepay는 non-200 응답 시 재시도 → 중복 알림 방지를 위해 중복 제거 필요)content(주문 ID가 포함된 송금 메시지)를 기준으로 리드를 조회합니까?- Sepay에 반환하는 응답 형식:
{ success: true }또는 일반200 OK?
기존 핸들러에 중복 제거가 없는 경우 — 사용자에게 경고하지만 차단하지는 않습니다. 스킬은 Telegram만 연결합니다. 중복 제거는 결제 로직의 문제입니다. 필요한 경우 별도로 중복 제거를 추가하도록 제안합니다.
Phase 0.5 — 리드 저장소 감지 (중요)
Sepay 웹훅은 content(고객이 송금 시 붙여넣은 텍스트) + transferAmount + referenceCode만 보냅니다. Sepay는 고객이 누구인지 알지 못합니다 — content(주문 ID 포함)를 기준으로 로컬 저장소를 조회해야 합니다.
스킬이 3가지를 확인합니다:
# 1. 리드 저장소 헬퍼 존재 확인
test -f lib/leads-kv.ts || test -f lib/leads-kv.js && echo "lead store: ✓"
# 2. Vercel KV 환경 변수 확인
grep -E "KV_REST_API_URL|KV_REST_API_TOKEN" .env.local && echo "KV env: ✓"
# 3. @vercel/kv 설치 확인
grep '"@vercel/kv"' package.json && echo "KV package: ✓"
3가지 시나리오:
| 시나리오 | 조치 |
|---|---|
| 3가지 모두 ✓ — 리드 저장소 준비 완료 | Phase 1로 진행, 웹훅 핸들러에서 lib/leads-kv.ts의 getLeadByOrderId() 사용 |
| 1개 이상 누락 — 설정되지 않음 | 중단, 사용자에게 2가지 옵션 질문 (아래 참조) |
누락된 경우, 사용자에게 질문:
리드 저장소(Vercel KV)가 설정되지 않은 것을 발견했습니다. Sepay 웹훅은 고객이 누구인지 알지 못합니다 — 송금 내용과 금액만 있습니다. 다음 중 원하는 옵션을 선택해 주세요:
옵션 1 — 먼저
/biz-setup-sepay-payment를 통해 전체 리드 저장소 설정 (권장):
- 해당 스킬이 Vercel KV를 설정하고, lib/leads-kv.ts를 스캐폴딩하며, 사용자 등록 시 리드를 저장하도록 양식 제출을 연결합니다.
- 그런 다음 여기로 돌아와서 고객 이름/전화번호/이메일을 조회하는 Telegram을 연결합니다.
- Telegram 알림에 표시됩니다: "Nguyễn Văn A — 0901234567 — [email protected] — 499K — AI Agent 코스"
옵션 2 — 원시 모드: 리드를 조회하지 않고 Sepay 페이로드를 Telegram으로 직접 전송:
- Telegram에만 표시됩니다: "Vietcombank — 499.000đ — content: DH001 0901234567 — 14:32"
- 소유자는 내용을 통해 고객을 스스로 추측하고 Resend 이메일 로그와 교차 확인해야 합니다.
- 간단하고 즉시 실행 가능하지만 UX가 떨어집니다.
1 또는 2를 선택해 주세요.
사용자가 1을 선택한 경우 → 이 스킬을 중단하고 /biz-setup-sepay-payment를 제안합니다. 사용자가 설정을 마친 후 이 스킬로 돌아옵니다.
사용자가 2를 선택한 경우 → Phase 1로 진행하고 원시 템플릿을 사용합니다(templates/lib-telegram-raw.ts 참조).
Phase 1 — 사용자에게 Telegram 봇 생성 안내
자세한 지침과 텍스트 스크린샷은 references/telegram-bot-setup.md를 참조하세요. 사용자를 위한 3단계 요약:
1단계 — @BotFather를 통해 새 봇 만들기
1. Telegram 열기 (모바일/데스크톱)
2. 검색: @BotFather → 시작 버튼 클릭
3. 명령어 입력: /newbot
4. BotFather가 봇 이름을 묻습니다 — 표시 이름 입력 (예: "브랜드 X 주문")
5. BotFather가 사용자 이름을 묻습니다 — "_bot"으로 끝나는 사용자 이름 입력 (예: brandx_orders_bot) — 전역적으로 고유해야 함
6. BotFather가 다음 줄이 포함된 메시지를 반환합니다:
"Use this token to access the HTTP API: 1234567890:AAxxxxxxxxxxxxxxxxxxxxxx"
7. 해당 토큰을 복사하여 붙여넣으세요.
보안 참고: 이 토큰은 봇을 제어할 수 있는 권한입니다. Git에 커밋하지 말고 공개적으로 공유하지 마세요. 실수로 유출된 경우 @BotFather → /token → /revoke로 돌아가 새 토큰을 만드세요.
2단계 — chat_id 가져오기
1:1 채팅 (소유자만 수신) — 솔로프리너에게 권장:
1. Telegram에서 검색: @userinfobot → 시작 버튼 클릭
2. 봇이 "Id: 123456789"가 포함된 메시지를 반환합니다 — 해당 번호를 복사합니다.
3. 그것이 당신의 chat_id입니다.
그룹 채팅 (영업팀이 함께 수신) — 팀에게 권장:
1. 새 Telegram 그룹 만들기 (또는 기존 그룹 사용)
2. 그룹 이름 클릭 → 구성원 추가 → 방금 만든 봇 검색 (@brandx_orders_bot 사용자 이름으로) → 추가
3. 그룹에서 입력: /start@brandx_orders_bot (또는 아무 메시지나)
4. 브라우저를 열고 다음 주소로 접속:
https://api.telegram.org/bot<TOKEN>/getUpdates
(<TOKEN>을 1단계의 토큰으로 바꾸기)
5. JSON 응답에서 "chat":{"id":-1001234567890,...}를 찾습니다 — 해당 번호를 복사합니다 (있는 경우 빼기 기호 유지)
6. 그것이 그룹의 chat_id입니다.
채널 (단방향 브로드캐스트, 토론 없음) — chat_id가 -100으로 시작합니다. 가져오는 방법은 그룹과 동일하지만 봇을 채널 관리자로 추가합니다.
3단계 — 스킬에 붙여넣기
사용자가 다음을 붙여넣을 때까지 기다립니다:
TELEGRAM_BOT_TOKEN=1234567890:AAxxxxxxxxxxxxxxxxxxxxxx
TELEGRAM_CHAT_ID=123456789
중요 게이트: 사용자가 두 값을 모두 붙여넣을 때까지 Phase 2로 진행하지 마십시오. 사용자가 "다른 곳에 이미 있습니다"라고 말하더라도 여전히 붙여넣기를 기다리십시오. Phase 3에서 .env.local에 주입해야 하기 때문입니다.
Phase 2 — 메시지 형식 초안 작성
기본 형식은 베트남어, 모바일 친화적(Telegram HTML 렌더링):
🎉 <b>신규 주문</b>
👤 <b>{{name}}</b>
📞 {{phone}}
📧 {{email}}
💰 <b>{{amount}}</b> — {{product_name}}
🕐 {{time_vn}}
🔗 거래 코드: <code>{{reference_code}}</code>
변수 주입 후:
🎉 신규 주문
👤 Nguyễn Văn A
📞 0901 234 567
📧 [email protected]
💰 499.000đ — AI Agent 코스
🕐 14:32 14/05/2026
🔗 거래 코드: FT26134567890
사용자에게 미리보기 표시, 질문:
- 이 형식이 괜찮습니까? 추가/제거할 필드가 있습니까? (예: UTM 소스, 일일 주문 수, 고객 Zalo 링크 추가)
- 이모지가 너무 많습니까? (일부 사용자는 일반 텍스트를 선호)
- CRM/시트로 연결되는 인라인 버튼(CTA)을 추가하시겠습니까? (고급 — 스킬이 인라인 키보드를 추가할 수 있음)
형식 규칙:
- HTML 모드(
parse_mode: "HTML") — Telegram은<b>,<i>,<u>,<code>,<pre>,<a href>를 지원합니다.<br>은 지원하지 않습니다 —\n을 사용하세요. - VND 금액 형식:
499.000đ(천 단위 구분 마침표, 끝에đ포함) 또는 깔끔하게499K/1.99M를 사용합니다.offer.jsonvoice설정이 있으면 이를 읽습니다. - 전화번호 형식:
0901 234 567(3-3-3 간격) — 모바일에서 더 쉽게 스캔할 수 있습니다. - 베트남 시간:
HH:mm dd/mm/yyyy— UTC가 아닌 베트남 시간(UTC+7)으로 설정합니다. - 이모지 스팸을 피하세요(메시지당 5개 이상의 이모지는 전문성을 떨어뜨립니다).
Phase 3 — 코드 연결
3가지 변경 사항:
3.1 헬퍼 함수 lib/telegram.ts 만들기
templates/lib-telegram.ts (TypeScript) 또는 templates/lib-telegram.js (vanilla JS)를 읽습니다. 내용:
// lib/telegram.ts
type TelegramPayload = {
name: string;
phone: string;
email: string;
amount: number; // VND, raw number (예: 499000)
productName: string;
referenceCode?: string;
};
export async function sendTelegramNotification(payload: TelegramPayload): Promise<void> {
const token = process.env.TELEGRAM_BOT_TOKEN;
const chatId = process.env.TELEGRAM_CHAT_ID;
if (!token || !chatId) {
console.error('[telegram] Missing TELEGRAM_BOT_TOKEN or TELEGRAM_CHAT_ID');
return; // throw하지 않음 — Telegram 실패가 결제 응답을 차단하지 않도록 함
}
const text = formatMessage(payload);
try {
const res = await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
chat_id: chatId,
text,
parse_mode: 'HTML',
disable_web_page_preview: true,
}),
});
if (!res.ok) {
const errBody = await res.text();
console.error(`[telegram] Send failed: ${res.status} ${errBody}`);
}
} catch (err) {
console.error('[telegram] Network error:', err);
// Swallow — Telegram 실패가 Sepay를 재시도하게 하지 않음
}
}
function formatMessage(p: TelegramPayload): string {
const amountStr = p.amount.toLocaleString('vi-VN') + 'đ';
const phoneStr = p.phone.replace(/(\d{4})(\d{3})(\d{3})/, '$1 $2 $3');
const now = new Date().toLocaleString('vi-VN', {
timeZone: 'Asia/Ho_Chi_Minh',
day: '2-digit', month: '2-digit', year: 'numeric',
hour: '2-digit', minute: '2-digit',
});
return [
'🎉 <b>신규 주문</b>',
'',
`👤 <b>${escapeHtml(p.name)}</b>`,
`📞 ${phoneStr}`,
`📧 ${escapeHtml(p.email)}`,
`💰 <b>${amountStr}</b> — ${escapeHtml(p.productName)}`,
`🕐 ${now}`,
p.referenceCode ? `\n🔗 거래 코드: <code>${escapeHtml(p.referenceCode)}</code>` : '',
].filter(Boolean).join('\n');
}
function escapeHtml(s: string): string {
return s.replace(/[<>&]/g, c => ({ '<': '<', '>': '>', '&': '&' }[c]!));
}
오류를 삼키는 이유: Sepay는 non-200 응답을 수신하면 웹훅을 재시도합니다. Telegram 시간 초과/다운으로 웹훅이 중단되면 Sepay가 재시도하고, 각 재시도는 이메일을 다시 보내고 결제를 다시 기록하여 사용자가 단일 주문에 대해 5개의 이메일과 5개의 Telegram 알림을 받게 됩니다. Telegram은 최선 노력 사이드 채널이지 중요 경로가 아닙니다.
3.2 Sepay 웹훅 핸들러에 연결
기존 핸들러를 읽고 결제 성공 처리 위치(Sepay 페이로드 파싱 + transferType === 'in' 검증 후)를 찾습니다. 추가:
// app/api/payment-success/route.ts (또는 동등한 파일)
import { sendTelegramNotification } from '@/lib/telegram';
import { sendCustomerEmail, sendOwnerEmail } from '@/lib/resend'; // 이미 있는 경우
export async function POST(req: Request) {
const payload = await req.json();
// [기존] Sepay 인증 + 페이로드 파싱 검증
if (payload.transferType !== 'in') {
return Response.json({ success: true }); // 나가는 송금 무시
}
// [기존] content(주문 ID 포함) 또는 referenceCode를 기준으로 리드 조회
const lead = await lookupLeadByTransferContent(payload.content);
// [신규] 3가지 부수 효과를 병렬로 전개 — Telegram 실패가 이메일을 차단하지 않음
await Promise.allSettled([
sendCustomerEmail(lead),
sendOwnerEmail(lead),
sendTelegramNotification({
name: lead.name,
phone: lead.phone,
email: lead.email,
amount: payload.transferAmount,
productName: lead.productName,
referenceCode: payload.referenceCode,
}),
]);
return Response.json({ success: true });
}
Promise.allSettled를 사용하는 이유 (Promise.all 대신):
Promise.all은 어떤 프로미스라도 실패하면 reject됩니다 → 핸들러가 throw됨 → Sepay가 500을 수신 → 재시도 → 중복 이메일.Promise.allSettled는 모든 프로미스가 처리될 때까지 기다립니다(resolve 또는 reject). throw되지 않습니다.
Phase 4 — 테스트 계획 (3단계)
레벨 1 — 봇 직접 테스트 (curl)
사용자가 다음을 실행하도록 요청:
curl -X POST "https://api.telegram.org/bot<TOKEN>/sendMessage" \
-H "Content-Type: application/json" \
-d '{"chat_id": "<CHAT_ID>", "text": "🧪 <b>테스트 알림</b>\n\n👤 <b>홍길동</b>\n📞 0901 234 567\n📧 [email protected]\n💰 <b>499.000đ</b> — 테스트 제품", "parse_mode": "HTML"}'
기대: 2초 이내에 Telegram 앱에 메시지가 표시됩니다. 실패할 경우:
- 400 Bad Request: chat_id가 잘못되었거나 봇이 채팅의 구성원이 아닙니다(그룹인 경우).
- 401 Unauthorized: 토큰이 잘못되었거나 취소되었습니다.
- 404 Not found: Bot API URL 형식이 잘못되었습니다(토큰 구문 확인).
레벨 2 — 시뮬레이션된 Sepay 페이로드
사용자가 프로젝트를 시작하고 다음을 통해 테스트 웹훅을 수동으로 보내도록 요청(로컬에서)
# 프로젝트 루트에서 (로컬 서버가 실행 중이라고 가정)
curl -X POST http://localhost:3000/api/payment-success \
-H "Content-Type: application/json" \
-d '{
"id": "test_webhook_001",
"gateway": "VCB",
"transferType": "in",
"transferAmount": 499000,
"content": "lead_test_001",
"referenceCode": "TEST123",
"accountNumber": "1234567890",
"transactionDate": "2026-05-14 07:32:00"
}'
기대:
- Resend 이메일(있는 경우)이 고객과 소유자에게 전송됩니다.
- Telegram 알림이 표시됩니다.
- curl 응답:
{ "success": true }(200). - 만약 Telegram이 실패하면(예: 잘못된 chat_id) → 응답은 여전히 200이어야 합니다. 500이 아니어야 합니다.
레벨 3 — 프로덕션에서 확인 (선택 사항이지만 권장)
1. 프로덕션에 배포합니다.
2. 실제 고객이 Sepay를 통해 결제하거나 VND 10,000을 직접 송금합니다.
3. 조건:
- Owner: 10초 이내에 Telegram 알림 수신 (이메일보다 빠름).
- 고객: Resend 이메일 수신 (영수증/감사).
- 결제가 Sepay에 200 OK로 기록됨 (관리자 페이지).
- 웹훅 로그에 재시도 없음 (동일한 결제 ID에 대해 하나의 POST만).
4. 문제 해결:
- Telegram은 수신했지만 이메일은 없음 → Resend 확인.
- 이메일은 수신했지만 Telegram은 없음 → 봇 토큰/chat_id 확인 + curl로 직접 테스트.
- 둘 다 수신했지만 1분 후에 수신 → `Promise.allSettled`가 여전히 올바른지 확인 (50x 오류를 던지고 있지 않은지).
환경 변수 (업데이트할 .env.local)
.env.local에 추가:
TELEGRAM_BOT_TOKEN=1234567890:AAxxxxxxxxxxxxxxxxxxxxxx
TELEGRAM_CHAT_ID=123456789
.gitignore에 이미 추가되어 있는지 확인 (가장 일반적인 패턴):
# .gitignore (이미 있는 경우 위 주석 해제 또는 추가)
.env.local
.env*.local
.env.local이 이미 .gitignore에 있다면 사용자에게 알릴 필요가 있지만 항상 다시 확인하세요: cat .gitignore | grep .env.local
(선택 사항, 권장) .env.example 업데이트:
# .env.example
TELEGRAM_BOT_TOKEN=
TELEGRAM_CHAT_ID=
이를 통해 새 개발자가 놓치지 않도록 합니다.
관련 파일
| 파일 | 목적 | 생성됨? |
|---|---|---|
lib/telegram.ts | sendTelegramNotification() + formatMessage() | 예 (Phase 3) |
app/api/payment-success/route.ts | 웹훅 핸들러 (수정됨) | 기존 + 수정됨 |
.env.local | TELEGRAM_BOT_TOKEN + TELEGRAM_CHAT_ID | 수정됨 |
.gitignore | .env.local 행 확인 | 확인됨 |
lib/leads-kv.ts — 이 스킬이 생성하지 않음. Phase 0.5에서 확인하고 없으면 사용자에게 /biz-setup-sepay-payment를 안내합니다.
자주 묻는 질문
Q: 동일한 주문에 대해 여러 Telegram 알림을 받았습니다. 왜 그런가요?
A: Sepay가 웹훅을 재시도했을 가능성이 높습니다(핸들러가 200을 반환하지 않았거나 Sepay가 네트워크 시간 초과를 감지했습니다). 확인: (1) 웹훅 핸들러가 항상 { success: true }를 반환하는지 확인합니다(모든 분기 종료). (2) id 또는 referenceCode를 기준으로 중복 제거를 추가합니다. (3) Promise.allSettled를 사용하고 있는지 확인합니다(Promise.all이 아님).
Q: Telegram 메시지가 깨져서 표시됩니다. 텍스트가 표시되지 않습니다.
A: Telegram sendMessage HTML 모드를 사용하는 경우: (1) 여는 태그와 닫는 태그가 일치하는지 확인합니다(<b>텍스트</b>). (2) <br>을 사용하지 마세요 — \n을 사용하세요. (3) 모든 특수 문자(<, >, &)가 이스케이프 처리되었는지 확인합니다.
Q: 에이전트/사용자 정의 로직을 위해 메시지 형식을 어떻게 변경합니까?
A: lib/telegram.ts에서 formatMessage() 수정. Parse mode를 MarkdownV2(더 많은 형식)로 전환하거나 HTML로 유지합니다. 참고: MarkdownV2는 훨씬 더 엄격한 이스케이프 규칙이 있습니다.
Q: 그룹 chat_id를 찾을 수 없습니다. getUpdates가 비어 있습니다.
A: 봇이 먼저 그룹에 추가되었는지 확인합니다. getUpdates는 새 업데이트가 있을 때만 표시합니다. 봇이 추가된 후 그룹에 /start@YourBotUsername 또는 아무 메시지나 보내 getUpdates에 트리거합니다.
Q: Vercel Edge Functions에서 작동합니까?
A: 예. fetch는 Edge runtime에서 지원됩니다. 단, Edge에서는 console.error가 다르게 동작할 수 있으며(수집되지 않음), 수집을 위해 logger를 통해 기록하는 것을 고려하세요.