Skip to Content
개발자알림 발송 API

알림 발송 API

알림 발송의 핵심 엔드포인트 4종을 다룹니다. 모든 요청은 인증에서 설명한 HMAC 서명 헤더 4종이 필요하며, 모든 2xx 응답은 { "data": ..., "meta": { "timestamp": ... } } 봉투(envelope)로 감싸 반환됩니다.

엔드포인트설명필요 스코프
POST /v1/notifications단건 발송notifications:send
POST /v1/notifications/bulk대량 발송 (최대 500건)notifications:send
GET /v1/notifications/:requestId상태·이력 조회notifications:read
POST /v1/notifications/:requestId/cancel발송 취소notifications:send

요청 본문에 아래 스키마에 없는 필드가 포함되면 400 에러로 거부됩니다. 본문 크기 제한은 1MB입니다.

상태 라이프사이클

발송은 비동기로 처리됩니다. 발송 요청(request)의 상태는 다음 순서로 전이됩니다.

queued → processing → sent (성공) → failed (모든 시도 실패) └─ 종료 전 어느 시점이든 → canceled (취소 API 호출 시)
상태의미
queued접수 완료, 발송 대기 중. 예약 발송도 예약 시각 전까지 queued입니다
processing공급자를 통해 발송 시도 중
sent발송 성공 (종료 상태)
failed재시도를 포함한 모든 시도 실패 (종료 상태)
canceled취소 API로 취소됨 (종료 상태)

조회 응답에는 요청 상태 외에 큐 잡(job) 상태와 공급자별 시도(attempt) 이력이 함께 담깁니다.

  • 잡 상태: scheduled queued processing retrying dead_letter completed failed canceled
  • 시도 상태: success failure — 폴백이 동작하면 한 요청에 여러 시도가 쌓입니다

상태 변화를 실시간으로 받으려면 웹훅을 사용하세요.

단건 발송

POSThttps://api.posmit.io/v1/notificationsscope notifications:send

요청 필드

필드타입필수설명
channelstring발송 채널. 아래 채널 표 참고
recipient.addressstring수신자 주소 (채널별 의미가 다름). 최대 320자
recipient.namestring수신자 이름. 최대 120자
content.textstring본문 텍스트. templateCode가 없으면 필수
content.titlestring제목 (이메일 제목, 푸시 타이틀 등). 최대 200자
content.htmlstringHTML 본문 (이메일 등)
content.templateCodestring템플릿 코드. 최대 120자. 템플릿 발송 참고
content.templateVariablesobject템플릿 변수 (string → string 맵)
idempotencyKeystring중복 발송 방지 키. 최대 120자. 멱등성 참고
scheduledAtstring예약 발송 시각. ISO 8601 (T 구분자 필수), 미래 시각만 허용
preferredProviderAccountIdsstring[]우선 시도할 공급자 계정 ID(UUID). 최대 10개
metadataobject임의 메타데이터 (string → string 맵)

채널

channel에 지정할 수 있는 값은 8종이며, 채널에 따라 recipient.address의 의미가 다릅니다.

channel채널recipient.address의 의미
smsSMS 문자수신자 전화번호
kakao_alimtalk카카오 알림톡수신자 전화번호
email이메일수신자 이메일 주소
push_fcmFCM 푸시FCM 디바이스 토큰
slack_webhookSlack (Incoming Webhook)발송에는 사용되지 않음 (아래 참고)
discord_webhookDiscord (Webhook)발송에는 사용되지 않음 (아래 참고)
slack_botSlack (Bot)지원 준비 중
discord_botDiscord (Bot)지원 준비 중

slack_webhook·discord_webhook 채널의 실제 전송 대상은 공급자 계정에 등록된 웹훅 URL입니다. recipient.address는 발송에 쓰이지 않지만 필수 필드이므로, "ops-alerts"처럼 조회할 때 알아볼 수 있는 식별용 값을 넣으세요.

채널별 요청 예시

SMS

{ "channel": "sms", "recipient": { "address": "01012345678" }, "content": { "text": "[Posmit] 인증번호는 123456입니다." } }

이메일title이 메일 제목이 되고, html이 있으면 HTML 메일로 발송됩니다.

{ "channel": "email", "recipient": { "address": "user@example.com", "name": "홍길동" }, "content": { "title": "가입을 환영합니다", "text": "홍길동님, 가입이 완료되었습니다.", "html": "<h1>환영합니다</h1><p>홍길동님, 가입이 완료되었습니다.</p>" } }

카카오 알림톡 — 검수 승인된 알림톡 템플릿과 연결된 템플릿 코드로 발송합니다.

{ "channel": "kakao_alimtalk", "recipient": { "address": "01012345678" }, "content": { "templateCode": "order_complete", "templateVariables": { "userName": "홍길동", "orderNo": "A20260710-001" } } }

FCM 푸시

{ "channel": "push_fcm", "recipient": { "address": "FCM_DEVICE_TOKEN" }, "content": { "title": "새 메시지", "text": "새 메시지가 도착했습니다." } }

Slack 웹훅

{ "channel": "slack_webhook", "recipient": { "address": "ops-alerts" }, "content": { "text": ":rotating_light: 결제 실패율이 5%를 넘었습니다." } }

Discord 웹훅

{ "channel": "discord_webhook", "recipient": { "address": "deploy-log" }, "content": { "text": "v2.4.1 배포가 완료되었습니다." } }

템플릿 발송

content.templateCode를 지정하면 대시보드에 등록된 템플릿으로 발송합니다. 해석 규칙은 다음과 같습니다.

  1. 워크스페이스 + 템플릿 코드 + 채널이 일치하고 활성화된 템플릿을 찾습니다. 없으면 400.
  2. API Key에 허용 템플릿 목록이 설정되어 있고 그 안에 없으면 403.
  3. 템플릿의 발행(published)된 최신 버전을 사용합니다. 발행 버전이 없으면 400.
  4. 요청에 content.text·title·html을 함께 보내면 해당 필드는 템플릿 값을 덮어씁니다.
  5. 본문의 {{변수명}} 자리를 templateVariables의 값으로 치환합니다. 변수명은 영숫자와 언더스코어만 허용되며, 전달하지 않은 변수는 빈 문자열로 치환됩니다.
  6. 카카오 알림톡 템플릿은 검수 상태가 승인(APPROVED)이어야 합니다. 아니면 400.

예를 들어 템플릿 본문이 {{userName}}님, 주문 {{orderNo}}이 완료되었습니다.라면:

{ "channel": "kakao_alimtalk", "recipient": { "address": "01012345678" }, "content": { "templateCode": "order_complete", "templateVariables": { "userName": "홍길동", "orderNo": "A20260710-001" } } }

멱등성 (idempotencyKey)

네트워크 오류로 같은 요청을 재시도할 때 중복 발송을 막으려면 idempotencyKey를 지정하세요.

  • 중복 판정 범위는 워크스페이스 단위입니다. 같은 키로 이미 접수된 요청이 있으면 새로 만들지 않고 기존 요청을 반환하며, 응답에 "deduplicated": true가 표시됩니다.
  • 이때 requestId·status·scheduledAt은 기존 요청의 현재 값입니다. 이미 발송이 끝났다면 statussent로 올 수도 있습니다.
{ "channel": "sms", "recipient": { "address": "01012345678" }, "content": { "text": "[Posmit] 인증번호는 123456입니다." }, "idempotencyKey": "signup-otp-user123-20260710" }

예약 발송 (scheduledAt)

scheduledAt에 미래의 ISO 8601 시각을 지정하면 해당 시각에 발송됩니다.

{ "channel": "email", "recipient": { "address": "user@example.com" }, "content": { "title": "내일 오전 이벤트 안내", "text": "..." }, "scheduledAt": "2026-07-11T00:00:00.000Z" }
  • 날짜와 시각 사이에 T 구분자가 있는 엄격한 ISO 8601 형식이어야 합니다. 파싱할 수 없으면 400.
  • 과거·현재 시각이면 400 (scheduledAt must be in the future).
  • 예약된 요청의 상태는 발송 시각 전까지 queued이며, 그 사이에 취소할 수 있습니다.

응답 — 201 Created

{ "data": { "status": "queued", "requestId": "9b2f1c3e-8a4d-4c6e-b7f0-1234567890ab", "queueJobId": "1234", "deduplicated": false, "scheduledAt": null }, "meta": { "timestamp": "2026-07-10T09:00:00.000Z" } }
필드타입설명
statusstring요청 상태. 신규 접수 시 항상 queued, 중복(dedup) 시 기존 요청의 현재 상태
requestIdstring (UUID)발송 요청 ID. 조회·취소에 사용합니다
queueJobIdstring 또는 null내부 큐 잡 ID. 중복 응답에서 기존 잡이 없으면 null
deduplicatedbooleanidempotencyKey 중복으로 기존 요청이 반환되었는지 여부
scheduledAtstring 또는 null예약 발송 시각. 즉시 발송이면 null

주요 에러

HTTP발생 조건
400필드 검증 실패, 정의되지 않은 필드 포함, content.text 누락, 템플릿 없음/발행 버전 없음, scheduledAt 형식·과거 시각
401인증 실패 — 인증 참고
403스코프 부족, 허용되지 않은 템플릿 사용
429요청 한도 초과 — 요청 한도 참고

대량 발송

POSThttps://api.posmit.io/v1/notifications/bulkscope notifications:send

한 번의 요청으로 최대 500건을 접수합니다. items 배열의 각 항목은 단건 발송 요청 본문과 완전히 동일하며, idempotencyKey·템플릿·scheduledAt도 항목별로 각각 적용됩니다.

요청

{ "items": [ { "channel": "sms", "recipient": { "address": "01012345678" }, "content": { "text": "[Posmit] 점검 안내: 오늘 밤 2시부터 30분간 점검합니다." }, "idempotencyKey": "maintenance-20260710-user1" }, { "channel": "sms", "recipient": { "address": "01098765432" }, "content": { "text": "[Posmit] 점검 안내: 오늘 밤 2시부터 30분간 점검합니다." }, "idempotencyKey": "maintenance-20260710-user2" } ] }

items가 500개를 초과하거나 항목 중 하나라도 필드 검증에 실패하면 요청 전체가 400으로 거부됩니다.

응답 — 201 Created

{ "data": { "status": "queued", "total": 2, "queued": 1, "deduplicated": 1, "items": [ { "requestId": "9b2f1c3e-8a4d-4c6e-b7f0-1234567890ab", "status": "queued", "queueJobId": "1234", "deduplicated": false, "scheduledAt": null }, { "requestId": "7a1e0d2c-5b3f-4a8e-9c6d-0987654321fe", "status": "sent", "queueJobId": null, "deduplicated": true, "scheduledAt": null } ] }, "meta": { "timestamp": "2026-07-10T09:00:00.000Z" } }
필드설명
total성공적으로 접수된 항목 수
queued새로 접수된 항목 수 (total - deduplicated)
deduplicatedidempotencyKey 중복으로 기존 요청이 반환된 항목 수
items접수된 항목별 결과. 단건 발송 응답과 동일한 구조

검증은 통과했지만 접수 과정에서 실패한 항목(예: 템플릿 해석 실패)은 응답 items에서 제외됩니다. total은 요청한 항목 수가 아니라 성공한 항목 수이므로, total이 보낸 items 개수보다 작으면 일부가 실패한 것입니다. 항목별 idempotencyKey를 지정해 두면 누락분만 안전하게 재요청할 수 있습니다.

상태 조회

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

발송 요청의 현재 상태, 큐 잡 정보, 공급자별 시도 이력을 반환합니다.

  • requestId는 UUID 형식이어야 합니다. 형식이 다르면 400.
  • 자신의 워크스페이스 요청만 조회할 수 있습니다. 없으면 404.
curl https://api.posmit.io/v1/notifications/9b2f1c3e-8a4d-4c6e-b7f0-1234567890ab \ -H "X-API-Key: pk_xxxxxxxxxxxxxxxxxxxx.YOUR_KEY_SECRET" \ -H "X-Timestamp: 1752130860" \ -H "X-Nonce: a1b2c3d4e5f60718293a4b5c" \ -H "X-Signature: GENERATED_SIGNATURE"

응답 — 200 OK

{ "data": { "requestId": "9b2f1c3e-8a4d-4c6e-b7f0-1234567890ab", "workspaceId": "0f8e7d6c-5b4a-3928-1706-fedcba987654", "channel": "sms", "status": "sent", "recipientAddress": "01012345678", "requestedAt": "2026-07-10T09:00:00.000Z", "scheduledAt": null, "selectedProviderAccountId": "3c2b1a09-8f7e-6d5c-4b3a-291807654321", "selectedProviderMessageId": "M4V20260710090001", "finalErrorCode": null, "finalErrorMessage": null, "job": { "id": "5d4c3b2a-1908-7f6e-5d4c-3b2a19087f6e", "queueJobId": "1234", "status": "completed", "attemptsMade": 1, "maxAttempts": 3, "queuedAt": "2026-07-10T09:00:00.100Z", "scheduledAt": null, "startedAt": "2026-07-10T09:00:00.350Z", "finishedAt": "2026-07-10T09:00:01.200Z", "lastError": null }, "attempts": [ { "notificationJobId": "5d4c3b2a-1908-7f6e-5d4c-3b2a19087f6e", "providerAccountId": "3c2b1a09-8f7e-6d5c-4b3a-291807654321", "providerKind": "solapi_sms", "providerDisplayName": "Solapi SMS", "status": "success", "failureCode": null, "failureMessage": null, "providerMessageId": "M4V20260710090001", "startedAt": "2026-07-10T09:00:00.400Z", "finishedAt": "2026-07-10T09:00:01.150Z" } ] }, "meta": { "timestamp": "2026-07-10T09:05:00.000Z" } }

응답 필드

필드타입설명
requestIdstring (UUID)발송 요청 ID
workspaceIdstring (UUID)워크스페이스 ID
channelstring발송 채널
statusstringqueued processing sent failed canceled
recipientAddressstring수신자 주소
requestedAtstring접수 시각
scheduledAtstring 또는 null예약 발송 시각
selectedProviderAccountIdstring 또는 null최종 발송에 사용된 공급자 계정 ID
selectedProviderMessageIdstring 또는 null공급자가 발급한 메시지 ID
finalErrorCodestring 또는 null최종 실패·취소 사유 코드 (예: canceled_by_api)
finalErrorMessagestring 또는 null최종 실패·취소 사유 메시지
jobobject 또는 null큐 잡 정보 (상태, 시도 횟수 attemptsMade/maxAttempts, 시각, 마지막 에러)
attemptsarray공급자별 발송 시도 이력. 시작 시각 오름차순

attempts의 각 항목에는 시도한 공급자(providerKind, providerDisplayName), 결과(status), 실패 시 분류 코드(failureCode)가 담깁니다. 폴백이 동작하면 실패한 시도와 성공한 시도가 순서대로 쌓이므로, 어떤 공급자에서 실패해 어디로 대체 발송됐는지 추적할 수 있습니다.

failureCode 값: credit_exhausted(잔액 부족), rate_limited(공급자 한도 초과), temporary_unavailable(일시 장애), invalid_request, unauthorized, unsupported_channel, unknown

발송 취소

POSThttps://api.posmit.io/v1/notifications/:requestId/cancelscope notifications:send

아직 발송되지 않은 요청을 취소합니다. 예약 발송 취소에 특히 유용합니다.

curl -X POST https://api.posmit.io/v1/notifications/9b2f1c3e-8a4d-4c6e-b7f0-1234567890ab/cancel \ -H "X-API-Key: pk_xxxxxxxxxxxxxxxxxxxx.YOUR_KEY_SECRET" \ -H "X-Timestamp: 1752130920" \ -H "X-Nonce: 0e1d2c3b4a5968778695a4b3" \ -H "X-Signature: GENERATED_SIGNATURE"

동작

  • 상태가 queued 또는 processing이면 canceled로 변경하고, 대기 중인 큐 잡을 제거합니다. 조회 응답의 finalErrorCode에는 canceled_by_api가 기록됩니다.
  • 이미 종료 상태(sent·failed·canceled)이면 아무것도 변경하지 않고 현재 상태를 그대로 반환합니다.
  • 취소가 수행되면 notification.canceled 웹훅 이벤트가 발행됩니다.

응답 — 200 OK

{ "data": { "requestId": "9b2f1c3e-8a4d-4c6e-b7f0-1234567890ab", "status": "canceled", "canceled": true }, "meta": { "timestamp": "2026-07-10T09:02:00.000Z" } }
필드설명
status처리 후 요청 상태
canceled이번 호출로 실제 취소가 수행됐는지 여부

취소는 발송 경쟁(race)에 안전하지 않을 수 있습니다. 취소 호출 시점에 이미 발송 시도가 진행 중이면 (processing) 요청 상태는 canceled가 되지만 진행 중이던 시도는 중단되지 않아, 메시지가 수신자에게 도달할 수 있습니다. 확실한 취소가 필요하면 여유를 둔 scheduledAt 예약 발송과 함께 사용하세요. 이미 종료된 요청에 대한 취소 응답은 "canceled": false입니다.

Last updated on