Sendgo API error codes and retry strategy

Sendgo API error codes and retry strategy

Every error code returned by the Sendgo send endpoints, and how to tell a failure worth retrying from one that will fail identically every time.

Most Sendgo errors are not fixed by retrying. The request is wrong, or the account is. Getting that distinction right is what keeps you from burning credit in a retry loop.

Response shape

{
  "code": "INVALID_TEMPLATE_CODE",
  "message": "존재하지 않는 템플릿 코드입니다."
}

Validation failures carry per-field detail:

{
  "code": "VALIDATION_FAILED",
  "message": "The given data was invalid.",
  "errors": {
    "targetUrl": ["The target url field must be a valid URL."]
  }
}

Every code

HTTP Code Meaning Retry
400 EMPTY_CONTACTS Recipient array is empty
400 INVALID_TEMPLATE_CODE Unknown or unapproved template
400 VALIDATION_FAILED Input validation failed
400 NOT_A_BRAND_MESSAGE Free-form type sent to the brand-message endpoint
401 INVALID_ACCESS_KEY Wrong access key or secret
402 PAYMENT_REQUIRED Out of credit ❌ (top up)
403 ACCESS_KEY_NOT_APPROVED App not approved
403 IP_NOT_ALLOWED Called from outside the allowlist
404 NOT_FOUND Campaign or resource missing
404 INVALID_KAKAO_SENDER_KEY Wrong Kakao sender profile key
Timeout / 5xx Transient

Only the last row deserves a retry.

Handling it

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('Sendgo credit exhausted');
        break;
      case 'INVALID_TEMPLATE_CODE':
      case 'INVALID_KAKAO_SENDER_KEY':
        // Configuration error — the deployed code is wrong. Make it loud.
        logger.error('Sendgo misconfiguration', { code: error.code });
        break;
      default:
        logger.warn('Alimtalk send failed', { code: error.code, message: error.message });
    }
    return;   // do not retry
  }

  throw error;  // network-level problems go to the caller's retry logic
}

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("Alimtalk send failed, not retryable: %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('Alimtalk send failed', [
        'code'    => $e->getCode(),
        'message' => $e->getMessage(),
        'order'   => $order->id,
    ]);
}

Retries in a queue

Keep the retry count low. Framework defaults will happily send the same doomed request dozens of times.

<?php

class SendAlimtalk implements ShouldQueue
{
    public int $tries = 3;
    public int $backoff = 30;
}

PAYMENT_REQUIRED is the dangerous one: with 10,000 jobs queued and a zero balance, default retry settings turn into tens of thousands of failing calls.

Accepted is not delivered

HTTP 200 means the request was accepted.

  • Recipient does not use KakaoTalk → cover it with SMS fallback
  • Recipient blocked the channel → same
  • Number does not exist → a data problem; validate before sending

Per-recipient outcomes are in the console's send history.

Prevent rather than handle

<?php

// Normalise numbers, strip country codes, drop duplicates before sending.
$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()                          // duplicates send twice and bill twice
    ->filter(fn ($p) => strlen($p) >= 10)
    ->values();

Next

자주 묻는 질문

Should I retry a failed send?
Usually not. Almost every Sendgo failure is either a malformed request (template code, sender key, recipients) or an account state problem (unapproved app, no credit), and the same request will fail the same way. Only network timeouts and 5xx responses are worth retrying.
How do I handle PAYMENT_REQUIRED?
It means the credit balance is exhausted. Retrying does not help — alert an operator and top up. During a bulk send every remaining message will fail, so check the balance before large batches.
Why do I only get IP_NOT_ALLOWED locally?
The app has an IP allowlist that does not include your development machine. Either use a separate development app with no allowlist, or add your IP. For production, note that behind a NAT gateway or load balancer the outbound IP differs from the instance IP.
The request succeeded but no message arrived.
Acceptance and delivery are different things. The recipient may not use KakaoTalk or may have blocked the channel. Check per-recipient results in the console, and enable SMS fallback for notifications that must arrive.

이 문서에서 쓰는 패키지

관련 문서