Skip to Content
개발자라우팅과 폴백

라우팅과 폴백

Posmit은 하나의 채널에 여러 공급자(Provider) 계정을 연결하고, 우선순위에 따라 순차 시도합니다. 앞선 공급자가 일시적 사유로 실패하면 자동으로 다음 공급자로 폴백(failover) 하므로, 단일 공급자 장애가 곧바로 발송 실패로 이어지지 않습니다.

발송 처리 흐름

발송 요청이 큐에서 꺼내지면 워커는 다음 순서로 처리합니다.

사전 차단 검사

email 채널은 수신자 주소가 억제 목록(수신 거부·하드 바운스 등)에 있으면 발송 시도 없이 즉시 invalid_request로 실패 처리됩니다. 테스트 발송도 예외가 아닙니다.

라우트 로드

워크스페이스의 채널 라우트 중 enabled: true인 항목을 priority 오름차순으로 불러옵니다. 비활성화된 공급자 계정에 연결된 라우트는 제외됩니다. 사용할 수 있는 라우트가 하나도 없으면 즉시 unsupported_channel로 실패합니다.

선호 공급자 재정렬

요청에 preferredProviderAccountIds가 있으면 해당 공급자의 라우트를 앞으로 당깁니다. 순서는 “선호 라우트(priority 순) → 나머지 라우트(priority 순)“가 되며, 나머지 라우트도 폴백 후보로 유지됩니다.

순차 시도와 폴백

정렬된 라우트를 순서대로 1회씩 시도합니다.

  • 성공 — 즉시 종료하고 사용한 공급자 계정과 메시지 ID를 기록합니다.
  • 재시도 가능 실패 — 다음 우선순위 공급자로 폴백합니다.
  • 재시도 불가 실패 — 폴백하지 않고 즉시 종료합니다. (아래 폴백 규칙 참고)

최종 실패 처리

모든 공급자가 실패하면 잡(job) 레벨 재시도로 넘어가고, 재시도까지 소진되면 요청 상태가 failed로 확정되며 notification.failed 웹훅이 발행됩니다.

채널 라우트와 우선순위

라우트는 “채널 → 공급자 계정 + 우선순위” 매핑입니다. priority 숫자가 낮을수록 먼저 시도됩니다. 같은 채널에 같은 공급자 계정을 중복 등록할 수는 없습니다.

라우트 관리는 대시보드 API(대시보드 JWT 인증)로 제공됩니다.

GEThttps://api.posmit.io/v1/routes

현재 라우트 목록을 채널·우선순위 순으로 반환합니다. (역할: owner / admin / operator / viewer)

PUThttps://api.posmit.io/v1/routes

라우트를 전체 치환(replace) 합니다. 기존 라우트를 모두 삭제한 뒤 요청 본문의 라우트로 다시 생성하므로, 반드시 유지할 라우트까지 포함한 전체 목록을 보내야 합니다. (역할: owner / admin / operator)

curl -X PUT https://api.posmit.io/v1/routes \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "routes": [ { "channel": "sms", "providerAccountId": "PROVIDER_ACCOUNT_UUID_A", "priority": 1 }, { "channel": "sms", "providerAccountId": "PROVIDER_ACCOUNT_UUID_B", "priority": 2 }, { "channel": "email", "providerAccountId": "PROVIDER_ACCOUNT_UUID_C", "priority": 1, "enabled": true } ] }'
제약
routes 배열 크기최대 200개
priority정수 1 ~ 9999 (낮을수록 우선)
enabled생략 시 true
providerAccountId해당 워크스페이스 소속이어야 함 (아니면 400)

요청 단위 우선순위 조정

발송 요청의 preferredProviderAccountIds(UUID, 최대 10개)로 특정 요청에서만 시도 순서를 바꿀 수 있습니다.

curl -X POST https://api.posmit.io/v1/notifications \ -H "X-API-Key: YOUR_KEY_ID.YOUR_KEY_SECRET" \ -H "X-Timestamp: 1752130800" -H "X-Nonce: YOUR_NONCE" -H "X-Signature: YOUR_SIGNATURE" \ -H "Content-Type: application/json" \ -d '{ "channel": "sms", "recipient": { "address": "+821012345678" }, "content": { "text": "인증번호는 123456 입니다." }, "preferredProviderAccountIds": ["PROVIDER_ACCOUNT_UUID_B"] }'

위 요청은 라우트 우선순위와 무관하게 PROVIDER_ACCOUNT_UUID_B를 먼저 시도하고, 실패 시 나머지 라우트를 priority 순으로 이어서 시도합니다.

폴백 규칙: 어떤 실패가 다음 공급자로 넘어가는가

공급자 시도 결과의 실패 코드에 따라 폴백 여부가 결정됩니다. 재시도 가능(retryable) 실패만 다음 공급자로 넘어가고, 재시도 불가 실패는 즉시 종료합니다 — 잘못된 요청은 어느 공급자로 보내도 실패하기 때문입니다.

실패 코드의미폴백 여부
credit_exhausted공급자 잔액·크레딧 소진폴백
rate_limited공급자 측 속도 제한폴백
temporary_unavailable공급자 일시 장애 (5xx 등)폴백
unknown분류되지 않은 오류폴백
invalid_request잘못된 요청 (수신자·본문 오류 등)즉시 종료
unauthorized공급자 자격 증명 오류즉시 종료
unsupported_channel사용할 수 있는 라우트 없음즉시 종료

공급자별 응답이 위 코드로 정규화됩니다. 예를 들어 공급자 HTTP 응답이 429면 rate_limited, 500 이상이면 temporary_unavailable, 401·403이면 unauthorized로 매핑됩니다. 일부 공급자는 자체 코드 기준이 적용됩니다 — 예: Aligo 잔액 부족(-201)은 credit_exhausted이지만 재시도 불가로 처리되어 폴백하지 않습니다.

서킷브레이커

공급자 계정 단위로 서킷브레이커가 동작해, 연속 실패 중인 공급자에 대한 무의미한 시도를 차단합니다.

항목
단위공급자 계정(providerAccountId)별
열림(OPEN) 조건연속 5회 실패 (성공 1회면 카운터 리셋)
열림 유지 시간60초
복구60초 경과 후 HALF_OPEN으로 전환해 시도를 1회 통과시키고, 성공하면 CLOSED로 복귀

회로가 열린 공급자는 발송 시도 없이 건너뛰며, 시도 이력에는 failureCode: "circuit_open"으로 기록됩니다. 회로 열림은 temporary_unavailable(재시도 가능)과 동일하게 취급되므로 폴백은 계속 진행됩니다.

잡 재시도와 최종 실패

한 번의 잡 실행에서 모든 공급자 폴백이 실패했고 마지막 실패가 재시도 가능이라면, 요청은 queued로 되돌아가 잡 레벨에서 다시 시도됩니다.

  • 잡 재시도 횟수: 기본 3회 (환경 변수 NOTI_QUEUE_JOB_ATTEMPTS)
  • 백오프: 지수 백오프, 기본 시작 지연 3,000ms (NOTI_QUEUE_BACKOFF_MS)

재시도가 소진되거나 재시도 불가 실패로 종료되면 요청 상태가 failed로 확정되고, finalErrorCode / finalErrorMessage가 기록되며 notification.failed 웹훅 이벤트가 발행됩니다. 성공 시에는 sent 상태와 함께 notification.delivered 이벤트가 발행됩니다.

디스패치 TPS 제한

공급자로 나가는 발송 속도를 제어하는 토큰 버킷 제한입니다. 요청 한도(인입 API 제한)와 달리 요청을 거부하지 않고, 워커가 토큰을 확보할 때까지 발송을 지연시키는 방식입니다.

3가지 스코프를 지원하며, 하나의 발송 건에 해당되는 모든 스코프의 한도가 동시에(AND) 적용됩니다.

스코프적용 대상targetId
workspace워크스페이스 전체 발송량생략
provider특정 공급자 계정으로 나가는 발송량공급자 계정 ID
apiKey특정 API 키로 접수된 발송량API 키 자격 증명 ID

관리 API는 대시보드 JWT 인증을 사용합니다.

GEThttps://api.posmit.io/v1/limits/dispatch

설정된 디스패치 한도 목록을 조회합니다. (역할: owner / admin / operator / viewer)

PUThttps://api.posmit.io/v1/limits/dispatch

한도를 생성 또는 갱신(upsert)합니다. (역할: owner / admin)

curl -X PUT https://api.posmit.io/v1/limits/dispatch \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "scope": "provider", "targetId": "PROVIDER_ACCOUNT_UUID", "ratePerSecond": 10, "enabled": true }'
필드제약
scopeworkspace / provider / apiKey
targetIdworkspace 스코프는 생략, 그 외 필수 (최대 64자)
ratePerSecond정수 1 ~ 1,000,000
DELETEhttps://api.posmit.io/v1/limits/dispatch/:id

한도를 삭제합니다. 성공 시 204를 반환합니다. (역할: owner / admin)

디스패치 한도의 토큰 버킷은 발송 워커 프로세스의 메모리에서 동작합니다. 워커를 여러 인스턴스로 수평 확장하면 인스턴스마다 한도가 별도로 적용되어 전체 발송량이 설정값보다 커질 수 있습니다.

어댑터 자체 배치·속도 제한

디스패치 한도와 별개로, 각 공급자 어댑터는 요청을 배치로 묶어 발송하며 자체 API 호출 속도 상한을 가집니다.

공급자초당 API 호출배치 대기 시간배치 최대 크기
Aligo SMS / Aligo 알림톡2800ms500건
SOLAPI (SMS·알림톡 공용)20500ms100건
FCM20300ms500건
Resend2750ms100건
MailerSend21,000ms100건
AWS SES건별 발송 (배치 API 없음)

공급자 계정의 설정(configJson)에 다음 키를 넣으면 계정별로 재정의할 수 있습니다: dispatch_max_requests_per_second, dispatch_batch_window_ms, dispatch_max_batch_size.

발송 시도 이력으로 추적하기

어떤 공급자가 시도되었고 왜 폴백이 일어났는지는 상태 조회 API의 attempts 배열로 확인합니다.

GEThttps://api.posmit.io/v1/notifications/:requestIdscope notifications:read

attempts는 시도 시작 시각 오름차순으로 정렬되며, 폴백이 발생한 요청은 여러 건이 기록됩니다.

{ "data": { "requestId": "9b2f1c3e-…", "channel": "sms", "status": "sent", "selectedProviderAccountId": "PROVIDER_ACCOUNT_UUID_B", "selectedProviderMessageId": "M4V2025…", "attempts": [ { "providerAccountId": "PROVIDER_ACCOUNT_UUID_A", "providerKind": "aligo_sms", "providerDisplayName": "Aligo 기본 계정", "status": "failure", "failureCode": "rate_limited", "failureMessage": "일 발송 한도를 초과했습니다.", "providerMessageId": null, "startedAt": "2026-07-10T09:00:00.120Z", "finishedAt": "2026-07-10T09:00:00.480Z" }, { "providerAccountId": "PROVIDER_ACCOUNT_UUID_B", "providerKind": "solapi_sms", "providerDisplayName": "SOLAPI 백업 계정", "status": "success", "failureCode": null, "failureMessage": null, "providerMessageId": "M4V2025…", "startedAt": "2026-07-10T09:00:00.510Z", "finishedAt": "2026-07-10T09:00:00.930Z" } ] }, "meta": { "timestamp": "2026-07-10T09:00:05.000Z" } }

읽는 방법:

  • 폴백 발생 여부attempts가 2건 이상이면 폴백이 일어난 것입니다. 첫 시도의 failureCode로 원인을 확인합니다.
  • 서킷브레이커 개입failureCode: "circuit_open" 시도는 실제 발송 없이 건너뛴 기록입니다.
  • 최종 사용 공급자 — 최상위 selectedProviderAccountId / selectedProviderMessageId가 실제 발송에 사용된 공급자입니다.
  • 잡 재시도job.attemptsMade / job.maxAttempts로 잡 레벨 재시도 진행 상황을 확인합니다.

알아두면 좋은 사항

Slack·Discord 계열 채널(slack_webhook, slack_bot, discord_webhook, discord_bot)은 라우팅·폴백 구성까지 지원되지만, 현재 실제 발송 어댑터가 구현되지 않아 모의(Mock) 발송으로 항상 성공 처리됩니다. 운영 트래픽에는 사용하지 마세요.

  • SOLAPI 알림톡의 자체 SMS 대체발송 기능은 비활성화되어 있습니다. 알림톡 실패 시의 대체 발송은 Posmit 라우팅 폴백으로 일원화되어 있습니다.
  • 이메일 억제 목록에 등재된 주소는 폴백·테스트 발송을 포함한 모든 발송이 차단됩니다.
  • 공급자 설정을 점검할 때는 라우팅·폴백을 거치지 않는 단건 테스트 발송 API(POST /v1/providers/:id/test-send)를 사용할 수 있습니다. 이 경로는 지정한 공급자 1개만 시도하며, 실패해도 다른 공급자로 넘어가지 않습니다.
Last updated on