증상: 사진은 왔는데 담당자 메일에는 왜 아무것도 없나?
카카오 챗봇 이미지 전송을 링크 그대로 메일에 넘기면, 담당자가 메일을 여는 시점에 사진은 이미 사라져 있습니다. 저희가 맡은 요건은 단순했습니다. 고객이 카카오톡 채널에서 문의 유형 4종(영업·서비스·마케팅·일반) 중 하나를 고르고, 이름·회사명·연락처·이메일·문의내용 5항목을 넣고, 필요하면 장비 사진을 보냅니다. 챗봇은 이걸 유형별 담당 부서 메일로 보내고, 대화창에는 접수 완료 안내를 띄웁니다.
사진만 빼면 나머지는 평범한 폼 전달입니다. 문제는 사진이 카카오의 '이미지 보안전송' 플러그인으로 들어온다는 점이었습니다. 이 플러그인은 사진 파일 자체가 아니라 카카오 보안 서버에 올라간 사진의 URL을 스킬 서버로 넘깁니다. 처음 그린 그림은 그 URL을 담당 부서 메일 본문에 링크로 붙이는 방식이었습니다.
이 그림에는 실패가 두 겹으로 숨어 있었습니다. 하나는 시간입니다. 공식 문서 원문은 '이미지의 URL은 최대 10분간 유지됩니다'입니다. 담당자가 30분 뒤 메일을 열면 링크는 죽어 있고, 그 순간 챗봇은 사진을 하나도 전달하지 못한 셈입니다. 다른 하나는 형식입니다. URL 목록을 흔히 하듯 JSON으로 읽으면 사진이 몇 장이 왔든 결과는 0장이고, 예외도 나지 않습니다.
두 실패 모두 겉으로는 정상처럼 보입니다. 고객은 대화창에서 '접수되었습니다'를 이미 받았고, 담당자는 사진 없는 메일이나 열리지 않는 링크를 받습니다. 어느 쪽도 무엇이 빠졌는지 모릅니다. 실제 운영에서 사고가 난 것은 아니고, 문서 대조와 시험판 단계에서 잡았습니다. 다만 잡지 못했다면 오류 로그 한 줄 없이 지나갔을 종류의 결함이었습니다.

처음 의심한 것: 사진 기능이 일정을 늘린다
사진 문제를 살피기 전에 먼저 세운 가설은 일정 쪽이었습니다. 이미지 보안전송은 '비즈플러그인'이라 카카오톡 채널을 사업자 심사를 거친 '비즈니스 채널'로 바꿔야 쓸 수 있습니다. 공식 문서 기준 심사는 영업일 3~5일이고, 신청은 채널 마스터만 할 수 있습니다. 저희가 대신 할 수 없는 고객사 몫의 일입니다.
그래서 초판 기술검토는 견적을 두 갈래로 나눴습니다. A안은 사진 없이 유형 선택·정보 입력·부서 전달·접수 안내만 하는 구성이고, '카카오톡 채널만 있으면 됨'으로 적었습니다. B안은 사진을 포함하는 구성이고, 비즈니스 채널 전환이 선행 조건이었습니다. 사진을 포기하면 고객이 심사를 기다리지 않고 바로 시작할 수 있다는 가설이었습니다.
이 가설은 그럴듯했습니다. 비즈플러그인 문서의 목적별 표에 이미지 보안전송이 명시돼 있었고, 일반 챗봇 문서에서는 채널 유형 요건을 찾지 못했기 때문입니다. 저희가 먼저 쓴 상위 검토 문서도 '챗봇 연결의 채널 유형 요건은 공식 문서로 미확정'이라고 적어 두었습니다. 찾지 못한 요건을 '없는 요건'으로 읽은 셈이었습니다.
무엇으로 확인했나: 공식 문서 원문, 시험판, 독립 재확인
확인 수단은 세 가지였습니다. 첫째는 카카오 공식 도메인의 문서 원문입니다. 공식 문서에 없는 값은 추정으로 채우지 않고 '미확인'으로 남기는 원칙을 처음부터 정했습니다. 둘째는 시험판입니다. 요건대로 스킬 서버를 만들어 로컬 프로덕션 빌드에서 실제 메일 발송까지 돌렸고, 확인 항목 20개를 모두 통과했습니다.
시험판이 본 범위는 이렇습니다. 시크릿 없이 부르면 403으로 막는지, 유형 4종을 인식하는지, 정보 5항목이 메일에 실리는지, List(...) 문자열을 제대로 푸는지, 실제 이미지를 내려받아 첨부로 바꾸는지, 5초 응답 제한을 지키는지(최악 19ms), 실패해도 5xx를 돌려주지 않는지, 수신함에 실제로 도착하는지(5/5)였습니다. 단위 테스트는 32건이고, 입력 예시는 공식 문서의 스킬 호출값 예시를 글자 그대로 옮겼습니다.
셋째는 독립 재확인입니다. 초판을 쓴 쪽이 자기 결론을 다시 보면 같은 데서 같은 걸 놓칩니다. 그래서 같은 날 저녁, 처음 검토와 분리된 작업으로 카카오 공식 문서를 다시 열어 초판의 주장 9개를 하나씩 판정했습니다. 결과는 7개 확인, 2개 부분 확인, 0개 반박이었습니다. 반박은 없었지만 초판이 아예 보지 않은 제약이 여럿 나왔습니다.
| 수단 | 무엇을 물었나 | 찾은 것 |
|---|---|---|
| 공식 문서 원문 대조 | 카카오가 무엇을 약속하나 | URL 최대 10분 유지, 심사 영업일 3~5일, 최대 10장 |
| 시험판(20개 항목) | 요건대로 돌리면 실제로 도착하나 | 20/20 통과, 수신함 실도착 5/5, 최악 19ms |
| 단위 테스트 32건 | 공식 예시 모양을 제대로 푸나 | List(...) 문자열 파싱 고정 |
| 독립 재확인(주장 9개) | 초판의 결론이 문서와 맞나 | 7개 확인 · 2개 부분 · 0개 반박, 놓친 제약 다수 |

알리바이: 사진을 빼면 정말 비즈니스 채널 없이 되나?
가설은 2026년 9월 8일에 뒤집혔습니다. 카카오디벨로퍼스의 '카카오톡 채널' 문서는 동일한 사업자 정보를 앱과 채널에 등록해 '비즈 앱과 비즈니스 채널로 각각 전환해야 합니다'라고 적고, 연결 가능한 채널의 조건으로 '비즈니스 채널로 전환된 채널'을 듭니다. 챗봇 배포 문서는 운영 채널과 개발 채널이 모두 동일한 비즈앱에 연결돼야 한다고 요구합니다.
즉 비즈니스 채널은 사진 기능의 요건이 아니라 챗봇을 채널에 붙이는 일 자체의 요건이었습니다. 사진을 빼도 비즈니스 채널 없이는 봇이 돌지 않습니다. 사진 기능은 그 위에 플러그인 하나를 더 얹을 뿐 고객 준비물을 늘리지 않습니다. 견적이 사진 유무로 갈린다는 초판의 두 갈래 구분은 근거를 잃었고, 저희는 그 구분을 폐기했습니다.
일정도 다시 계산해야 했습니다. 비즈니스 채널 전환 심사(영업일 3~5일) 뒤에, 채널을 앱에 연결하는 심사가 영업일 3~5일 한 번 더 붙습니다. 반려와 재심사도 공식 문서에 정상 경로로 적혀 있고, 재심사 역시 마스터만 신청할 수 있습니다. 그래서 고객 회신에는 '한 번에 통과한다는 전제로 일정을 잡지 마시라'는 문장을 넣었습니다.
이 반증에서 배운 점은 하나입니다. 한 문서에서 요건을 못 찾았다는 사실은 '요건이 없다'가 아니라 '아직 못 찾았다'입니다. 카카오처럼 기능 문서, 채널 문서, 앱 문서가 나뉜 플랫폼에서는 요건이 기능 층이 아니라 계정 층에 적혀 있을 수 있습니다.

진짜 원인: 링크와 배열이라는 두 가지 착각
사진이 사라지는 진짜 원인은 두 가지 착각이었습니다. 첫째는 'URL을 받았으니 사진을 받았다'는 착각입니다. 이미지 보안전송이 주는 것은 최대 10분짜리 열쇠입니다. 사람이 메일을 여는 시간은 그보다 훨씬 깁니다. 따라서 링크를 넘기는 설계는 구현 방식의 선택지가 아니라 요건 위반입니다. 받는 즉시 내려받아 메일 첨부로 바꾸는 것만이 요건을 지키는 방법입니다.
둘째는 '목록은 JSON 배열로 온다'는 착각입니다. 공식 문서의 스킬 호출값 예시를 그대로 옮기면 아래와 같습니다. secureimage 값 자체가 JSON 문자열이고, 그 안의 secureUrls는 배열이 아니라 'List(http://...)'라는 문자열입니다. Scala 언어의 List를 toString으로 찍은 모양이 그대로 실려 옵니다.
"secureimage": "{\"privacyAgreement\":\"Y\",\"imageQuantity\":\"1\",\"secureUrls\":\"List(http://secure.kakaocdn.net/...&signature=...)\",\"expire\":\"1970-01-19T05:25:58+0900\"}"
이 값을 배열로 기대하고 읽으면 사진 목록은 빈 채로 끝납니다. 문자열을 배열처럼 다루려다 실패해도 많은 코드가 '사진 없음'으로 처리하고 넘어가기 때문입니다. 오류가 아니라 침묵이라 발견이 늦습니다. 메일은 정상적으로 나가고, 첨부만 비어 있습니다.
두 착각의 공통점은 '카카오가 이 기능을 지원한다'는 기능 층의 답에서 멈췄다는 점입니다. 기능이 있어도 값이 언제 만료되는지(시간 층), 값이 실제로 어떤 모양인지(직렬화 층), 어떤 계정이어야 쓸 수 있는지(계정 층)는 각각 다른 문서에 따로 적혀 있었습니다.

어떻게 고쳤나: 받는 즉시 첨부로 바꾸고 보관하지 않는다
수리는 세 가지였습니다. 첫째, secureUrls를 JSON 배열이 아니라 'List(' 와 ')' 사이를 쉼표로 나누는 문자열로 풉니다. 이 처리는 공식 예시 원문을 입력으로 쓰는 단위 테스트로 고정했습니다. 지어낸 모양으로 테스트를 통과시키면 그건 증명이 아니라 자기 확인이기 때문입니다.
둘째, 스킬 서버는 사진 URL을 받는 즉시 내려받아 메일 첨부로 바꿉니다. 여기서 카카오 스킬 서버의 5초 제한이 걸립니다. 공식 문서는 타임아웃이 고정이라 조정할 수 없다고 적습니다. 그래서 대화창에는 접수 확인을 먼저 돌려주고, 사진 내려받기와 메일 발송은 응답 뒤 백그라운드 작업으로 돌렸습니다. 시험판에서 응답은 최악 19ms였습니다.
로컬 시험만으로는 서버리스 환경에서 응답 뒤 백그라운드 작업이 끝까지 도는지 증명하지 못했습니다. 그래서 2026년 9월 7일 밤 공개 주소로 다시 확인했습니다. 시크릿 없이 부르면 403, 필수 항목이 빠지면 200과 안내(메일 없음), 정상 접수 1건은 200 응답이 0.28초에 오고 담당 수신함에 메일이 실제로 도착했습니다.
셋째, 사진은 저희 서버에 남기지 않습니다. 받아서 메일에 첨부하고 버립니다. 카카오 고객센터 문서는 사진이 보안존 서버에 저장되며 '유효시간 내에 봇 관리자가 확인 및 저장'해야 한다고 적습니다. 내려받는 순간부터 개인정보 보관·파기 책임이 저희 쪽으로 넘어온다는 뜻이고, 보관하지 않는 설계가 그 책임을 가장 작게 만듭니다.
- secureUrls: 배열 파싱 대신 List(...) 문자열 분해, 공식 예시 원문으로 테스트 고정
- 사진 전달: 링크 대신 즉시 내려받아 메일 첨부(플러그인 최대 10장)
- 응답: 접수 확인을 먼저 돌려주고 메일·사진은 백그라운드(5초 제한)
- 보관: 서버에 남기지 않고 첨부 후 폐기

교차 확인에서 나온 제약은 무엇이었나?
독립 재확인이 찾은 제약 가운데 셋은 고객 준비물을 늘렸습니다. 첫째, 배포 조건이 '운영 채널과 개발 채널이 모두 동일한 비즈앱에 연결'이라 고객이 준비할 채널이 1개가 아니라 2개입니다. 둘째, 채널당 마스터는 1명뿐이라 그 사람이 퇴사했거나 대행사 계정이면 프로젝트가 바로 멈춥니다. 셋째, 비즈니스 채널 신청 서류가 신청인 유형(개인사업자 대표·법인 대표·직원·대행사)에 따라 다릅니다.
운영 쪽 제약도 있었습니다. 스킬 서버는 공인 IP나 공중망 도메인만 쓸 수 있어 사내망 서버에는 둘 수 없습니다. 배포는 최대 30분이 걸리고 시작하면 중지할 수 없습니다. 비즈앱 1개당 플러그인은 최대 10개이고, 봇 삭제는 7일 지연되며, 개발 채널의 테스트 발송도 Event API 과금 대상입니다.
문구를 고친 항목도 있습니다. 초판은 '카카오에 내는 비용은 Event API 외에는 없다'로 읽힐 수 있게 적었습니다. 그런데 문서가 말하는 것은 '요금이 명시된 항목이 Event API(메시지 1건당 15원, VAT 별도)'라는 사실이지, 나머지가 무료라는 선언이 아닙니다. 내부 문서에는 이 유보가 적혀 있었는데, 고객 회신으로 옮기는 과정에서 단정으로 바뀌어 나갈 뻔했습니다.

같은 실수를 막는 장치
첫째 장치는 테스트 입력의 출처 규칙입니다. 외부 플랫폼이 보내는 값을 다루는 테스트는 공식 문서 예시를 글자 그대로 입력으로 씁니다. 저희가 상상한 모양으로 테스트를 쓰면, 저희가 착각한 모양 그대로 통과합니다. secureUrls 문제는 이 규칙이 없었다면 테스트도 같은 착각을 공유했을 자리입니다.
둘째 장치는 '고객에게 말해도 되는 사실' 목록을 기술검토 문서와 분리한 일입니다. 카카오 관련 주장은 한 줄에 한 사실씩, 출처 URL과 함께 별도 파일에 적습니다. 검토 문서는 '왜 그 결론에 왔는지'를 담고, 사실 목록은 '무엇을 고객에게 말해도 되는지'를 담습니다. 회신 초안은 이 목록과 대조하는 검사 스크립트를 통과해야 나갑니다. 유보가 단정으로 바뀌어 나가는 경로를 막으려는 장치입니다.
셋째 장치는 찾지 못한 값을 숫자로 채우지 않는 규칙입니다. 이미지 파일 크기·확장자 제한, 봇 개수와 트래픽 한도는 공식 문서 어디에도 없었습니다. 회신에 수치를 쓰지 않고 실측으로만 답합니다. 사진 원본 해상도가 카카오 CDN에서 보존되는지도 미확인으로 남겼습니다. 정밀 계측기기 회사라 이 값이 사진 기능의 존재 이유를 가를 수 있어, 구축 첫 주에 실측해 알리기로 했습니다.
넷째는 넓은 의미의 습관입니다. 문서 검토가 끝나도 시험판을 돌리고, 시험판은 응답이 아니라 도착을 셉니다. 같은 시험판에서 응답 검사는 통과했는데 메일이 사라지던 다른 결함은 챗봇 알림 메일 누락 디버깅기에 따로 적었습니다. 카카오 챗봇처럼 외부 플랫폼 요건이 일정을 좌우하는 구축을 검토 중이라면 상담으로 요건을 보내 주셔도 좋습니다.
정리: 카카오 챗봇 이미지 전송을 설계할 때 확인할 네 가지
카카오 챗봇 이미지 전송은 '플러그인이 지원한다'에서 끝나지 않습니다. 저희가 실제로 걸려 넘어질 뻔한 순서대로 정리하면 아래 네 가지입니다.
- 시간: URL은 최대 10분. 링크가 아니라 즉시 내려받아 첨부로 바꾼다
- 형식: secureUrls는 List(...) 문자열. 공식 예시 원문으로 테스트한다
- 계정: 사진이 아니라 챗봇 연결 자체가 비즈니스 채널을 요구한다. 심사 영업일 3~5일이 두 번
- 책임: 내려받는 순간 개인정보 보관 책임이 생긴다. 보관하지 않는 설계를 기본으로 둔다
처음 가설이 틀렸다는 사실도 그대로 남깁니다. '사진을 빼면 빨리 시작할 수 있다'는 판단은 한 문서만 보고 내린 결론이었고, 다른 층의 문서가 그 판단을 뒤집었습니다. 외부 플랫폼 위에 무언가를 만들 때는 기능·계정·시간·직렬화 네 층을 각각 열어 보고 나서야 견적과 일정을 말할 수 있습니다.