네이버 클라우드 SENS 인증번호 발송 실패, 알림톡 일일 한도와 SMS 폴백 400 에러가 겹친 경우
알림톡은 일일 한도 1,000건에 걸려 429를, 폴백 SMS는 대시가 들어간 발신번호 때문에 400을 내서 인증번호가 503으로 막혔던 장애입니다. 로그로 원인을 가르는 법, 바로 고치는 설정, 폴백을 평소에도 한 번씩 태워 보는 방법을 적었습니다.
운영을 맡고 있던 서비스에서 회원가입 인증번호가 한 건도 나가지 않은 날이 있었습니다. 인증번호는 네이버 클라우드 SENS로 보내는데, 1차로 카카오 알림톡을 보내고 실패하면 SMS로 다시 보내도록 짜 두었습니다. 그날은 알림톡이 일일 한도 1,000건에 걸려 429를 냈고, 이어서 폴백 SMS가 발신번호에 대시가 들어가 있어서 400을 냈습니다. 두 경로가 다 막히니 사용자에게는 503 VERIFICATION_SEND_FAILED가 보였습니다.
SMS 쪽 문제는 그날 생긴 게 아니었습니다. 서비스를 연 4월 30일부터 6월 1일 장애 날까지 한 달 넘게 SMS 경로는 한 번도 호출되지 않았고, 그래서 아무도 몰랐습니다. 아래에 그날 로그로 원인을 가른 순서와 고치는 설정, 그리고 폴백을 평소에도 한 번씩 돌려 봐야 하는 이유를 적었습니다.

인증번호 발송 503, 로그에서 먼저 볼 것
증상은 인증번호 요청 API 하나만 503을 내고 나머지 API는 멀쩡한 상태였습니다. 시간 순서는 이렇습니다.
- 07:01 알림톡 인증번호 정상 발송 (SENS 콘솔 발송 이력에서 확인)
- 10시 20분대 알림톡 그날 누적 1,000건 도달, 이후 요청은 전부
429 Quota Exceed - 10:24 폴백 SMS도 400, 사용자에게 503
- 사용자 제보를 받고 운영 컨테이너 로그로 원인 확인
서버가 죽은 게 아니라 발송만 막힌 경우라서, 컨테이너 로그에서 발송 쪽 줄만 보면 됩니다. 그날 로그는 이랬습니다.
WARN VerificationService : Alimtalk failed, falling back to SMS:
429 Too Many Requests ... {"status":429,"errorMessage":"Quota Exceed."}
ERROR VerificationService : SMS fallback also failed: ...
400 Bad Request ... {"status":400,"errorMessage":"Validation failed. 1 error(s)",
"errors":["'from' must be phone number format excluding dash(-)"]}
첫 줄은 1차 경로가 한도에 걸렸다는 뜻이고, 둘째 줄은 폴백이 요청 형식 때문에 거부됐다는 뜻입니다. 원인이 서로 다른 두 가지라서, 한쪽만 고치면 다음 날 또 막힐 수 있었습니다.
알림톡 일일 한도 1,000건, 월 한도보다 먼저 찹니다
SENS 콘솔의 이용 한도 현황을 열어 보면 서비스마다 일일 한도와 월 한도가 따로 있습니다. 그날 화면은 이랬습니다.
| 서비스 | 일일 한도 | 월 한도 | 그날 상태 |
|---|---|---|---|
| 알림톡 | 1,000건 | 30,000건 | 일일 1,000건 초과 |
| 국내 SMS | 500건 | 10,000건 | 0건 |
월 한도로 보면 3%만 쓴 상태였는데, 일일 한도가 먼저 찼습니다. 그날 가입이 몰리면서 오전 10시 20분대에 1,000건을 다 썼습니다. 월 소진율만 보고 있으면 “아직 한참 남았다”고 읽히는 숫자입니다.
1,000건은 알림톡 발신 프로필을 등록할 때 들어가는 일별 최대 발송량 기본값이었고, 콘솔에서 직접 올릴 수 없어서 네이버 클라우드 고객지원에 1:1 문의로 증액을 요청해야 했습니다(2026년 6월 기준). 증액 전까지 하루에 보낼 수 있는 양은 알림톡 1,000건과 SMS 500건을 합쳐 약 1,500건입니다. 오픈 초기 가입 추세라면 다시 닿을 수 있는 숫자라서 증액 요청을 같이 넣었습니다.
SMS 폴백 400 에러: 발신번호는 대시 없이, 승인된 번호로
두 번째 원인은 운영 .env의 발신번호 값이었습니다. SENS_SENDER_PHONE에 02-123-4567처럼 대시가 들어간 형식으로 들어 있었고, SMS 발송 코드가 이 값을 정규화 없이 그대로 from에 넣고 있었습니다. SENS는 from에 대시가 있으면 위의 400을 돌려줍니다.
형식 말고도 확인할 게 하나 더 있었습니다. SENS는 발신번호 사전등록제라서, 콘솔 “발신번호 관리”에 등록되고 승인된 번호만 from에 쓸 수 있습니다. 네이버 클라우드 SENS 발신번호 안내에는 “SMS 메시지를 발송하려면 먼저 발신 번호를 등록해야 합니다”라고 되어 있고, 승인에는 영업일 기준 3~4일 정도 걸릴 수 있다고 적혀 있습니다(2026년 10월 확인). 그날 등록해 둔 대표번호는 “서류 보완 필요” 상태로 대기 중이어서, 대시를 빼도 그 번호로는 보낼 수 없었습니다.
정리하면 SMS 폴백이 돌려면 두 조건이 다 맞아야 합니다.
- 발신번호 관리에서 상태가 정상(승인)인 번호일 것
- 값은 숫자만, 대시 없이 넣을 것
알림톡과 SMS가 같은 발신번호 환경변수와 자격 증명을 같이 쓰고 있었다는 점도 같이 봐 둘 만합니다. 한 값이 틀리면 폴백 설정까지 같이 틀립니다.
발신번호 설정 고치는 법과 코드에서 막는 방법
급한 쪽은 코드 배포 없이 고칠 수 있습니다. 발신번호가 .env → docker-compose 환경변수 → application-prod.yml → 설정 클래스 순서로 들어가기 때문에, .env만 고치고 컨테이너를 다시 띄우면 반영됩니다.
# 승인된 발신번호를 숫자만으로 (아래 번호는 예시)
sed -i 's/^SENS_SENDER_PHONE=.*/SENS_SENDER_PHONE=021234567/' .env
docker compose -f docker-compose.prod.yml up -d backend
이때 당시 코드는 대시를 지워 주지 않으니 반드시 숫자만 넣어야 합니다. 다음에 누가 대시를 넣어도 안 깨지게 하려면 설정 값을 읽는 쪽에서 숫자만 남기면 됩니다.
public String getSenderPhone() {
return senderPhone == null ? null : senderPhone.replaceAll("[^0-9]", "");
}
고친 뒤에는 본인 번호로 한 번 보내고 로그를 확인합니다.
curl -s -X POST http://localhost:8080/api/v1/auth/signup/verify-phone-request \
-H 'Content-Type: application/json' -d '{"phone":"본인번호"}'
docker logs --since 2m <백엔드-컨테이너> 2>&1 \
| grep -iE "SENS SMS sent|SENS Alimtalk sent|SENS .* failed|SMS fallback|Alimtalk" | tail -10
SENS SMS sent successfully나 SENS Alimtalk sent successfully가 찍히고 실제로 문자가 오면 복구된 것입니다. 대신 “발신번호 미등록” 같은 새 에러가 나오면 그 번호가 아직 승인되지 않은 것이니, 콘솔에서 발신번호 상태부터 봐야 합니다. “반려” 상태라면 제출한 서류로는 승인이 안 된다는 뜻이라 새로 신청해야 합니다.
폴백을 평소에도 한 번씩 돌려 보는 이유
이번 장애에서 SMS 쪽 400은 한 달 넘게 잠들어 있었습니다. 평소 인증번호가 전부 알림톡으로 나가니 SMS 경로는 호출될 일이 없었고, 호출되지 않으니 틀린 값을 알려 줄 에러도 없었고, 알림톡이 한도에 걸린 그날 처음 호출되면서 400이 드러났습니다.
그래서 폴백은 주 경로가 멀쩡할 때도 일부러 한 번씩 태워 봐야 합니다. 방법은 어렵지 않습니다.
- 카나리 발송 · 하루 한 번처럼 정해 둔 주기로, 주 경로를 건너뛰고 SMS 경로로 내 번호에 한 통을 보내 응답 코드를 확인합니다. 400이나 미등록 에러가 나면 알림을 받습니다.
- 일 단위 소진 감시 · 알림톡 그날 누적 건수를 보고, 일일 한도의 일정 비율을 넘으면 미리 알림을 받습니다. 월 소진율만 보면 이번처럼 3%에서 막힙니다.
- 발송 실패율 알림 · 인증번호 발송 실패가 몇 건 이어지면 사용자 제보보다 먼저 알 수 있게 합니다.
백업도 같은 이유로 복구를 실제로 한 번 해 봐야 믿을 수 있었습니다. 임시 DB에 복원해 행 수를 대조한 과정은 PostgreSQL 백업 복구 리허설에 적었고, 에러 없이 멈춘 작업을 성공 신호로 잡는 방법은 에러 없이 멈춘 크론 백업과 실패 로그 남기는 법에 정리했습니다.
같은 구조로 쓰고 있다면 확인할 것
- SENS 콘솔 이용 한도 현황에서 알림톡·SMS 각각의 일일 한도를 확인합니다. 알림톡 기본값이 1,000건이었습니다.
- 발신번호 관리에서
from에 쓰는 번호가 승인 상태인지 확인합니다. .env의 발신번호 값에 대시가 없는지 확인하고, 코드에서 숫자만 남기도록 정규화합니다.- 폴백 경로를 마지막으로 실제 호출한 날이 언제인지 로그에서 찾아봅니다. 기억나지 않으면 한 통 보내 봅니다.
자주 묻는 질문
폴백 경로는 평소에 검증하지 않아도 되나요?
평소에 한 번도 실행되지 않는 폴백은 검증되지 않은 코드와 같습니다. 주 경로가 살아 있는 동안 설정 오류나 자격 증명 만료가 조용히 쌓이다가, 정작 폴백이 필요한 순간 함께 실패합니다. 주기적으로 강제 실행해 실제로 응답을 받아 두어야 합니다.
카카오 알림톡 월 한도만 확인하면 충분한가요?
월 한도가 넉넉해도 일일 한도가 먼저 막힐 수 있습니다. 저는 월 한도의 3%만 쓴 상태에서 일일 한도 1,000건에 걸려 429를 받았습니다. 한도는 월이 아니라 일 단위부터, 가능하면 시간 단위 소진 추이까지 봐야 합니다.
발신번호 형식 때문에 SMS API가 400을 내는 이유는 무엇인가요?
많은 문자 발송 API는 발신번호를 하이픈 없는 숫자열로만 받습니다. 지역번호-국번-번호처럼 대시가 섞인 형식으로 환경변수에 저장돼 있으면 요청이 400으로 거부됩니다. 이런 값은 저장 시점에 정규화하거나 발송 직전 하이픈을 제거해야 합니다.
두 경로가 동시에 실패하는 장애는 어떻게 예방하나요?
주 경로와 폴백이 서로 다른 원인으로도 겹칠 수 있다는 전제로 봐야 합니다. 폴백을 카나리로 정기 실행해 잠재 실패를 미리 드러내고, 주 경로의 소진 추이를 실시간으로 감시해 한도 도달 전에 알림을 받으면 두 실패가 같은 날 만나는 상황을 피할 수 있습니다.