Python 문자 발송 API 예제
requests 한 줄이면 센드엠 문자 발송 API를 호출할 수 있습니다. 단문·장문·포토문자와 결과 조회까지 예제로 정리했습니다.
Python에서 문자를 어떻게 보내나요?
https://api.sendm.co.kr/v1/sms/send 로 JSON을 POST 하고, 헤더에 user-id 와 api-key 를 넣으면 됩니다. 응답의 code 가 문자열 "0" 이면 접수 성공입니다.
표준 REST 호출이라 requests, httpx, 표준 라이브러리 urllib 중 무엇을 써도 됩니다. Django·FastAPI·Flask 어느 프레임워크에서든 동일합니다.
인증키를 소스에 하드코딩하지 마세요. 환경변수나 설정 관리 도구에서 읽어 쓰고, 저장소에 커밋되지 않도록 하세요.
단문(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=10,
)
res.raise_for_status()
return res.json()
호출과 결과 판정
result = send_sms(
caller_no="02-6959-8827",
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-6959-8827",
"smsType": "LMS",
"title": "배송 안내",
"message": "주문하신 상품이 오늘 출고되었습니다.",
"receiveNos": "010-0000-0001",
"reserveYn": "Y",
"sendDate": send_at, # YYYYMMDDHHMM (24시간제)
},
headers=HEADERS,
timeout=10,
)
포토문자(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()
이미지 규격은 웹 발송과 같습니다 — 1500×1440px 이하, 장당 300KB 미만, JPG·JPEG·PNG. 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-6959-8827"),
"senderKey": (None, "카카오 채널 발신 프로필 키"),
"tmplCd": (None, "승인된 템플릿 코드"),
"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=10).json()
print(info["data"]["smsCnt"], "건 발송 가능")
# 발송 상세 결과 (수신자별 resultCd)
detail = requests.get(BASE_URL + "/v1/sms/sendList/%s" % msg_id,
headers=HEADERS, timeout=10).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=10)
발송 직후 조회하면 대부분
waitCnt 입니다. 통신사 리포트를 받아야 결과가 확정되며 최대 48시간 걸릴 수 있습니다. 발송 함수 안에서 바로 조회하지 말고 배치·스케줄러로 분리하세요. resultCd 해석은 발송 실패 원인과 결과코드를 참고하세요.
이어서 보면 좋은 문서
최종 업데이트 : 2026년 9월 16일 · 「센드엠 API 연동정의서 v3.0」 기준