이미지 문자(MMS) 보내기: 미디어 업로드부터 발송까지
KAT Cloud에서 이미지 문자(MMS)를 보내는 방법입니다. 채팅 API는 /chat/media/upload, 문자 발송 API는 /message/media/upload로 이미지를 올리고 받은 key를 imageList에 넣습니다. 규격과 실패 처리도 다룹니다.

KAT Cloud에서 이미지 문자(MMS)를 보내는 과정은 두 단계입니다. 먼저 미디어 업로드 API에 이미지 파일을 multipart/form-data로 올려 key를 받고, 그 key를 발송 요청의 imageList에 넣으면 메시지가 MMS로 발송됩니다. 채팅 API로 보낼 때는 POST /chat/media/upload와 POST /chat을, 문자 발송 API로 보낼 때는 POST /message/media/upload와 POST /message/send를 짝지어 씁니다.
업로드 경로 두 가지: 채팅 API와 문자 발송 API
KAT Cloud(캣클라우드) API 문서에는 미디어 업로드 엔드포인트가 두 개 있습니다. 하나는 채팅에 첨부할 파일을 올리는 /chat/media/upload이고, 다른 하나는 /message/send로 MMS를 보내기 전에 쓰는 /message/media/upload입니다. 두 경로는 사용 범위와 권장 여부가 다르므로 먼저 어느 쪽으로 보낼지 정합니다.
| 항목 | 채팅 API 경로 | 문자 발송 API 경로 |
|---|---|---|
| 업로드 | POST /chat/media/upload | POST /message/media/upload |
| 발송 | POST /chat(chatRoomId, content, imageList) | POST /message/send(toPhoneNumber, content, imageList) |
| 업로드 필드 | files(multipart/form-data, 최대 3개) | files(multipart/form-data, 최대 3개) |
| 업로드 결과 | result.urls | result.urls(업로드에 성공한 파일의 key만) |
| 사용 범위 | 비즈니스 요금제 그룹 또는 허용 그룹 | 모든 사용자 |
| 문서상 상태 | 새 연동에 권장 | Deprecated(채팅 API 권장) |
문서는 /message/media/upload가 돌려준 key를 /message/send의 imageList에 넣으라고 안내하고, /chat/media/upload는 채팅에 첨부할 파일을 올리는 API로 설명합니다. 그러니 업로드와 발송은 같은 경로끼리 짝을 맞춰 씁니다. 업로드 결과는 chat-media/123456/1713776400000-abcd123.jpg 같은 key 문자열 배열입니다.

이미지 준비: 권장 규격 맞추기
MMS는 일반 문자보다 담을 수 있는 용량이 작아서 사진을 그대로 올리면 실패할 수 있습니다. 문서의 MMS 이미지 업로드 항목에 안내된 조건은 다음과 같습니다.
- 한 번에 최대 3개까지 업로드할 수 있습니다.
- 업로드한 이미지는 JPG로 자동 변환되고, 가능하면 MMS 규격에 맞게 축소됩니다.
- 권장 조건은 최대 1280px, 300KB 이하입니다.
- 자동 축소 후에도 300KB 이하로 줄지 않는 이미지가 있을 수 있습니다.
- 일부 이미지는 자동 축소 후에도 MMS 규격을 만족하지 못해 전송이 실패할 수 있습니다.
자동 변환에 기대기보다 업로드 전에 직접 줄여 두면 실패가 줄어듭니다. Python의 Pillow로 긴 변을 1280px 이하로 줄이고, JPEG 품질을 낮춰 가며 300KB 이하로 맞추는 예시입니다.
# resize_for_mms.py — pip install pillow
from io import BytesIO
from PIL import Image
MAX_SIDE = 1280
MAX_BYTES = 300 * 1000 # 여유 있게 300,000바이트
def to_mms_jpeg(src: str, dst: str) -> int:
img = Image.open(src).convert("RGB") # PNG 투명도 등을 정리하고 JPG로 저장
img.thumbnail((MAX_SIDE, MAX_SIDE))
for quality in (85, 75, 65, 55, 45):
buf = BytesIO()
img.save(buf, format="JPEG", quality=quality, optimize=True)
if buf.tell() <= MAX_BYTES:
with open(dst, "wb") as f:
f.write(buf.getvalue())
return buf.tell()
raise ValueError(f"{src}: 품질을 낮춰도 300KB를 넘습니다. 크기를 더 줄이세요.")
print(to_mms_jpeg("coupon.png", "coupon.jpg"))글자가 많은 이미지는 품질을 너무 낮추면 읽기 어려워집니다. 쿠폰이나 약도처럼 글자가 중요한 이미지는 처음부터 가로 800~1000px 정도로 디자인해 두면 용량과 가독성을 함께 맞추기 쉽습니다.
방법 1: 채팅 API로 MMS 보내기
채팅 API를 쓸 수 있는 그룹이라면 이 경로를 권장합니다. 채팅방 ID는 POST /chat-room으로 미리 받아 둡니다. 채팅방을 만드는 과정은 채팅 API로 문자 보내기 글에 있습니다.
export KAT_API_KEY="콘솔에서 발급받은 키"
BASE="https://studio-gateway.zerocall.kr"
# 1) 이미지 업로드 (-F를 쓰면 curl이 multipart/form-data와 boundary를 자동으로 붙인다)
curl -s -X POST "$BASE/chat/media/upload" \
-H "x-api-key: $KAT_API_KEY" \
-F "files=@coupon.jpg;type=image/jpeg"
# {"code":"0","message":"Success","result":{"urls":["chat-media/123456/1713776400000-abcd123.jpg"]}}
# 2) 받은 key를 imageList에 넣어 발송
curl -s -X POST "$BASE/chat" \
-H "x-api-key: $KAT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"chatRoomId":"yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy","content":"방문 시 이 쿠폰을 보여 주세요.","subject":"11월 쿠폰","imageList":["chat-media/123456/1713776400000-abcd123.jpg"]}'Node.js 18 이상에서는 내장 FormData와 Blob으로 같은 요청을 만들 수 있습니다. 이때 Content-Type 헤더를 직접 넣지 않는 것이 중요합니다. 직접 넣으면 boundary 값이 빠져 서버가 파일을 읽지 못합니다.
// send-mms-chat.js — Node.js 18+
const { readFile } = require('node:fs/promises');
const path = require('node:path');
const BASE_URL = 'https://studio-gateway.zerocall.kr';
const API_KEY = process.env.KAT_API_KEY;
const MIME = { '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png' };
async function readResult(res) {
const json = await res.json().catch(() => ({}));
if (!res.ok || json.code !== '0') throw new Error(`${res.status} ${json.code} ${json.message}`);
return json.result;
}
async function uploadChatMedia(filePaths) {
if (filePaths.length > 3) throw new Error('한 번에 최대 3개까지 업로드할 수 있습니다');
const form = new FormData();
for (const p of filePaths) {
const type = MIME[path.extname(p).toLowerCase()] || 'application/octet-stream';
form.append('files', new Blob([await readFile(p)], { type }), path.basename(p));
}
const res = await fetch(`${BASE_URL}/chat/media/upload`, {
method: 'POST',
headers: { 'x-api-key': API_KEY }, // Content-Type은 fetch가 boundary와 함께 붙인다
body: form,
});
const { urls } = await readResult(res);
if (urls.length !== filePaths.length) {
throw new Error(`업로드 일부 실패: ${filePaths.length}개 중 ${urls.length}개 성공`);
}
return urls;
}
async function sendMms({ chatRoomId, content, subject, files }) {
const imageList = await uploadChatMedia(files);
const res = await fetch(`${BASE_URL}/chat`, {
method: 'POST',
headers: { 'x-api-key': API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ chatRoomId, content, subject, imageList }),
});
return readResult(res); // { chatId, chatType: 'MMS', sendStatus: 'PENDING', ... }
}
sendMms({
chatRoomId: 'yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy',
subject: '11월 쿠폰',
content: '방문 시 이 쿠폰을 보여 주세요.',
files: ['./coupon.jpg'],
})
.then((chat) => console.log(chat.chatId, chat.chatType, chat.sendStatus))
.catch((err) => console.error(err));POST /chat의 imageList 항목은 하나당 최대 300자까지 넣을 수 있습니다. 업로드 결과로 받은 key를 가공하지 않고 그대로 넣으면 됩니다.
방법 2: /message/send로 MMS 보내기
채팅 API를 쓸 수 없는 그룹이라면 문자 발신 API를 씁니다. POST /message/media/upload, POST /message/send, GET /message/send-result/{chatId} 세 개는 모든 사용자가 쓸 수 있습니다. 다만 /message/send는 문서에 Deprecated로 표시되어 있으니 새 연동이라면 채팅 API로 옮길 계획을 함께 세웁니다.
# 1) 이미지 업로드 (최대 3개)
curl -s -X POST "$BASE/message/media/upload" \
-H "accept: */*" \
-H "x-api-key: $KAT_API_KEY" \
-F "files=@sample1.png;type=image/png" \
-F "files=@sample2.jpg;type=image/jpg"
# 2) result.urls의 key를 imageList에 넣어 발송
curl -s -X POST "$BASE/message/send" \
-H "x-api-key: $KAT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"toPhoneNumber":"01098765432","content":"11월 방문 쿠폰입니다.","subject":"11월 쿠폰","imageList":["chat-media/123456/1713776400000-abcd123.jpg","chat-media/123456/1713776400001-efgh456.jpg"]}'
# {"code":"0","message":"Success","result":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}Python으로 쓰면 다음과 같습니다. fromPhoneNumber는 선택 필드이며, 넣을 때는 숫자만, 내 그룹 번호만 쓸 수 있습니다.
# send_mms_message.py — /message/media/upload → /message/send
import os
import requests
BASE_URL = "https://studio-gateway.zerocall.kr"
API_KEY = os.environ["KAT_API_KEY"]
def check(r: requests.Response):
body = r.json()
if r.status_code >= 400 or body.get("code") != "0":
raise RuntimeError(f"{r.status_code} {body.get('code')} {body.get('message')}")
return body["result"]
def upload_message_media(paths: list[str]) -> list[str]:
handles = [open(p, "rb") for p in paths]
try:
files = [("files", (os.path.basename(p), fh, "image/jpeg")) for p, fh in zip(paths, handles)]
r = requests.post(
f"{BASE_URL}/message/media/upload",
headers={"x-api-key": API_KEY},
files=files,
timeout=30,
)
finally:
for fh in handles:
fh.close()
urls = check(r)["urls"] # 업로드에 실패한 파일은 이 배열에 없다
if len(urls) != len(paths):
raise RuntimeError(f"{len(paths)}개 중 {len(urls)}개만 업로드됨")
return urls
def send_mms(to: str, content: str, paths: list[str], subject: str | None = None) -> str:
payload = {"toPhoneNumber": to, "content": content, "imageList": upload_message_media(paths)}
if subject:
payload["subject"] = subject # 제목, 최대 64자
r = requests.post(
f"{BASE_URL}/message/send",
headers={"x-api-key": API_KEY, "Content-Type": "application/json"},
json=payload,
timeout=10,
)
return check(r) # 32자 chatId
if __name__ == "__main__":
print(send_mms("01098765432", "11월 방문 쿠폰입니다.", ["coupon.jpg"], subject="11월 쿠폰"))/message/send의 imageList는 S3 key 또는 URL을 받으며, 값이 하나라도 있으면 MMS로 발송됩니다. 업로드 API와 발송 API의 전체 필드는 API 문서의 문자 발송 항목과 채팅 항목에 있습니다.
업로드 실패와 발송 결과 확인
MMS 연동에서 실패는 두 군데에서 납니다. 하나는 업로드 단계입니다. /message/media/upload는 업로드에 성공한 파일의 key만 result.urls에 담고 실패한 파일은 빼고 돌려줍니다. 위 예제처럼 요청한 파일 수와 받은 key 수를 비교해 두면, 이미지 한 장이 빠진 채 MMS가 나가는 일을 막을 수 있습니다. 채팅 API 업로드에도 같은 검사를 넣어 두면 안전합니다.
다른 하나는 발송 이후입니다. 업로드가 성공해도 이미지가 MMS 규격을 만족하지 못하면 전송이 실패할 수 있습니다. 발송 응답의 chatType이 MMS인지 확인하고, 최종 결과는 SEND_RESULT 웹훅이나 결과 조회 API의 resultStatus로 판단합니다. LMS와 MMS의 성공 코드는 6600입니다. 실패 코드는 resultMessage로 사유를 확인하고, 이미지 크기를 줄여 다시 보내는 식으로 처리합니다. 결과 코드를 다루는 방법은 문자 발송 결과 확인에서 자세히 설명합니다.
비용도 미리 따져 봅니다. KAT Cloud 요금표 기준 MMS 초과분 단가는 75원, 70원, 65원(누적 구간, 부가세 별도)으로 SMS·LMS보다 높습니다. 이미지가 꼭 필요하지 않은 안내라면 LMS로 보내는 편이 낫습니다. 메시지 종류별 쓰임새와 단가 비교는 SMS·LMS·MMS·알림톡 차이 글에 정리했습니다.
자주 묻는 질문
MMS 한 번 발송에 이미지를 몇 장까지 넣을 수 있나요?
KAT Cloud의 미디어 업로드 API는 한 번에 최대 3개 파일을 받습니다. 문서가 안내하는 기본 흐름은 한 번 업로드해 받은 key 목록을 그대로 imageList에 넣는 것이므로, MMS 한 건은 이미지 3장 이내로 구성하는 것이 안전합니다.
업로드는 성공했는데 MMS가 실패하는 이유는 무엇인가요?
KAT Cloud는 업로드한 이미지를 JPG로 바꾸고 MMS 규격에 맞게 줄이지만, 일부 이미지는 자동 축소 후에도 규격을 만족하지 못해 전송이 실패할 수 있습니다. 업로드 전에 이미지를 최대 1280px, 300KB 이하로 직접 맞추면 이런 실패를 크게 줄일 수 있습니다.
채팅 API용 업로드와 /message/media/upload 중 무엇을 써야 하나요?
KAT Cloud 문서는 /message/media/upload의 key를 /message/send의 imageList에 쓰도록 안내하고, /chat/media/upload는 채팅에 첨부할 파일용으로 설명합니다. 채팅 API(POST /chat)로 보낸다면 /chat/media/upload를, /message/send로 보낸다면 /message/media/upload를 짝지어 쓰는 것이 문서 기준에 맞습니다.
- #MMS
- #이미지 문자
- #미디어 업로드
- #문자 발송



