본문 바로가기

Python 문자 발송 API 예제

requests 한 줄이면 센드엠 문자 발송 API를 호출할 수 있습니다. 단문·장문·포토문자와 결과 조회까지 예제로 정리했습니다.

Python에서 문자를 어떻게 보내나요?

https://api.sendm.co.kr/v1/sms/send 로 JSON을 POST 하고, 헤더에 user-idapi-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. headersContent-Type 을 직접 넣으면 multipart 경계값이 깨지므로 넣지 마세요.

알림톡 발송

수신자 목록을 kakaoListJSON 문자열로 담아 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」 기준

API 신청부터 시작하세요

승인 후 발급되는 인증키를 환경변수에 넣으면 바로 실행됩니다.

loading...