샌드고 API 오류 코드와 재시도 전략
알림톡·문자 발송 실패 시 나오는 오류 코드 전체 목록과, 재시도해도 되는 실패와 그렇지 않은 실패를 구분하는 방법.
샌드고 API 오류의 대부분은 재시도로 해결되지 않습니다. 요청이 잘못됐거나 계정 상태가 문제이기 때문입니다. 이 구분을 먼저 잡아야 무의미한 재시도 루프와 크레딧 낭비를 피할 수 있습니다.
응답 형태
{
"code": "INVALID_TEMPLATE_CODE",
"message": "존재하지 않는 템플릿 코드입니다."
}
검증 실패는 필드별 오류가 함께 옵니다.
{
"code": "VALIDATION_FAILED",
"message": "The given data was invalid.",
"errors": {
"targetUrl": ["The target url field must be a valid URL."]
}
}
오류 코드 전체
| HTTP | 코드 | 뜻 | 재시도 |
|---|---|---|---|
| 400 | EMPTY_CONTACTS |
수신자 배열이 비었다 | ❌ |
| 400 | INVALID_TEMPLATE_CODE |
없거나 미승인 템플릿 | ❌ |
| 400 | VALIDATION_FAILED |
입력값 검증 실패 | ❌ |
| 400 | NOT_A_BRAND_MESSAGE |
자유형(FT/FI/FW)+개별 수신자를 브랜드메시지로 보냄 | ❌ |
| 401 | INVALID_ACCESS_KEY |
액세스 키/시크릿이 틀림 | ❌ |
| 402 | PAYMENT_REQUIRED |
크레딧 부족 | ❌ (충전 필요) |
| 403 | ACCESS_KEY_NOT_APPROVED |
앱이 승인되지 않음 | ❌ |
| 403 | IP_NOT_ALLOWED |
허용 IP 밖에서 호출 | ❌ |
| 404 | NOT_FOUND |
캠페인 등 대상 없음 | ❌ |
| 404 | INVALID_KAKAO_SENDER_KEY |
카카오 발신프로필 키가 틀림 | ❌ |
| — | 타임아웃 / 5xx | 일시적 장애 | ✅ |
재시도할 가치가 있는 건 마지막 한 줄뿐입니다.
언어별 처리
Node.js / TypeScript
import Sendgo, { SendgoError } from '@sendgo/node';
try {
await sendgo.alimtalk.send({ templateCode: 'ORDER_CONFIRM_001', contacts });
} catch (error) {
if (error instanceof SendgoError) {
switch (error.code) {
case 'PAYMENT_REQUIRED':
// 재시도해도 소용없다. 운영자에게 알린다.
await notifyOps('샌드고 크레딧이 소진되었습니다');
break;
case 'INVALID_TEMPLATE_CODE':
case 'INVALID_KAKAO_SENDER_KEY':
// 설정 오류 — 배포된 코드가 잘못됐다는 뜻이므로 크게 알린다.
logger.error('샌드고 설정 오류', { code: error.code });
break;
default:
logger.warn('알림톡 발송 실패', { code: error.code, message: error.message });
}
return; // 재시도하지 않는다
}
throw error; // 네트워크 오류 등은 상위 재시도 로직으로
}
Python
from sendgo import SendgoError
NON_RETRYABLE = {
"EMPTY_CONTACTS", "INVALID_TEMPLATE_CODE", "VALIDATION_FAILED",
"INVALID_ACCESS_KEY", "PAYMENT_REQUIRED",
"ACCESS_KEY_NOT_APPROVED", "IP_NOT_ALLOWED", "INVALID_KAKAO_SENDER_KEY",
}
try:
client.alimtalk.send(template_code="ORDER_CONFIRM_001", contacts=contacts)
except SendgoError as e:
if e.code in NON_RETRYABLE:
logger.error("알림톡 발송 실패(재시도 불가): %s", e.code)
return
raise # 일시적 오류만 상위로 올려 재시도
PHP · Laravel
<?php
use Sendgo\Php\Exception\SendgoException;
try {
$sendgo->alimtalk->send([
'templateCode' => config('sendgo.templates.order_confirm'),
'contacts' => $contacts,
]);
} catch (SendgoException $e) {
// 발송 실패가 주문 처리를 되돌리게 하지 않는다.
Log::error('알림톡 발송 실패', [
'code' => $e->getCode(),
'message' => $e->getMessage(),
'order' => $order->id,
]);
}
큐에서의 재시도
Laravel 큐나 Celery 를 쓴다면 재시도 횟수를 낮게 잡으세요. 기본값을 그대로 두면 재시도로 해결되지 않는 오류에 대해 수십 번 같은 요청을 보냅니다.
<?php
class SendAlimtalk implements ShouldQueue
{
public int $tries = 3;
public int $backoff = 30;
// 재시도해도 소용없는 예외는 즉시 실패 처리한다.
public function retryUntil(): \DateTime
{
return now()->addMinutes(5);
}
}
크레딧 부족(PAYMENT_REQUIRED)에서는 특히 조심해야 합니다. 큐에 1만 건이 쌓인 상태에서 잔액이 떨어지면, 재시도 설정에 따라 수만 번의 실패 호출이 발생합니다.
요청은 성공, 메시지는 미도달
HTTP 200 은 접수 성공이지 도달 성공이 아닙니다.
- 수신자가 카카오톡을 안 쓴다 → SMS 대체 발송으로 커버
- 채널을 차단했다 → 대체 발송으로 커버
- 없는 번호다 → 데이터 정합성 문제. 발송 전 번호 검증 필요
건별 결과는 콘솔의 발송 내역에서 확인합니다.
발송 전 점검
가장 좋은 오류 처리는 발송 전에 막는 것입니다.
<?php
// 대량 발송 전 잔액 확인 — 1만 건 도중에 떨어지는 것보다 낫다.
// 번호 정규화 — 하이픈과 국가번호 제거
$contacts = collect($recipients)
->map(fn ($r) => preg_replace('/\D/', '', $r->phone))
->map(fn ($p) => str_starts_with($p, '82') ? '0'.substr($p, 2) : $p)
->unique() // 중복 제거 — 두 번 나가고 두 번 과금된다
->filter(fn ($p) => strlen($p) >= 10)
->values();
다음 단계
자주 묻는 질문
- 발송에 실패하면 재시도해야 하나요?
- 대부분은 아닙니다. 샌드고의 발송 실패는 거의 전부 요청이 잘못됐거나(템플릿 코드, 발신 키, 수신자) 계정 상태 문제(미승인, 크레딧 부족)라서 같은 요청을 다시 보내도 똑같이 실패합니다. 재시도가 의미 있는 것은 네트워크 타임아웃과 5xx 뿐입니다.
- PAYMENT_REQUIRED 는 어떻게 처리하나요?
- 크레딧 잔액이 부족하다는 뜻입니다. 재시도해도 해결되지 않으므로 즉시 알림을 띄우고 충전해야 합니다. 대량 발송 중이라면 남은 건이 모두 실패하므로, 발송 전에 잔액을 확인하는 편이 좋습니다.
- IP_NOT_ALLOWED 가 로컬에서만 납니다.
- 앱에 허용 IP 를 설정했는데 개발 환경 IP 가 목록에 없기 때문입니다. 개발용 앱을 따로 만들어 허용 IP 를 비워 두거나, 개발자 IP 를 추가하세요. 프로덕션 IP 는 NAT 게이트웨이나 로드밸런서를 쓰면 인스턴스 IP 와 다르므로 실제 아웃바운드 IP 를 확인해야 합니다.
- 요청은 성공했는데 메시지가 안 왔습니다.
- 요청 접수와 실제 도달은 다릅니다. 수신자가 카카오톡을 쓰지 않거나 채널을 차단했을 수 있습니다. 콘솔의 발송 내역에서 건별 결과를 확인하고, 도달이 중요한 알림이라면 SMS 대체 발송을 켜세요.