Python 문자 발송 API 예제
requests 로 센드엠 문자 발송 API를 호출하는 예제입니다. 단문·장문·포토문자 발송과 결과 조회를 다룹니다.
별도 SDK 없이 표준 HTTP 호출만으로 동작합니다.
적용 버전 「센드엠 API 연동정의서 v3.1」 · 작성 센드엠 고객지원팀 · 작성일 2026년 9월 16일 · 최종 수정 2026년 10월 21일
Python에서 문자를 어떻게 보내나요?
https://api.sendm.co.kr/v1/sms/send 로 JSON을 POST 하고, 헤더에 user-id 와 api-key 를 넣으면 됩니다. 응답의 code 가 문자열 "0" 이면 접수 성공입니다.
표준 REST 호출이라 requests, httpx, 표준 라이브러리 urllib 중 무엇을 써도 됩니다. Django·FastAPI·Flask 어느 프레임워크에서든 동일합니다.
실행 환경
| 구분 | 사용 | 검증 |
|---|---|---|
| 런타임 | Python 3 | 2026-09-16 실행 확인 |
| HTTP | requests | 2026-09-16 실행 확인 |
| 설치 | pip install requests | - |
requests 는 표준 라이브러리가 아니므로 먼저 설치해야 합니다. httpx·urllib도 같은 헤더와 JSON 본문으로 호출하면 됩니다.문자 요청 예제 단문(SMS) 발송
예시 값을 실제 발신번호·수신번호·인증키로 바꿔 고객님 서버에서 실행하세요.
sendm.py
import os
import requests
BASE_URL = "https://api.sendm.co.kr"
HEADERS = {
"user-id": os.environ["SENDM_USER_ID"], # 센드엠 아이디
"api-key": os.environ["SENDM_API_KEY"], # API 신청 후 발급받은 키
}
def send_sms(caller_no, receive_nos, message, sms_type="SMS", title=None, ad_yn="N"):
"""문자 발송. receive_nos 는 여러 건이면 '#' 로 구분한다."""
payload = {
"callerNo": caller_no, # 등록·승인된 발신번호
"receiveNos": receive_nos, # "010-0000-0001#010-0000-0002"
"message": message,
"smsType": sms_type, # "SMS" 단문 / "LMS" 장문
"adYn": ad_yn, # "N" 일반 / "Y" 광고 / "E" 선거
}
if title:
payload["title"] = title # 장문에서만 사용
res = requests.post(
BASE_URL + "/v1/sms/send",
json=payload, # Content-Type: application/json 자동 설정
headers=HEADERS,
timeout=30,
)
res.raise_for_status()
return res.json()
호출과 결과 판정
result = send_sms(
caller_no="02-0000-0000",
receive_nos="010-0000-0001#010-0000-0002",
message="주문이 접수되었습니다.",
)
# code 는 숫자가 아니라 문자열 "0" 이다
if result["code"] == "0":
msg_id = result["data"]["msgId"]
print("발송 접수 완료. msgId =", msg_id)
else:
print("발송 실패 [%s] %s" % (result["code"], result["message"]))
장문(LMS)과 예약 발송
from datetime import datetime, timedelta
send_at = (datetime.now() + timedelta(hours=2)).strftime("%Y%m%d%H%M")
requests.post(
BASE_URL + "/v1/sms/send",
json={
"callerNo": "02-0000-0000",
"smsType": "LMS",
"title": "배송 안내",
"message": "주문하신 상품이 오늘 출고되었습니다.",
"receiveNos": "010-0000-0001",
"reserveYn": "Y",
"sendDate": send_at, # YYYYMMDDHHMM (24시간제)
},
headers=HEADERS,
timeout=30,
)
포토문자(MMS) 발송
이미지를 첨부하므로 JSON이 아니라 multipart/form-data 로 보냅니다. json= 대신 data= 와 files= 를 씁니다.
def send_mms(caller_no, receive_nos, message, image_paths, title=None):
"""포토문자 발송. 이미지는 최대 3장."""
data = {
"callerNo": caller_no,
"receiveNos": receive_nos,
"message": message, # MMS 는 본문이 없어도 된다
"smsType": "MMS",
}
if title:
data["title"] = title
files = {}
handles = []
try:
for i, path in enumerate(image_paths[:3], start=1):
fh = open(path, "rb")
handles.append(fh)
files["attachFile%02d" % i] = fh # attachFile01 ~ attachFile03
res = requests.post(
BASE_URL + "/v1/mms/send",
data=data,
files=files,
headers=HEADERS, # Content-Type 은 requests 가 자동 생성한다
timeout=30,
)
res.raise_for_status()
return res.json()
finally:
for fh in handles:
fh.close()
이미지는 JPG·JPEG·PNG만 받습니다. 1,500×1,440px보다 크면 서버가 줄여 JPG로 변환하고, 변환 후 합계 1MB를 넘으면 거부합니다(801·802). 장당 300KB 이하를 권장합니다. headers 에 Content-Type 을 직접 넣으면 multipart 경계값이 깨지므로 넣지 마세요.
알림톡 발송
수신자 목록을 kakaoList 에 JSON 문자열로 담아 multipart로 보냅니다.
import json
kakao_list = [
{
"receive": "010-0000-0001",
"message": "홍길동님, 주문번호 A1234 상품이 출고되었습니다.",
"reYn": "Y", # 알림톡 실패 시 문자로 대체
"reType": "LMS",
"reTitle": "배송 안내",
"reMsg": "홍길동님, 주문번호 A1234 상품이 출고되었습니다.",
},
]
# 알림톡은 multipart/form-data 로 보내야 한다.
# 파일이 없어도 (None, 값) 튜플로 files 에 넘기면 각 필드가 multipart part 로 전송된다.
# 주의: data={...}, files={} 로 보내면 requests 가 multipart 를 만들지 않고
# application/x-www-form-urlencoded 로 보내 규격에 맞지 않는다.
res = requests.post(
BASE_URL + "/v2/kakao/sendAt",
files={
"callerNo": (None, "02-0000-0000"),
"senderKey": (None, "YOUR_SENDER_KEY"),
"tmplCd": (None, "YOUR_TEMPLATE_CODE"),
"kakaoList": (None, json.dumps(kakao_list, ensure_ascii=False)),
},
headers=HEADERS, # Content-Type 은 requests 가 자동 생성
timeout=30,
)
print(res.json())
message 는 승인된 템플릿과 내용이 일치해야 합니다. 템플릿 등록·승인 절차는 카카오 알림톡 시작하기를 참고하세요.
발송 결과와 잔여 건수 조회
# 잔여 코인·발송 가능 건수
info = requests.get(BASE_URL + "/v2/info/coinInfo",
headers=HEADERS, timeout=30).json()
print(info["data"]["smsCnt"], "건 발송 가능")
# 발송 상세 결과 (수신자별 resultCd)
detail = requests.get(BASE_URL + "/v1/sms/sendList/%s" % msg_id,
headers=HEADERS, timeout=30).json()
print("성공 %s / 실패 %s / 대기 %s" % (
detail["data"]["successCnt"],
detail["data"]["failCnt"],
detail["data"]["waitCnt"]))
for row in detail["list"]:
print(row["receiveNo"], row["resultCd"], row["resultCdNm"])
# 예약 발송 취소
requests.get(BASE_URL + "/v1/sms/cancel/%s" % msg_id,
headers=HEADERS, timeout=30)
waitCnt 입니다. 통신사 리포트를 받아야 결과가 확정되며 최대 48시간 걸릴 수 있습니다. 발송 함수 안에서 바로 조회하지 말고 배치·스케줄러로 분리하세요. resultCd 해석은 발송 실패 원인과 결과코드를 참고하세요.
제한과 주의
GET /v1/sms/sendList)으로 접수 여부를 먼저 확인하고, 접수되지 않은 건만 다시 보냅니다.
- 응답
code가"0"이면 접수입니다. 수신 성공은 결과 조회로 따로 확인합니다. - 예약 발송의
sendDate는 호출 시점 기준으로 계산하세요. 예시 날짜를 그대로 복사하면 지난 시각이 됩니다. - 인증키는 서버 환경변수에서 읽고 저장소에 커밋하지 마세요.
- 광고문자는
adYn=Y로 보내며 야간(21시~08시) 발송 제한이 적용됩니다.
자주 막히는 문제와 해결
| 증상 | 원인 | 해결 |
|---|---|---|
HTTPError 예외 | raise_for_status() 가 4xx·5xx 응답에서 예외를 냄 | 예외의 response.text 로 실패 사유 확인 |
| MMS 첨부가 안 됨 | headers 에 Content-Type 을 직접 넣음 | 인증 헤더만 넣고 Content-Type 은 requests 에 맡기기 |
| 알림톡 형식 오류 | data= 로 보내 urlencoded 로 전송됨 | files= 에 (None, 값) 으로 넣어 multipart 로 보내기 |
| 권한 없음으로 차단 | 호출 서버 IP가 미등록 | API 설정에서 발송 IP 등록. 서버 증설·이전 시 다시 등록 |
| 발신번호 오류 | callerNo 가 미등록·미승인 번호 | 발신번호 등록 후 사용 |
| 야간에만 실패 | 야간 발송 제한(21시~08시) | reserveYn=Y 와 sendDate 로 예약 전환 |
이 글에서 답한 질문
Python에서는 어떤 라이브러리로 호출하나요?
표준 REST 호출이라 requests, httpx, 표준 라이브러리 urllib 중 무엇을 써도 됩니다. 이 글의 예제는 requests를 쓰며, 표준 라이브러리가 아니므로 pip install requests로 먼저 설치합니다.
인증키는 어디에 두어야 하나요?
소스에 하드코딩하지 말고 환경변수나 설정 관리 도구에서 읽어 쓰세요. 저장소에 커밋되지 않도록 해야 합니다.
접수에 성공했는지는 어떻게 아나요?
응답의 code가 문자열 "0"이면 접수 성공입니다. Django·FastAPI·Flask 어느 프레임워크에서든 같습니다.
다음 단계
최종 업데이트 : 2026년 10월 21일 · 「센드엠 API 연동정의서 v3.1」 기준