샌드고 API 오류 코드와 재시도 전략

샌드고 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 대체 발송을 켜세요.

이 문서에서 쓰는 패키지

관련 문서