sendgo-python 1.2.1__tar.gz → 1.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. {sendgo_python-1.2.1 → sendgo_python-1.4.0}/PKG-INFO +304 -1
  2. {sendgo_python-1.2.1 → sendgo_python-1.4.0}/README.md +303 -0
  3. {sendgo_python-1.2.1 → sendgo_python-1.4.0}/pyproject.toml +1 -1
  4. sendgo_python-1.4.0/sendgo/__init__.py +72 -0
  5. sendgo_python-1.4.0/sendgo/_payload.py +34 -0
  6. sendgo_python-1.4.0/sendgo/account.py +77 -0
  7. sendgo_python-1.4.0/sendgo/brand_template.py +102 -0
  8. {sendgo_python-1.2.1 → sendgo_python-1.4.0}/sendgo/client.py +26 -0
  9. sendgo_python-1.4.0/sendgo/http_client.py +155 -0
  10. sendgo_python-1.4.0/sendgo/kakao_image.py +67 -0
  11. sendgo_python-1.4.0/sendgo/kakao_sender.py +127 -0
  12. sendgo_python-1.4.0/sendgo/message_template.py +79 -0
  13. sendgo_python-1.4.0/sendgo/notice_template.py +190 -0
  14. sendgo_python-1.4.0/sendgo/rejected_number.py +38 -0
  15. sendgo_python-1.4.0/sendgo/sender_registration.py +150 -0
  16. sendgo_python-1.4.0/sendgo/webhook.py +87 -0
  17. {sendgo_python-1.2.1 → sendgo_python-1.4.0}/sendgo_python.egg-info/PKG-INFO +304 -1
  18. {sendgo_python-1.2.1 → sendgo_python-1.4.0}/sendgo_python.egg-info/SOURCES.txt +10 -0
  19. sendgo_python-1.2.1/sendgo/__init__.py +0 -34
  20. sendgo_python-1.2.1/sendgo/http_client.py +0 -67
  21. {sendgo_python-1.2.1 → sendgo_python-1.4.0}/sendgo/alimtalk.py +0 -0
  22. {sendgo_python-1.2.1 → sendgo_python-1.4.0}/sendgo/brand_message.py +0 -0
  23. {sendgo_python-1.2.1 → sendgo_python-1.4.0}/sendgo/exceptions.py +0 -0
  24. {sendgo_python-1.2.1 → sendgo_python-1.4.0}/sendgo/friendtalk.py +0 -0
  25. {sendgo_python-1.2.1 → sendgo_python-1.4.0}/sendgo/short_url.py +0 -0
  26. {sendgo_python-1.2.1 → sendgo_python-1.4.0}/sendgo/sms.py +0 -0
  27. {sendgo_python-1.2.1 → sendgo_python-1.4.0}/sendgo/token_manager.py +0 -0
  28. {sendgo_python-1.2.1 → sendgo_python-1.4.0}/sendgo_python.egg-info/dependency_links.txt +0 -0
  29. {sendgo_python-1.2.1 → sendgo_python-1.4.0}/sendgo_python.egg-info/requires.txt +0 -0
  30. {sendgo_python-1.2.1 → sendgo_python-1.4.0}/sendgo_python.egg-info/top_level.txt +0 -0
  31. {sendgo_python-1.2.1 → sendgo_python-1.4.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sendgo-python
3
- Version: 1.2.1
3
+ Version: 1.4.0
4
4
  Summary: Sendgo Python SDK — 카카오 알림톡/브랜드메시지, SMS/LMS/MMS
5
5
  Author-email: Sendgo <dev@sendgo.io>
6
6
  License: MIT
@@ -501,8 +501,284 @@ sendgo.short_url.deactivate(code) # 리다이렉트만 중지, 통계는 남
501
501
  `stats` 는 일별 추이(`daily`)와 디바이스(`byDevice`)·유입경로(`byReferer`)·국가(`byCountry`)별
502
502
  분해를 반환합니다. 일별 추이는 사전 집계 표에서 읽으므로 클릭이 많아도 응답 시간이 일정합니다.
503
503
 
504
+ ## 관리 API — 채널·템플릿·발신번호 등록 (v2 전용)
505
+
506
+ 발송은 처음부터 API였지만 **등록과 심사는 콘솔에서만** 되던 것들이 있었습니다.
507
+ 1.3.0 부터 그 작업도 코드로 처리합니다.
508
+
509
+ | 서비스 | 하는 일 | 계정 |
510
+ | --- | --- | --- |
511
+ | `client.kakao_senders` | 카카오 채널 인증·등록·동기화, 브랜드메시지 M/N 신청 | 기업 |
512
+ | `client.notice_templates` | 알림톡 템플릿 CRUD, 검수 요청·취소, 승인 취소, 휴면 해제 | 기업 |
513
+ | `client.brand_templates` | 브랜드메시지(구 친구톡) 템플릿 CRUD, 동기화, 가져오기 | 기업 |
514
+ | `client.sender_registration` | 발신번호 등록 신청, 중복 확인, 유형 안내 | 개인·기업 |
515
+ | `client.message_templates` | 문자 상용구 템플릿 CRUD | 개인·기업 |
516
+ | `client.kakao_images` | 카카오 이미지 업로드 — 템플릿용 URL 발급 | 기업 |
517
+ | `client.rejected_numbers` | 수신거부(080) 번호 조회 | 개인·기업 |
518
+ | `client.webhook` | 이벤트 웹훅 구독 — 심사 결과 수신 | 개인·기업 |
519
+
520
+ > **sendgo.io 콘솔에 들어올 일이 없습니다.** 고객의 채널·발신번호·템플릿을
521
+ > 여러분 화면만으로 끝까지 처리할 수 있습니다. 휴대폰 발신번호는 콘솔의 PASS
522
+ > 본인인증 대신 **신분증 사본(`identityDocument`)을 받아 sendgo 운영자가 대신
523
+ > 심사**합니다.
524
+ >
525
+ > 사람이 개입하는 지점은 **카카오 채널 인증번호 하나**뿐이고, 그마저도
526
+ > 여러분 화면에서 입력받으면 됩니다 — 카카오가 관리자 휴대폰으로 직접 보내는
527
+ > 확인이라 없앨 수 없습니다.
528
+ >
529
+ > 심사가 붙는 것들은 **비동기**입니다. 등록 호출이 성공했다는 건 "접수됐다"는
530
+ > 뜻이지 "쓸 수 있다"는 뜻이 아닙니다 — 웹훅을 구독해 결과를 받으세요.
531
+
532
+ 인자는 snake_case 로 씁니다 — SDK 가 camelCase 로 변환해 보냅니다.
533
+
534
+ ### 카카오 채널 등록
535
+
536
+ ```python
537
+ # 1단계 — 카카오가 관리자 휴대폰으로 인증번호를 SMS 발송한다 (응답에 번호는 없다)
538
+ client.kakao_senders.request_token("@my-channel", "01012345678")
539
+
540
+ # 2단계 — 사람이 받은 인증번호로 발신프로필 생성
541
+ created = client.kakao_senders.create(
542
+ token="123456",
543
+ yellow_id="@my-channel",
544
+ phone_number="01012345678",
545
+ category_code="001001", # categories() 로 조회
546
+ )
547
+
548
+ kakao_sender_key = created["data"]["sender"]["kakaoSenderKey"]
549
+
550
+ client.kakao_senders.categories()
551
+ client.kakao_senders.list()
552
+ client.kakao_senders.sync() # 전체 상태 동기화 (하루 한 번 권장)
553
+ client.kakao_senders.sync(kakao_sender_key) # 단건
554
+ ```
555
+
556
+ 채널이 카카오 쪽에서 차단되면 발송이 조용히 실패하기 시작합니다. `sync()` 를
557
+ 주기적으로 돌리고 `block: True` 인 채널을 감시하세요.
558
+
559
+ ### 알림톡 템플릿 등록과 검수
560
+
561
+ ```python
562
+ created = client.notice_templates.create(
563
+ kakao_sender_key=kakao_sender_key,
564
+ template_name="주문 접수 안내",
565
+ template_content="#{name}님, 주문 #{orderNo}이 접수되었습니다.",
566
+ template_message_type="BA", # BA 기본형 / EX 부가정보형 / AD 채널추가형 / MI 복합형
567
+ template_emphasize_type="NONE", # NONE / TEXT / ITEM_LIST / IMAGE
568
+ category_code="001001",
569
+
570
+ # sendgo 자체 정책 게이트 — 카카오 심사와 별개다
571
+ message_purpose="order_delivery",
572
+ legal_basis="transaction",
573
+ benefit_origin="none",
574
+ expiry_type="none",
575
+
576
+ # 선택 필드는 그냥 이어서 쓰면 된다 (snake_case → camelCase 자동 변환)
577
+ buttons=[{"name": "주문 조회", "linkType": "WL", "linkMo": "https://example.com/orders"}],
578
+ )
579
+
580
+ template_code = created["data"]["template"]["templateCode"]
581
+
582
+ # 검수 요청 — 증빙이 필요하면 파일도 붙인다 (첨부가 있으면 comment 필수)
583
+ client.notice_templates.request_inspection(template_code)
584
+
585
+ with open("proof.png", "rb") as f:
586
+ client.notice_templates.request_inspection(
587
+ template_code,
588
+ comment="주문 확인 화면 첨부",
589
+ attachments=[("proof.png", f, "image/png")],
590
+ )
591
+
592
+ # 결과는 비동기다. 웹훅이 없으므로 폴링한다
593
+ synced = client.notice_templates.sync(template_code)
594
+ synced["data"]["template"]["inspectionStatus"] # REG → REQ → APR / REJ
595
+ ```
596
+
597
+ `opt_in_review_confirmed` · `cta_clear_confirmed` · `policy_confirmed` 는 기본값이
598
+ `True` 지만, **내용을 실제로 검토한 뒤에** 그대로 두어야 합니다 — 이 값은 법적
599
+ 확인의 기록입니다.
600
+
601
+ 정책 필드 조합이 본문과 어긋나면 저장 단계에서 `POLICY_VALIDATION_FAILED` 로
602
+ 막힙니다. 예외의 `errors["reasons"]` 에 사유가 한국어로 담기니 그대로 사용자에게
603
+ 보여 주면 됩니다. 여기서 걸리는 문안은 **카카오 심사에서도 거의 반려**되므로,
604
+ 며칠 기다렸다 반려당하는 것보다 즉시 아는 편이 낫습니다.
605
+
606
+ ```python
607
+ client.notice_templates.list(kakao_sender_key=kakao_sender_key, inspection_status="APR")
608
+ client.notice_templates.show(template_code)
609
+ client.notice_templates.update(template_code, template_content="...") # 본문이 바뀌면 재검수 필요
610
+ client.notice_templates.cancel_inspection(template_code)
611
+ client.notice_templates.cancel_approval(template_code)
612
+ client.notice_templates.release(template_code) # 휴면 해제
613
+ client.notice_templates.delete(template_code) # sendgo 목록에서만 삭제된다
614
+ client.notice_templates.categories()
615
+ ```
616
+
617
+ 이미지 템플릿은 `image=` 로 파일을 넘기면 multipart 로 나갑니다.
618
+
619
+ ```python
620
+ with open("banner.jpg", "rb") as f:
621
+ client.notice_templates.create(
622
+ kakao_sender_key=kakao_sender_key,
623
+ template_name="이벤트 안내",
624
+ template_emphasize_type="IMAGE",
625
+ image=("banner.jpg", f, "image/jpeg"),
626
+ # ... 나머지 필드 동일
627
+ )
628
+ ```
629
+
630
+ > **삭제 동작이 채널마다 다릅니다.** 알림톡 템플릿은 카카오에 삭제 API 가 없어
631
+ > sendgo 목록에서만 빠지고 동기화하면 되살아납니다. 브랜드메시지 템플릿은
632
+ > 카카오 쪽에서도 실제로 삭제됩니다.
633
+
634
+ ### 브랜드메시지 템플릿
635
+
636
+ ```python
637
+ created = client.brand_templates.create(
638
+ kakao_sender_key=kakao_sender_key,
639
+ template_name="여름 세일 안내",
640
+ template_type="FI", # FT/FI/FW/FL/FC/FM/FP/FA — 서버가 chatBubbleType 으로 변환
641
+ template_content="여름 세일이 시작되었습니다.",
642
+ image_url="https://mud-kage.kakao.com/....jpg",
643
+ )
644
+
645
+ # 동보 발송(targeting="F")에는 변수가 없는 템플릿만 쓸 수 있다
646
+ created["data"]["template"]["containsVariables"]
647
+
648
+ client.brand_templates.list(kakao_sender_key=kakao_sender_key)
649
+ client.brand_templates.sync(template_code)
650
+ client.brand_templates.import_from_sender(kakao_sender_key) # 카카오에 있는 템플릿 가져오기
651
+ client.brand_templates.delete(template_code) # 카카오에서도 삭제된다
652
+ ```
653
+
654
+ ### 발신번호 등록 신청
655
+
656
+ ```python
657
+ # 계정 종류에 맞는 유형과 유형별 필수 서류
658
+ client.sender_registration.number_types()
659
+
660
+ # 형식·중복 미리 확인
661
+ check = client.sender_registration.validate("02-1234-5678", "team_main")
662
+
663
+ with open("csu.pdf", "rb") as f:
664
+ created = client.sender_registration.create(
665
+ sender_alias="고객센터 대표번호",
666
+ sender_number_type="team_main", # personal_other / team_main / team_other_company
667
+ phone_e164="02-1234-5678",
668
+ files={"csuCertificate": ("csu.pdf", f, "application/pdf")},
669
+ # check["data"]["duplicationReasonRequired"] 가 True 면 필수
670
+ # duplication_reason="부서별 분리 운영",
671
+ )
672
+
673
+ created["data"]["sender"]["status"] # PENDING — 운영자 승인 후 SUCCESS
674
+
675
+ client.sender_registration.list()
676
+ client.sender_registration.update(sender_key, sender_alias="새 이름")
677
+ client.sender_registration.delete(sender_key)
678
+ ```
679
+
680
+ **휴대폰 유형도 API 로 접수할 수 있습니다.** 콘솔의 PASS 본인인증 대신
681
+ 신분증 사본(`identityDocument`)을 첨부하면 sendgo 운영자가 직접 확인합니다.
682
+ 이 경로로 접수된 건은 응답의 `identityVerificationMethod` 가 `document` 이고
683
+ **자동 승인되지 않습니다** — 운영자 확인 전까지 `PENDING` 입니다.
684
+
685
+ 유형별 필수 서류는 `numberTypes()` 응답의 `requiredDocuments` 로 확인하세요.
686
+ 반려되면 `rejectionReason` 에 사유가 담깁니다.
687
+
688
+ ### 문자 템플릿
689
+
690
+ ```python
691
+ client.message_templates.create(
692
+ message_tran_type="LMS",
693
+ message_tran_subject="주문 안내", # LMS·MMS 는 필수
694
+ message_tran_msg="주문이 접수되었습니다.",
695
+ )
696
+
697
+ client.message_templates.list(message_type="LMS")
698
+ client.message_templates.update(template_key, message_tran_msg="...")
699
+ client.message_templates.delete(template_key)
700
+ ```
701
+
702
+ ### 이벤트 웹훅 — 심사 결과를 밀어 받기
703
+
704
+ ```python
705
+ created = client.webhook.subscribe("https://reseller.example.com/hooks/sendgo")
706
+
707
+ # 시크릿은 이 응답에서 한 번만 나온다. 즉시 저장한다.
708
+ secret = created["data"]["secret"]
709
+
710
+ client.webhook.show() # 구독 설정 + 마지막 전송 결과
711
+ client.webhook.test() # 배선 확인
712
+ client.webhook.unsubscribe()
713
+ ```
714
+
715
+ 받는 쪽에서는 **원본 바이트**로 서명을 검증합니다.
716
+
717
+ ```python
718
+ from sendgo import verify_signature
719
+
720
+ # Django: request.body / FastAPI: await request.body()
721
+ if not verify_signature(request.body, request.headers["X-Sendgo-Signature"], secret):
722
+ return HttpResponse(status=401)
723
+
724
+ payload = json.loads(request.body)
725
+ # payload["event"] — sender.status_changed / notice_template.inspection_status_changed / ...
726
+ ```
727
+
728
+ 이벤트 목록은 `sendgo.WEBHOOK_EVENTS` 로 확인할 수 있습니다.
729
+
730
+ ### 카카오 이미지 업로드
731
+
732
+ 브랜드메시지 템플릿의 `imageUrl` 은 **카카오가 호스팅하는 URL** 이어야 합니다.
733
+
734
+ ```python
735
+ with open("banner.jpg", "rb") as f:
736
+ uploaded = client.kakao_images.upload("default", ("banner.jpg", f, "image/jpeg"))
737
+
738
+ client.brand_templates.create(
739
+ kakao_sender_key=kakao_sender_key,
740
+ template_name="여름 세일 안내",
741
+ template_type="FI",
742
+ image_url=uploaded["data"]["imageUrl"],
743
+ )
744
+
745
+ client.kakao_images.upload_many("carousel_feed", [slide1, slide2, slide3])
746
+ client.kakao_images.types() # 유형별 필드·최대 개수
747
+ ```
748
+
749
+ ### 수신거부(080) 동기화
750
+
751
+ ```python
752
+ # 증분만 가져간다. 하루 한 번이면 충분하다.
753
+ client.rejected_numbers.list(since="2026-09-01", count=500)
754
+ ```
755
+
756
+ ---
757
+
504
758
  ## 변경 사항
505
759
 
760
+ ### 1.3.0 (2026-09-11)
761
+
762
+ - **관리 API 추가** — 콘솔에서만 되던 등록·심사를 코드로 처리합니다.
763
+ `client.kakao_senders`(채널 인증·등록·동기화, 브랜드메시지 M/N 신청),
764
+ `client.notice_templates`(알림톡 템플릿 CRUD·검수 요청·승인 취소·휴면 해제),
765
+ `client.brand_templates`(브랜드메시지 템플릿 CRUD·동기화·가져오기),
766
+ `client.sender_registration`(발신번호 등록 신청·중복 확인·유형 안내),
767
+ `client.message_templates`(문자 상용구 템플릿 CRUD).
768
+ - `HttpClient` 에 `put()`·`patch()`·`post_multipart()` 를 추가했습니다.
769
+ 서류 첨부와 이미지 템플릿은 JSON 으로 보낼 수 없습니다.
770
+ - snake_case 인자를 camelCase 로 변환하는 `_payload.camelize()` 를 도입해,
771
+ 선택 필드를 그냥 키워드 인자로 이어 쓸 수 있게 했습니다.
772
+ - **휴대폰 발신번호도 API 로 접수됩니다.** 콘솔의 PASS 본인인증 대신
773
+ `identityDocument`(신분증 사본)를 첨부하면 sendgo 운영자가 확인합니다.
774
+ 이 경로는 자동 승인되지 않고 항상 `PENDING` 으로 시작합니다.
775
+ - **이벤트 웹훅** 추가 — 발신번호 승인, 알림톡 검수 결과, 채널 차단,
776
+ 브랜드메시지 타겟팅 결과를 구독해 받습니다. 서명은 받은 원본 바이트로
777
+ 검증합니다(SDK 에 검증 헬퍼 포함).
778
+ - **카카오 이미지 업로드** 추가 — 브랜드메시지 템플릿의 `imageUrl` 은 카카오가
779
+ 호스팅하는 URL 이어야 하는데, 그 URL 을 얻는 길이 콘솔에만 있었습니다.
780
+ - **수신거부(080) 조회** 추가 — 자기 DB 의 수신 상태를 맞출 수 있습니다.
781
+
506
782
  ### 1.2.1 (2026-08-14)
507
783
 
508
784
  - 레지스트리 목록에 노출되는 패키지 설명에서 친구톡을 브랜드메시지로 교체했습니다.
@@ -532,3 +808,30 @@ MIT License © 2026 [Sendgo](https://sendgo.io)
532
808
  ---
533
809
 
534
810
  *키워드: 카카오 알림톡 Python, 카카오 친구톡 Django, SMS 발송 FastAPI, 알림톡 SDK pip, Python 카카오 API 연동, Django 문자 발송, FastAPI 알림톡, Celery 알림톡 비동기, Sendgo Python SDK*
811
+
812
+ ## 계정·조직·API 키 관리 (1.4.0)
813
+
814
+ 발송용 `accessKey`/`secretKey`가 없는 단계에서 사용하는 **별도 계정 클라이언트**입니다.
815
+ 콘솔에서 발급받은 에이전트 토큰(`SENDGO_AGENT_TOKEN`)으로 `/api/v2/account`를 호출합니다.
816
+ 계정 조회에는 `account:read`, 키·허용 IP 변경에는 `keys:write` 권한이 필요합니다.
817
+ 토큰 만료나 권한 부족(401/403)은 그대로 예외로 반환하며 자동 갱신·재시도하지 않습니다.
818
+
819
+ 조직 선택은 서버에 저장되는 **사용자 계정의 현재 조직**을 바꿉니다. 같은 사용자로
820
+ 여러 조직의 설정을 동시에 변경하지 마세요. 개인 계정으로 돌아가려면 조직 ID에
821
+ `null`(Python `None`, Ruby `nil`, Go `nil`) 또는 `personal`을 전달합니다.
822
+ 키 발급 응답의 `data.apiKey.secretKey`는 한 번만 반환되므로 서버의 비밀 저장소에 보관하세요.
823
+ 허용 IP가 하나라도 등록되면 목록 밖의 IP는 차단됩니다.
824
+ 에이전트 토큰과 키는 브라우저·모바일 앱에 포함하거나 응답·로그에 출력하지 않습니다.
825
+
826
+ ```python
827
+ import os
828
+ from sendgo import AccountClient
829
+ account = AccountClient(agent_token=os.environ['SENDGO_AGENT_TOKEN'])
830
+ result = account.me()
831
+ account.select_organization('team-uuid')
832
+ issued = account.create_api_key({'name': '서버 연동'})
833
+ ```
834
+
835
+ 지원 메서드: `me`, `organizations`, `select_organization`, `api_keys`, `create_api_key`, `api_key`, `update_api_key`, `delete_api_key`, `issue_token`, `allowed_ips`, `add_allowed_ip`, `delete_allowed_ip`.
836
+
837
+ 키 생성 인자는 `name`, 선택적 `ipAddresses: [{ip, description}]`이며, 허용 IP 추가 인자는 `ip`, 선택적 `description`입니다. 키·IP 식별자는 응답의 `id`(UUID)를 사용합니다.
@@ -474,8 +474,284 @@ sendgo.short_url.deactivate(code) # 리다이렉트만 중지, 통계는 남
474
474
  `stats` 는 일별 추이(`daily`)와 디바이스(`byDevice`)·유입경로(`byReferer`)·국가(`byCountry`)별
475
475
  분해를 반환합니다. 일별 추이는 사전 집계 표에서 읽으므로 클릭이 많아도 응답 시간이 일정합니다.
476
476
 
477
+ ## 관리 API — 채널·템플릿·발신번호 등록 (v2 전용)
478
+
479
+ 발송은 처음부터 API였지만 **등록과 심사는 콘솔에서만** 되던 것들이 있었습니다.
480
+ 1.3.0 부터 그 작업도 코드로 처리합니다.
481
+
482
+ | 서비스 | 하는 일 | 계정 |
483
+ | --- | --- | --- |
484
+ | `client.kakao_senders` | 카카오 채널 인증·등록·동기화, 브랜드메시지 M/N 신청 | 기업 |
485
+ | `client.notice_templates` | 알림톡 템플릿 CRUD, 검수 요청·취소, 승인 취소, 휴면 해제 | 기업 |
486
+ | `client.brand_templates` | 브랜드메시지(구 친구톡) 템플릿 CRUD, 동기화, 가져오기 | 기업 |
487
+ | `client.sender_registration` | 발신번호 등록 신청, 중복 확인, 유형 안내 | 개인·기업 |
488
+ | `client.message_templates` | 문자 상용구 템플릿 CRUD | 개인·기업 |
489
+ | `client.kakao_images` | 카카오 이미지 업로드 — 템플릿용 URL 발급 | 기업 |
490
+ | `client.rejected_numbers` | 수신거부(080) 번호 조회 | 개인·기업 |
491
+ | `client.webhook` | 이벤트 웹훅 구독 — 심사 결과 수신 | 개인·기업 |
492
+
493
+ > **sendgo.io 콘솔에 들어올 일이 없습니다.** 고객의 채널·발신번호·템플릿을
494
+ > 여러분 화면만으로 끝까지 처리할 수 있습니다. 휴대폰 발신번호는 콘솔의 PASS
495
+ > 본인인증 대신 **신분증 사본(`identityDocument`)을 받아 sendgo 운영자가 대신
496
+ > 심사**합니다.
497
+ >
498
+ > 사람이 개입하는 지점은 **카카오 채널 인증번호 하나**뿐이고, 그마저도
499
+ > 여러분 화면에서 입력받으면 됩니다 — 카카오가 관리자 휴대폰으로 직접 보내는
500
+ > 확인이라 없앨 수 없습니다.
501
+ >
502
+ > 심사가 붙는 것들은 **비동기**입니다. 등록 호출이 성공했다는 건 "접수됐다"는
503
+ > 뜻이지 "쓸 수 있다"는 뜻이 아닙니다 — 웹훅을 구독해 결과를 받으세요.
504
+
505
+ 인자는 snake_case 로 씁니다 — SDK 가 camelCase 로 변환해 보냅니다.
506
+
507
+ ### 카카오 채널 등록
508
+
509
+ ```python
510
+ # 1단계 — 카카오가 관리자 휴대폰으로 인증번호를 SMS 발송한다 (응답에 번호는 없다)
511
+ client.kakao_senders.request_token("@my-channel", "01012345678")
512
+
513
+ # 2단계 — 사람이 받은 인증번호로 발신프로필 생성
514
+ created = client.kakao_senders.create(
515
+ token="123456",
516
+ yellow_id="@my-channel",
517
+ phone_number="01012345678",
518
+ category_code="001001", # categories() 로 조회
519
+ )
520
+
521
+ kakao_sender_key = created["data"]["sender"]["kakaoSenderKey"]
522
+
523
+ client.kakao_senders.categories()
524
+ client.kakao_senders.list()
525
+ client.kakao_senders.sync() # 전체 상태 동기화 (하루 한 번 권장)
526
+ client.kakao_senders.sync(kakao_sender_key) # 단건
527
+ ```
528
+
529
+ 채널이 카카오 쪽에서 차단되면 발송이 조용히 실패하기 시작합니다. `sync()` 를
530
+ 주기적으로 돌리고 `block: True` 인 채널을 감시하세요.
531
+
532
+ ### 알림톡 템플릿 등록과 검수
533
+
534
+ ```python
535
+ created = client.notice_templates.create(
536
+ kakao_sender_key=kakao_sender_key,
537
+ template_name="주문 접수 안내",
538
+ template_content="#{name}님, 주문 #{orderNo}이 접수되었습니다.",
539
+ template_message_type="BA", # BA 기본형 / EX 부가정보형 / AD 채널추가형 / MI 복합형
540
+ template_emphasize_type="NONE", # NONE / TEXT / ITEM_LIST / IMAGE
541
+ category_code="001001",
542
+
543
+ # sendgo 자체 정책 게이트 — 카카오 심사와 별개다
544
+ message_purpose="order_delivery",
545
+ legal_basis="transaction",
546
+ benefit_origin="none",
547
+ expiry_type="none",
548
+
549
+ # 선택 필드는 그냥 이어서 쓰면 된다 (snake_case → camelCase 자동 변환)
550
+ buttons=[{"name": "주문 조회", "linkType": "WL", "linkMo": "https://example.com/orders"}],
551
+ )
552
+
553
+ template_code = created["data"]["template"]["templateCode"]
554
+
555
+ # 검수 요청 — 증빙이 필요하면 파일도 붙인다 (첨부가 있으면 comment 필수)
556
+ client.notice_templates.request_inspection(template_code)
557
+
558
+ with open("proof.png", "rb") as f:
559
+ client.notice_templates.request_inspection(
560
+ template_code,
561
+ comment="주문 확인 화면 첨부",
562
+ attachments=[("proof.png", f, "image/png")],
563
+ )
564
+
565
+ # 결과는 비동기다. 웹훅이 없으므로 폴링한다
566
+ synced = client.notice_templates.sync(template_code)
567
+ synced["data"]["template"]["inspectionStatus"] # REG → REQ → APR / REJ
568
+ ```
569
+
570
+ `opt_in_review_confirmed` · `cta_clear_confirmed` · `policy_confirmed` 는 기본값이
571
+ `True` 지만, **내용을 실제로 검토한 뒤에** 그대로 두어야 합니다 — 이 값은 법적
572
+ 확인의 기록입니다.
573
+
574
+ 정책 필드 조합이 본문과 어긋나면 저장 단계에서 `POLICY_VALIDATION_FAILED` 로
575
+ 막힙니다. 예외의 `errors["reasons"]` 에 사유가 한국어로 담기니 그대로 사용자에게
576
+ 보여 주면 됩니다. 여기서 걸리는 문안은 **카카오 심사에서도 거의 반려**되므로,
577
+ 며칠 기다렸다 반려당하는 것보다 즉시 아는 편이 낫습니다.
578
+
579
+ ```python
580
+ client.notice_templates.list(kakao_sender_key=kakao_sender_key, inspection_status="APR")
581
+ client.notice_templates.show(template_code)
582
+ client.notice_templates.update(template_code, template_content="...") # 본문이 바뀌면 재검수 필요
583
+ client.notice_templates.cancel_inspection(template_code)
584
+ client.notice_templates.cancel_approval(template_code)
585
+ client.notice_templates.release(template_code) # 휴면 해제
586
+ client.notice_templates.delete(template_code) # sendgo 목록에서만 삭제된다
587
+ client.notice_templates.categories()
588
+ ```
589
+
590
+ 이미지 템플릿은 `image=` 로 파일을 넘기면 multipart 로 나갑니다.
591
+
592
+ ```python
593
+ with open("banner.jpg", "rb") as f:
594
+ client.notice_templates.create(
595
+ kakao_sender_key=kakao_sender_key,
596
+ template_name="이벤트 안내",
597
+ template_emphasize_type="IMAGE",
598
+ image=("banner.jpg", f, "image/jpeg"),
599
+ # ... 나머지 필드 동일
600
+ )
601
+ ```
602
+
603
+ > **삭제 동작이 채널마다 다릅니다.** 알림톡 템플릿은 카카오에 삭제 API 가 없어
604
+ > sendgo 목록에서만 빠지고 동기화하면 되살아납니다. 브랜드메시지 템플릿은
605
+ > 카카오 쪽에서도 실제로 삭제됩니다.
606
+
607
+ ### 브랜드메시지 템플릿
608
+
609
+ ```python
610
+ created = client.brand_templates.create(
611
+ kakao_sender_key=kakao_sender_key,
612
+ template_name="여름 세일 안내",
613
+ template_type="FI", # FT/FI/FW/FL/FC/FM/FP/FA — 서버가 chatBubbleType 으로 변환
614
+ template_content="여름 세일이 시작되었습니다.",
615
+ image_url="https://mud-kage.kakao.com/....jpg",
616
+ )
617
+
618
+ # 동보 발송(targeting="F")에는 변수가 없는 템플릿만 쓸 수 있다
619
+ created["data"]["template"]["containsVariables"]
620
+
621
+ client.brand_templates.list(kakao_sender_key=kakao_sender_key)
622
+ client.brand_templates.sync(template_code)
623
+ client.brand_templates.import_from_sender(kakao_sender_key) # 카카오에 있는 템플릿 가져오기
624
+ client.brand_templates.delete(template_code) # 카카오에서도 삭제된다
625
+ ```
626
+
627
+ ### 발신번호 등록 신청
628
+
629
+ ```python
630
+ # 계정 종류에 맞는 유형과 유형별 필수 서류
631
+ client.sender_registration.number_types()
632
+
633
+ # 형식·중복 미리 확인
634
+ check = client.sender_registration.validate("02-1234-5678", "team_main")
635
+
636
+ with open("csu.pdf", "rb") as f:
637
+ created = client.sender_registration.create(
638
+ sender_alias="고객센터 대표번호",
639
+ sender_number_type="team_main", # personal_other / team_main / team_other_company
640
+ phone_e164="02-1234-5678",
641
+ files={"csuCertificate": ("csu.pdf", f, "application/pdf")},
642
+ # check["data"]["duplicationReasonRequired"] 가 True 면 필수
643
+ # duplication_reason="부서별 분리 운영",
644
+ )
645
+
646
+ created["data"]["sender"]["status"] # PENDING — 운영자 승인 후 SUCCESS
647
+
648
+ client.sender_registration.list()
649
+ client.sender_registration.update(sender_key, sender_alias="새 이름")
650
+ client.sender_registration.delete(sender_key)
651
+ ```
652
+
653
+ **휴대폰 유형도 API 로 접수할 수 있습니다.** 콘솔의 PASS 본인인증 대신
654
+ 신분증 사본(`identityDocument`)을 첨부하면 sendgo 운영자가 직접 확인합니다.
655
+ 이 경로로 접수된 건은 응답의 `identityVerificationMethod` 가 `document` 이고
656
+ **자동 승인되지 않습니다** — 운영자 확인 전까지 `PENDING` 입니다.
657
+
658
+ 유형별 필수 서류는 `numberTypes()` 응답의 `requiredDocuments` 로 확인하세요.
659
+ 반려되면 `rejectionReason` 에 사유가 담깁니다.
660
+
661
+ ### 문자 템플릿
662
+
663
+ ```python
664
+ client.message_templates.create(
665
+ message_tran_type="LMS",
666
+ message_tran_subject="주문 안내", # LMS·MMS 는 필수
667
+ message_tran_msg="주문이 접수되었습니다.",
668
+ )
669
+
670
+ client.message_templates.list(message_type="LMS")
671
+ client.message_templates.update(template_key, message_tran_msg="...")
672
+ client.message_templates.delete(template_key)
673
+ ```
674
+
675
+ ### 이벤트 웹훅 — 심사 결과를 밀어 받기
676
+
677
+ ```python
678
+ created = client.webhook.subscribe("https://reseller.example.com/hooks/sendgo")
679
+
680
+ # 시크릿은 이 응답에서 한 번만 나온다. 즉시 저장한다.
681
+ secret = created["data"]["secret"]
682
+
683
+ client.webhook.show() # 구독 설정 + 마지막 전송 결과
684
+ client.webhook.test() # 배선 확인
685
+ client.webhook.unsubscribe()
686
+ ```
687
+
688
+ 받는 쪽에서는 **원본 바이트**로 서명을 검증합니다.
689
+
690
+ ```python
691
+ from sendgo import verify_signature
692
+
693
+ # Django: request.body / FastAPI: await request.body()
694
+ if not verify_signature(request.body, request.headers["X-Sendgo-Signature"], secret):
695
+ return HttpResponse(status=401)
696
+
697
+ payload = json.loads(request.body)
698
+ # payload["event"] — sender.status_changed / notice_template.inspection_status_changed / ...
699
+ ```
700
+
701
+ 이벤트 목록은 `sendgo.WEBHOOK_EVENTS` 로 확인할 수 있습니다.
702
+
703
+ ### 카카오 이미지 업로드
704
+
705
+ 브랜드메시지 템플릿의 `imageUrl` 은 **카카오가 호스팅하는 URL** 이어야 합니다.
706
+
707
+ ```python
708
+ with open("banner.jpg", "rb") as f:
709
+ uploaded = client.kakao_images.upload("default", ("banner.jpg", f, "image/jpeg"))
710
+
711
+ client.brand_templates.create(
712
+ kakao_sender_key=kakao_sender_key,
713
+ template_name="여름 세일 안내",
714
+ template_type="FI",
715
+ image_url=uploaded["data"]["imageUrl"],
716
+ )
717
+
718
+ client.kakao_images.upload_many("carousel_feed", [slide1, slide2, slide3])
719
+ client.kakao_images.types() # 유형별 필드·최대 개수
720
+ ```
721
+
722
+ ### 수신거부(080) 동기화
723
+
724
+ ```python
725
+ # 증분만 가져간다. 하루 한 번이면 충분하다.
726
+ client.rejected_numbers.list(since="2026-09-01", count=500)
727
+ ```
728
+
729
+ ---
730
+
477
731
  ## 변경 사항
478
732
 
733
+ ### 1.3.0 (2026-09-11)
734
+
735
+ - **관리 API 추가** — 콘솔에서만 되던 등록·심사를 코드로 처리합니다.
736
+ `client.kakao_senders`(채널 인증·등록·동기화, 브랜드메시지 M/N 신청),
737
+ `client.notice_templates`(알림톡 템플릿 CRUD·검수 요청·승인 취소·휴면 해제),
738
+ `client.brand_templates`(브랜드메시지 템플릿 CRUD·동기화·가져오기),
739
+ `client.sender_registration`(발신번호 등록 신청·중복 확인·유형 안내),
740
+ `client.message_templates`(문자 상용구 템플릿 CRUD).
741
+ - `HttpClient` 에 `put()`·`patch()`·`post_multipart()` 를 추가했습니다.
742
+ 서류 첨부와 이미지 템플릿은 JSON 으로 보낼 수 없습니다.
743
+ - snake_case 인자를 camelCase 로 변환하는 `_payload.camelize()` 를 도입해,
744
+ 선택 필드를 그냥 키워드 인자로 이어 쓸 수 있게 했습니다.
745
+ - **휴대폰 발신번호도 API 로 접수됩니다.** 콘솔의 PASS 본인인증 대신
746
+ `identityDocument`(신분증 사본)를 첨부하면 sendgo 운영자가 확인합니다.
747
+ 이 경로는 자동 승인되지 않고 항상 `PENDING` 으로 시작합니다.
748
+ - **이벤트 웹훅** 추가 — 발신번호 승인, 알림톡 검수 결과, 채널 차단,
749
+ 브랜드메시지 타겟팅 결과를 구독해 받습니다. 서명은 받은 원본 바이트로
750
+ 검증합니다(SDK 에 검증 헬퍼 포함).
751
+ - **카카오 이미지 업로드** 추가 — 브랜드메시지 템플릿의 `imageUrl` 은 카카오가
752
+ 호스팅하는 URL 이어야 하는데, 그 URL 을 얻는 길이 콘솔에만 있었습니다.
753
+ - **수신거부(080) 조회** 추가 — 자기 DB 의 수신 상태를 맞출 수 있습니다.
754
+
479
755
  ### 1.2.1 (2026-08-14)
480
756
 
481
757
  - 레지스트리 목록에 노출되는 패키지 설명에서 친구톡을 브랜드메시지로 교체했습니다.
@@ -505,3 +781,30 @@ MIT License © 2026 [Sendgo](https://sendgo.io)
505
781
  ---
506
782
 
507
783
  *키워드: 카카오 알림톡 Python, 카카오 친구톡 Django, SMS 발송 FastAPI, 알림톡 SDK pip, Python 카카오 API 연동, Django 문자 발송, FastAPI 알림톡, Celery 알림톡 비동기, Sendgo Python SDK*
784
+
785
+ ## 계정·조직·API 키 관리 (1.4.0)
786
+
787
+ 발송용 `accessKey`/`secretKey`가 없는 단계에서 사용하는 **별도 계정 클라이언트**입니다.
788
+ 콘솔에서 발급받은 에이전트 토큰(`SENDGO_AGENT_TOKEN`)으로 `/api/v2/account`를 호출합니다.
789
+ 계정 조회에는 `account:read`, 키·허용 IP 변경에는 `keys:write` 권한이 필요합니다.
790
+ 토큰 만료나 권한 부족(401/403)은 그대로 예외로 반환하며 자동 갱신·재시도하지 않습니다.
791
+
792
+ 조직 선택은 서버에 저장되는 **사용자 계정의 현재 조직**을 바꿉니다. 같은 사용자로
793
+ 여러 조직의 설정을 동시에 변경하지 마세요. 개인 계정으로 돌아가려면 조직 ID에
794
+ `null`(Python `None`, Ruby `nil`, Go `nil`) 또는 `personal`을 전달합니다.
795
+ 키 발급 응답의 `data.apiKey.secretKey`는 한 번만 반환되므로 서버의 비밀 저장소에 보관하세요.
796
+ 허용 IP가 하나라도 등록되면 목록 밖의 IP는 차단됩니다.
797
+ 에이전트 토큰과 키는 브라우저·모바일 앱에 포함하거나 응답·로그에 출력하지 않습니다.
798
+
799
+ ```python
800
+ import os
801
+ from sendgo import AccountClient
802
+ account = AccountClient(agent_token=os.environ['SENDGO_AGENT_TOKEN'])
803
+ result = account.me()
804
+ account.select_organization('team-uuid')
805
+ issued = account.create_api_key({'name': '서버 연동'})
806
+ ```
807
+
808
+ 지원 메서드: `me`, `organizations`, `select_organization`, `api_keys`, `create_api_key`, `api_key`, `update_api_key`, `delete_api_key`, `issue_token`, `allowed_ips`, `add_allowed_ip`, `delete_allowed_ip`.
809
+
810
+ 키 생성 인자는 `name`, 선택적 `ipAddresses: [{ip, description}]`이며, 허용 IP 추가 인자는 `ip`, 선택적 `description`입니다. 키·IP 식별자는 응답의 `id`(UUID)를 사용합니다.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "sendgo-python"
7
- version = "1.2.1"
7
+ version = "1.4.0"
8
8
  description = "Sendgo Python SDK — 카카오 알림톡/브랜드메시지, SMS/LMS/MMS"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}