Telegram Bot API
🌐 한국어

텔레그램 봇 API로 메시지 보내기: sendMessage 사용법과 예제

· 9 분 분량

텔레그램 봇 API로 메시지를 보내려면 https://api.telegram.org/bot<token>/sendMessage에 chat_id(받는 채팅)와 text(1~4096자) 두 값을 담아 GET 또는 POST 요청을 보내면 됩니다. 성공하면 "ok": true와 함께 보낸 메시지 객체가 돌아옵니다. 이 글에서는 curl, Python, JavaScript 예제부터 HTML·MarkdownV2 서식, 인라인 키보드, 그룹과 채널 전송, 실제로 자주 만나는 오류까지 순서대로 다룹니다.

텔레그램 봇 API 요청 형식

모든 Bot API 호출은 https://api.telegram.org/bot<token>/메서드이름 형식의 HTTPS 요청입니다. 토큰 바로 앞에 bot이 붙고 그 사이에 슬래시가 없다는 점에 주의하십시오. GET과 POST를 모두 지원하며, 파라미터는 쿼리 문자열, application/x-www-form-urlencoded, application/json(파일 업로드 제외), multipart/form-data 중 편한 방식으로 넘기면 됩니다. 메서드 이름은 대소문자를 구분하지 않습니다.

파라미터설명
chat_id필수. 숫자 chat id 또는 @channelusername
text필수. 엔티티 파싱 후 1~4096자
parse_modeHTML, MarkdownV2, 구버전 Markdown
reply_markup인라인 키보드, 답장 키보드, 키보드 제거, 강제 답장
link_preview_options링크 미리보기 끄기 또는 조정
disable_notification알림음 없이 전송
protect_content전달과 저장 금지
reply_parameters특정 메시지에 답장
message_thread_id포럼 토픽으로 전송

응답은 항상 JSON입니다. 실패하면 "ok": false와 함께 error_code, description이 오고, 경우에 따라 parameters에 retry_after나 migrate_to_chat_id가 담깁니다. 토큰과 chat id가 아직 없다면 텔레그램 봇 토큰 가이드와 텔레그램 chat id 확인방법을 먼저 확인하십시오. 봇은 먼저 대화를 시작할 수 없으므로 받는 사람이 봇을 한 번은 시작했어야 합니다.

curl로 텔레그램 봇 API 메시지 보내기

터미널, cron 작업, CI 파이프라인에서 가장 빠른 방법은 curl입니다. 한글 텍스트는 URL 인코딩이 필요하므로 GET으로 보낼 때는 -G와 --data-urlencode를 함께 쓰면 안전합니다.

curl -G "https://api.telegram.org/bot123456789:AAEexampleTokenReplaceWithYours/sendMessage" \
  --data-urlencode "chat_id=123456789" \
  --data-urlencode "text=curl로 보낸 메시지입니다"

서식이나 버튼을 넣을 때는 JSON 본문으로 POST하는 편이 읽기 쉽습니다.

curl -X POST "https://api.telegram.org/bot123456789:AAEexampleTokenReplaceWithYours/sendMessage" \
  -H "Content-Type: application/json" \
  -d '{"chat_id": 123456789, "text": "curl로 보낸 메시지입니다"}'

성공 응답은 다음과 같습니다(일부 생략). 나중에 메시지를 수정하거나 삭제하려면 message_id를 저장해 두십시오.

{
  "ok": true,
  "result": {
    "message_id": 42,
    "chat": {"id": 123456789, "first_name": "지훈", "type": "private"},
    "date": 1793266200,
    "text": "curl로 보낸 메시지입니다"
  }
}

Python으로 메시지 보내기

requests 라이브러리를 쓰는 재사용 가능한 함수입니다. 실패하면 텔레그램이 돌려준 오류 코드와 설명을 그대로 예외로 올립니다.

import os
import requests

TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN", "123456789:AAEexampleTokenReplaceWithYours")
API = f"https://api.telegram.org/bot{TOKEN}"

def send_message(chat_id, text, **params):
    payload = {"chat_id": chat_id, "text": text, **params}
    data = requests.post(f"{API}/sendMessage", json=payload, timeout=10).json()
    if not data["ok"]:
        raise RuntimeError(f'{data["error_code"]}: {data["description"]}')
    return data["result"]

msg = send_message(123456789, "Python에서 보낸 메시지입니다")
print("전송 완료:", msg["message_id"])

JavaScript(fetch)로 메시지 보내기

Node.js 18 이상(ES 모듈)이나 fetch를 지원하는 런타임에서 그대로 실행됩니다.

const TOKEN = process.env.TELEGRAM_BOT_TOKEN ?? "123456789:AAEexampleTokenReplaceWithYours";

async function sendMessage(chatId, text, extra = {}) {
  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, ...extra }),
  });
  const data = await res.json();
  if (!data.ok) throw new Error(`${data.error_code}: ${data.description}`);
  return data.result;
}

const msg = await sendMessage(123456789, "JavaScript에서 보낸 메시지입니다");
console.log("전송 완료:", msg.message_id);

이 코드는 반드시 서버에서 실행하십시오. 브라우저의 프런트엔드 코드에 토큰을 넣으면 페이지를 연 누구나 토큰을 볼 수 있고, 토큰을 가진 사람은 봇을 완전히 제어할 수 있습니다. 웹 페이지에서 메시지를 보내야 한다면 토큰이 필요한 호출은 서버나 서버리스 함수가 처리하고, 프런트엔드는 그 서버만 호출하도록 구성하십시오.

parse_mode HTML로 서식 넣기

"parse_mode": "HTML"을 지정하면 다음 태그만 사용할 수 있습니다: <b>, <strong>, <i>, <em>, <u>, <ins>, <s>, <strike>, <del>, <span class="tg-spoiler">, <tg-spoiler>, <a href="…">, <code>, <pre>, <pre><code class="language-python">, <blockquote>, <blockquote expandable>. 그 밖의 태그는 오류가 됩니다.

태그가 아닌 <, >, &는 각각 &lt;, &gt;, &amp;로 바꿔야 합니다. 사용자 입력이나 외부 데이터를 메시지에 넣을 때는 반드시 이스케이프하십시오.

import html

name = html.escape("김&박 상사 <본사>", quote=False)  # -> 김&amp;박 상사 &lt;본사&gt;
send_message(
    123456789,
    f"<b>주문 #1024</b> ({name}) 배송이 <i>시작</i>되었습니다.\n"
    '<a href="https://example.com/track/1024">배송 조회</a>',
    parse_mode="HTML",
)

parse_mode MarkdownV2 서식과 이스케이프

MarkdownV2는 *굵게*, _기울임_, __밑줄__, ~취소선~, ||스포일러||, [텍스트](https://example.com), `코드`, 백틱 세 개로 감싼 코드 블록, 줄 앞의 > 인용문을 지원합니다. 문제는 이스케이프입니다. 엔티티 밖에서는 다음 문자를 모두 앞에 \를 붙여 써야 합니다.

_ * [ ] ( ) ~ ` > # + - = | { } . !

백슬래시 자체도 이스케이프 대상입니다. pre와 code 안에서는 `와 \만, 링크의 (…) 부분 안에서는 )와 \만 이스케이프합니다. 한글 자체는 이스케이프할 필요가 없지만, 한국어 문장에 흔한 마침표, 느낌표, 괄호, 하이픈이 모두 예약 문자라는 점이 함정입니다. "완료!"나 "v1.2" 하나만 빠뜨려도 Bad Request: can't parse entities: Character '!' is reserved and must be escaped with the preceding '\' 같은 400 오류가 납니다. 동적인 텍스트는 함수로 이스케이프하십시오.

import re

def escape_md2(text):
    return re.sub(r"([_*\[\]()~`>#+\-=|{}.!\\])", r"\\\1", text)

send_message(123456789, f"*배포 {escape_md2('v1.2-beta')}* 완료\\!",
             parse_mode="MarkdownV2")
const escapeMd2 = (s) => s.replace(/[_*[\]()~`>#+\-=|{}.!\\]/g, "\\$&");

서식이 깨질 때 원인을 찾는 가장 빠른 방법은 parse_mode를 빼고 같은 텍스트를 보내 보는 것입니다. 그래도 실패한다면 서식이 아니라 다른 파라미터의 문제입니다.

인라인 키보드 버튼 추가하기 (reply_markup)

인라인 키보드는 메시지 아래에 붙는 버튼 줄의 목록입니다. url 버튼은 링크를 열고, callback_data 버튼은 지정한 문자열을 callback_query 업데이트로 봇에게 돌려보냅니다.

curl -X POST "https://api.telegram.org/bot123456789:AAEexampleTokenReplaceWithYours/sendMessage" \
  -H "Content-Type: application/json" \
  -d '{
    "chat_id": 123456789,
    "text": "다음 작업을 선택하세요",
    "reply_markup": {"inline_keyboard": [[
      {"text": "사이트 열기", "url": "https://example.com"},
      {"text": "승인", "callback_data": "approve:1024"}
    ]]}
  }'

callback_data는 1~64바이트입니다. 한글은 UTF-8에서 한 글자가 3바이트이므로 한글로는 21자까지밖에 넣을 수 없습니다. approve:1024처럼 짧은 영문 키를 쓰고, 화면에 보일 문구는 text에 넣는 것이 좋습니다. JSON 본문에서는 reply_markup을 객체로 넣고, 쿼리 문자열이나 폼으로 보낼 때는 JSON 문자열로 직렬화해서 넘깁니다.

사용자가 콜백 버튼을 누르면 텔레그램 앱은 응답이 올 때까지 로딩 표시를 보여 줍니다. 알림 문구가 필요 없더라도 항상 answerCallbackQuery를 호출하십시오.

cq = update["callback_query"]
requests.post(f"{API}/answerCallbackQuery",
              json={"callback_query_id": cq["id"], "text": "승인되었습니다"}, timeout=10)

그룹, 채널, 토픽으로 메시지 보내기

프라이버시 모드는 봇이 그룹 메시지를 받는 범위에만 영향을 주며, 그룹으로 메시지를 보내는 것과는 관계가 없습니다. 같은 그룹에 짧은 시간 동안 알림을 많이 보내면 분당 20건 한도에 금방 도달하므로, 여러 건의 알림은 하나의 메시지로 묶어 보내는 편이 좋습니다.

일반 그룹이 슈퍼그룹으로 업그레이드되면 chat id가 바뀝니다. 이때 오류 응답의 parameters.migrate_to_chat_id에 새 ID가 들어 있으므로 저장된 값을 교체하고 다시 보내면 됩니다.

무음 전송, 링크 미리보기, 답장 옵션

{
  "chat_id": 123456789,
  "text": "릴리스 노트: https://example.com/changelog",
  "disable_notification": true,
  "protect_content": true,
  "link_preview_options": {"is_disabled": true},
  "reply_parameters": {"message_id": 42}
}

disable_notification은 알림음 없이 메시지를 전달하므로 야간 알림에 유용하고, protect_content는 메시지의 전달과 저장을 막습니다. link_preview_options는 예전 disable_web_page_preview를 대체한 객체로, is_disabled로 미리보기를 끄거나 url, prefer_small_media, prefer_large_media, show_above_text로 모양을 조정합니다. reply_parameters의 message_id를 지정하면 해당 메시지에 대한 답장으로 보내집니다.

보낸 메시지 수정하고 삭제하기

sendMessage가 돌려준 message_id를 저장해 두면 같은 메시지를 나중에 고치거나 지울 수 있습니다. 배포 진행률이나 주문 상태처럼 내용이 계속 바뀌는 알림은 새 메시지를 연달아 보내기보다 editMessageText로 한 메시지를 갱신하는 편이 채팅방을 깔끔하게 유지합니다.

curl -X POST "https://api.telegram.org/bot123456789:AAEexampleTokenReplaceWithYours/editMessageText" \
  -H "Content-Type: application/json" \
  -d '{"chat_id": 123456789, "message_id": 42, "text": "배포 진행률: 100% 완료"}'

curl -X POST "https://api.telegram.org/bot123456789:AAEexampleTokenReplaceWithYours/deleteMessage" \
  -H "Content-Type: application/json" \
  -d '{"chat_id": 123456789, "message_id": 42}'

새 텍스트와 버튼이 기존 메시지와 완전히 같으면 Bad Request: message is not modified 오류가 반환됩니다. 진행률처럼 같은 값이 반복될 수 있는 알림이라면 내용이 실제로 바뀌었을 때만 수정 요청을 보내도록 코드에서 먼저 비교하십시오.

자주 발생하는 오류와 해결 방법

오류원인해결
400 Bad Request: chat not foundchat id 오타, -100 누락, 사용자가 봇을 시작하지 않음, 봇이 그룹에 없음getUpdates로 ID를 다시 확인하고 봇을 시작하거나 채팅에 추가합니다.
400 Bad Request: can't parse entitiesMarkdownV2 이스케이프 누락, 잘못된 HTML 태그텍스트를 이스케이프하거나 parse_mode 없이 보내 원인을 찾습니다.
401 Unauthorized잘못되었거나 폐기된 토큰getMe로 토큰을 확인합니다.
403 Forbidden: bot was blocked by the user사용자가 봇을 차단함해당 사용자에게는 발송을 중단합니다. 재시도해도 성공하지 않습니다.
429 Too Many Requests: retry after N전송 한도 초과parameters.retry_after초 기다린 뒤 다시 보냅니다.

텔레그램 공식 FAQ 기준으로 한 채팅에는 초당 1건, 그룹에는 분당 20건, 전체 대량 발송은 초당 약 30건을 넘지 않는 것이 안전합니다. 더 많이 보내야 한다면 allow_paid_broadcast로 초당 최대 1000건까지 보낼 수 있으며, 무료 한도를 넘는 메시지마다 0.1 Telegram Stars가 봇 잔액에서 차감됩니다. 429에 대한 최소한의 재시도 코드는 다음과 같습니다.

import time

def send_with_retry(chat_id, text, **params):
    while True:
        payload = {"chat_id": chat_id, "text": text, **params}
        r = requests.post(f"{API}/sendMessage", json=payload, timeout=10).json()
        if r["ok"]:
            return r["result"]
        if r["error_code"] != 429:
            raise RuntimeError(r["description"])
        time.sleep(r.get("parameters", {}).get("retry_after", 1))

4096자를 넘는 긴 텍스트는 문단 단위로 나눠 여러 번 보내고, 각 메시지 사이에 짧은 간격을 두면 한도에 걸릴 가능성이 줄어듭니다. 웹훅으로 업데이트를 받는 봇이라면 응답 본문에 메서드를 담아 답장하는 방법도 있으니 텔레그램 웹훅 설정 가이드를 참고하십시오.

브라우저에서 sendMessage 테스트하기

  1. Bot API Tester의 sendMessage를 열고 토큰을 붙여 넣습니다. 토큰은 브라우저의 localStorage에만 저장됩니다.
  2. chat_id와 text를 입력하고, 필요하면 parse_mode와 reply_markup도 채웁니다.
  3. 실행 버튼을 누르거나 Ctrl+Enter를 눌러 JSON 응답을 확인합니다.

모든 파라미터의 정의는 sendMessage 레퍼런스(영어·러시아어)와 Swagger 문서에서 확인할 수 있습니다.

자주 묻는 질문

텔레그램 봇 API로 메시지를 보내는 가장 간단한 방법은 무엇인가요?

https://api.telegram.org/bot<token>/sendMessage에 chat_id와 text를 넘겨 GET 또는 POST로 호출하면 됩니다. 성공하면 "ok": true와 보낸 메시지 객체가 result에 담겨 돌아옵니다.

MarkdownV2에서 이스케이프해야 하는 문자는 무엇인가요?

엔티티 밖에서는 _ * [ ] ( ) ~ ` > # + - = | { } . !와 백슬래시 자체를 앞에 \를 붙여 이스케이프해야 합니다. pre와 code 안에서는 `와 \만 이스케이프합니다.

봇이 사용자에게 먼저 메시지를 보낼 수 있나요?

아니요. 사용자가 먼저 봇을 시작하거나 봇을 그룹에 추가해야 합니다. 그 전에 보내면 보통 400 Bad Request: chat not found 오류가 납니다.

텔레그램 채널에 봇으로 메시지를 보내려면 어떻게 하나요?

봇을 메시지 게시 권한이 있는 채널 관리자로 추가한 뒤, 공개 채널이면 "chat_id": "@channelusername", 비공개 채널이면 -1001234567890 같은 숫자 ID를 사용합니다.

429 Too Many Requests 오류는 왜 발생하나요?

전송 한도를 넘었기 때문입니다. 한 채팅에는 초당 1건, 그룹에는 분당 20건, 전체적으로는 초당 약 30건을 넘지 않도록 하고, 오류가 나면 parameters.retry_after초만큼 기다린 뒤 다시 보내십시오.

메시지 길이 제한은 얼마인가요?

text는 엔티티 파싱 후 1~4096자입니다. 더 긴 내용은 여러 메시지로 나눠 보내야 하며, 사진 등의 캡션은 0~1024자까지 가능합니다.

텔레그램 봇 만들기: @BotFather로 생성하고 첫 메시지 보내기

텔레그램 봇 만들기 가이드입니다. @BotFather의 /newbot으로 봇을 생성하고 토큰을 getMe로 확인한 뒤, getUpdates와 sendMessage로 첫 메시지를 보내는 과정을 curl과 Python 예제로 설명합니다.

텔레그램 봇 토큰: 발급부터 확인, 폐기, 안전한 보관까지

텔레그램 봇 토큰을 @BotFather에서 발급받는 법, 기존 봇의 토큰 찾기, getMe로 유효성 확인, 유출 시 폐기 방법을 정리했습니다. n8n·AI 비서 연동에 필요한 bot token과 chat id도 안내합니다.

텔레그램 웹훅 설정 가이드: setWebhook, secret_token, getWebhookInfo

텔레그램 봇 웹훅 설정 방법을 단계별로 정리했습니다. setWebhook 파라미터, secret_token 헤더 검증 코드, getWebhookInfo 상태 확인, deleteWebhook으로 되돌리기, 409·HTTPS 오류 해결까지 다룹니다.

텔레그램 chat id 확인방법: 개인, 그룹, 채널, 토픽별 chat id 찾기

텔레그램 chat id 확인방법을 개인 채팅, 그룹, 슈퍼그룹, 채널, 포럼 토픽별로 정리했습니다. getUpdates로 chat id를 찾는 법, -100으로 시작하는 ID의 의미, 결과가 비어 있을 때 해결법까지 설명합니다.