sendgo 1.5.0 → 1.6.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 230cdcfeecd39f64c4518a917ee0758f158e3b92bda05e56f28caaa4f96edcb6
4
- data.tar.gz: f829e4cb4afbde0adf19e51ef13f8f0f94db6a52a402a22d24c991a5177cee8c
3
+ metadata.gz: 831e137eed970e07e04ea53d57b4c11e84da2dda290e64d5e2645e5550e8c526
4
+ data.tar.gz: 00f23753365220f337bf5a478f471295393d9453d858d0532c8f070cc5039372
5
5
  SHA512:
6
- metadata.gz: c7efbe668a9a7655dc47e0411b2c1ad4bede013459a0247731818182adcbacea52993f3e3d7f35f18bfd74fcdb5b56a404930b90210f3c4dd4d87602d220c96f
7
- data.tar.gz: e192b6145d4057babaa514061aa4f251c9705a398ae234a0e2ebb71202846ece3d5e6b9bb4d504dc5d6fd2249867119887d19b2f299e8329615b688bf72f28f1
6
+ metadata.gz: 5b29c3df4cadcc7dcd56a0e9620dc50545cd0e9696e5ebb1c74875226a3bb99ae16282027c624705ac9e84013e1ce656d6d89c2e5b4a6408eb0f01d9e917d5a2
7
+ data.tar.gz: 14bacdaffd0180a51adb7a08ea56103dbb2bbb5a3137970231aec2994d92df745f4371394ae0b03b769a657ccb6364aa27274efd0f468fefc4e57ba0af35eb7c
data/README.md CHANGED
@@ -153,7 +153,7 @@ client.friendtalk.send(
153
153
  > v2 전용입니다. 자유 본문 타입(`FT`/`FI`/`FW`)을 개별 수신자에게 보낼 때는 여전히 친구톡 API 를 쓰세요 — 이 엔드포인트는 그 조합에 `NOT_A_BRAND_MESSAGE` 를 반환합니다. 친구톡 요청은 카카오 측에서 브랜드메시지(자유형)로 대체 발송됩니다.
154
154
 
155
155
  ```ruby
156
- # 단건 발송 — 채널 친구 대상
156
+ # 단건 발송 — 지정 수신자 대상
157
157
  client.brand_message.send(
158
158
  targeting: "M",
159
159
  message_type: "FL",
@@ -674,7 +674,7 @@ MIT License © 2026 [Sendgo](https://sendgo.io)
674
674
 
675
675
  *키워드: 카카오 알림톡 Ruby, 카카오 친구톡 Rails, SMS 발송 Ruby, 알림톡 Ruby gem, Ruby 카카오 API 연동, Sendgo Ruby SDK, Rails 알림 발송*
676
676
 
677
- ## 계정·조직·API 키 관리 (1.5.0)
677
+ ## 계정·조직·API 키 관리 (1.6.0)
678
678
 
679
679
  발송용 `accessKey`/`secretKey`가 없는 단계에서 사용하는 **별도 계정 클라이언트**입니다.
680
680
  콘솔에서 발급받은 에이전트 토큰(`SENDGO_AGENT_TOKEN`)으로 `/api/v2/account`를 호출합니다.
@@ -699,7 +699,7 @@ issued = account.create_api_key({ name: '서버 연동' })
699
699
 
700
700
  키 생성 인자는 `name`, 선택적 `ipAddresses: [{ip, description}]`이며, 허용 IP 추가 인자는 `ip`, 선택적 `description`입니다. 키·IP 식별자는 응답의 `id`(UUID)를 사용합니다.
701
701
 
702
- ## 템플릿 폴더 (1.5.0)
702
+ ## 템플릿 폴더 (1.6.0)
703
703
 
704
704
  기업 계정의 발송용 API 키와 `apiVersion=v2` 설정으로 사용하는 서버 전용 API입니다.
705
705
  폴더는 알림톡·브랜드메시지가 공유하며, 목록의 `templateType`은 `notice` 또는 `brand`입니다.
@@ -724,3 +724,38 @@ client.template_folders.assign(
724
724
  client.notice_templates.list(folder_uuid: "none")
725
725
  # 템플릿 생성 시 folder_uuid: 폴더_UUID를 선택 인자로 전달합니다.
726
726
  ```
727
+
728
+ ## 1.6 이메일 API와 브랜드 타기팅
729
+
730
+ 이메일은 서버 전용이며 클라이언트 설정에서 API 버전을 `v2`로 지정합니다.
731
+ 브랜드 타기팅은 `M`(친구+비친구), `N`(비친구), `I`(친구교집합),
732
+ `O`(친구만), `F`(동보)를 지원합니다. `O`는 SDK에서 바꾸지 않고 서버로 전달합니다.
733
+
734
+ 이메일 발송·견적·조회·취소, 발신자·도메인 인증, 자격증명, 수신함·원본 EML,
735
+ 템플릿·주소록·연락처·발신자 프로필·캠페인 API를 지원합니다.
736
+ 일반 API는 기존 앱 Bearer 인증을 사용합니다. `EmailService`의
737
+ `withCredentials` / `with_credentials` / `WithCredentials` / Go `NewEmailWithCredentials`는
738
+ 별도로 발급된 이메일 credential ID/password를 사용하며 `/api/v2/email-service`로 호출합니다.
739
+ 이 인증은 auth, 발송·견적·조회·취소와 도메인 API에만 사용할 수 있습니다.
740
+ 내부 email-gateway, 공개 서명 수신거부 URL은 SDK 관리 API가 아닙니다.
741
+
742
+ 단건 `to`는 이메일 주소 하나입니다. `send`에는 `idempotency_key`를 반드시 지정하고
743
+ 같은 발송의 재시도에는 같은 키를 재사용하세요. 캠페인 발송에는 견적 응답의
744
+ `quote_hash`와 `idempotency_key`가 필요합니다. SDK가 키를 임의 생성하거나
745
+ 네트워크 오류·429·5xx를 자동 재시도하지 않습니다. Bearer 401만 최대 한 번
746
+ 갱신하며, 이메일 권한 거부 403 및 Basic 인증 실패는 그대로 반환합니다.
747
+ 마케팅 발송에는 `sender_name`, `sender_address`, `sender_contact`도 필요합니다.
748
+ 첨부는 `attachments: [{name, type, content}]`이며 content는 base64입니다.
749
+
750
+ 응답은 서버 JSON 객체 또는 배열을 그대로 반환하며 204는 null/nil/None입니다.
751
+ 원본 EML은 바이트(PHP/Ruby는 바이트 문자열)로 반환합니다. Java/Go/.NET/Dart는
752
+ 여러 응답 형태를 담는 Object/any/object/dynamic을 사용합니다(.NET JSON은 JsonElement).
753
+ 모든 관리 요청 본문은 서버 필드명(snake_case)을 그대로 사용합니다.
754
+
755
+ 아래 `client`는 기존 방식으로 구성한 v2 클라이언트입니다.
756
+
757
+ ```ruby
758
+ client.email.send({from: 'sender@example.com', to: 'recipient@example.com',
759
+ subject: '접수 안내', purpose: 'transactional', text: '접수되었습니다.',
760
+ idempotency_key: 'order-123-email'})
761
+ ```
@@ -8,7 +8,7 @@ module Sendgo
8
8
  # 친구톡과 달리 채널 친구가 아닌 수신자에게도 보낼 수 있고(+targeting: "N"+),
9
9
  # 수신 동의한 전체 채널 친구에게 동보 발송할 수 있다(+targeting: "F"+).
10
10
  #
11
- # @example 단건 발송 — 채널 친구 대상
11
+ # @example 단건 발송 — 지정 수신자 대상
12
12
  # client.brand_message.send(
13
13
  # targeting: "M",
14
14
  # message_type: "FL",
@@ -30,7 +30,7 @@ module Sendgo
30
30
 
31
31
  # 브랜드메시지를 전송한다.
32
32
  #
33
- # +targeting+ 이 "M"/"N"/"I" 이면 +contacts+ 가 필요하고 응답 data 에
33
+ # +targeting+ 이 "M"/"N"/"I"/"O" 이면 +contacts+ 가 필요하고 응답 data 에
34
34
  # 발송 건수(sentCount)가 담긴다. "F" 는 동보 발송이라 +contacts+ 없이
35
35
  # 접수 여부(accepted)만 반환되므로, 그 경우 #broadcast 가 더 명확하다.
36
36
  #
data/lib/sendgo/client.rb CHANGED
@@ -12,6 +12,7 @@ module Sendgo
12
12
  class Client
13
13
  # brand_message: 카카오 브랜드메시지 — 친구톡의 후속 채널. v2 전용.
14
14
  # friendtalk: @deprecated 2025-12-31 종료. brand_message 를 사용하세요.
15
+ attr_reader :email
15
16
  attr_reader :alimtalk, :friendtalk, :brand_message, :short_url, :sms
16
17
 
17
18
  # 관리 API (v2 전용) — 콘솔에서만 되던 등록·심사.
@@ -34,6 +35,7 @@ module Sendgo
34
35
  secret_key: secret_key, api_version: api_version)
35
36
  http = HttpClient.new(token_manager: token_manager, base_url: base_url, api_version: api_version)
36
37
 
38
+ @email = EmailService.new(token_manager: token_manager, base_url: base_url, api_version: api_version)
37
39
  @alimtalk = AlimtalkService.new(http: http, kakao_sender_key: kakao_sender_key, sms_sender_key: sms_sender_key)
38
40
  @friendtalk = FriendtalkService.new(http: http, kakao_sender_key: kakao_sender_key, sms_sender_key: sms_sender_key)
39
41
  @brand_message = BrandMessageService.new(http: http, kakao_sender_key: kakao_sender_key, sms_sender_key: sms_sender_key)
@@ -0,0 +1,246 @@
1
+ require "net/http"
2
+ require "json"
3
+ require "base64"
4
+ require "uri"
5
+ module Sendgo
6
+ # 서버 전용 이메일 API. 객체·배열·204(nil)·EML(String)을 보존합니다.
7
+ class EmailService
8
+ def initialize(token_manager: nil, base_url: "https://sendgo.io", api_version: "v2", credential: nil)
9
+ @tokens, @base_url, @api_version, @credential = token_manager, base_url.sub(%r{/$}, ""), api_version, credential
10
+ end
11
+ # 앱 키가 아닌 이메일 전용 credential ID/password입니다.
12
+ def self.with_credentials(id, password, base_url: "https://sendgo.io")
13
+ new(base_url: base_url, credential: Base64.strict_encode64("#{id}:#{password}"))
14
+ end
15
+
16
+ private
17
+ def encode(value)
18
+ URI.encode_www_form_component(value.to_s).gsub("+", "%20")
19
+ end
20
+ def request(method, path, body = nil, query = {}, raw = false, retrying = false)
21
+ raise ArgumentError, "이메일 API는 api_version=v2가 필요합니다." unless @api_version == "v2"
22
+ basic = !@credential.nil?
23
+ prefix = basic ? "email-service" : "email"
24
+ uri = URI("#{@base_url}/api/v2/#{prefix}/#{path}")
25
+ uri.query = URI.encode_www_form(query.reject { |_,v| v.nil? }) unless query.empty?
26
+ req = Net::HTTPGenericRequest.new(method, !body.nil?, true, uri.request_uri)
27
+ req["Authorization"] = basic ? "Basic #{@credential}" : "Bearer #{@tokens.get_token}"
28
+ req["Accept"] = "application/json"
29
+ unless body.nil?
30
+ req["Content-Type"] = "application/json"
31
+ req.body = JSON.generate(body)
32
+ end
33
+ resp = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https", read_timeout: 60, open_timeout: 10) { |h| h.request(req) }
34
+ ok = resp.is_a?(Net::HTTPSuccess)
35
+ return resp.body.to_s.b if ok && raw
36
+ begin
37
+ data = resp.body.nil? || resp.body.empty? ? nil : JSON.parse(resp.body)
38
+ rescue JSON::ParserError
39
+ raise if ok
40
+ data = {}
41
+ end
42
+ unless ok
43
+ error = data.is_a?(Hash) ? data : {}
44
+ if !retrying && !basic && resp.code.to_i == 401 && @tokens.should_refresh?(401, error["code"])
45
+ @tokens.invalidate
46
+ return request(method, path, body, query, raw, true)
47
+ end
48
+ raise SendgoError.from_response(resp.code.to_i, error, path, "v2")
49
+ end
50
+ data
51
+ end
52
+ public
53
+ # GET /email/account
54
+ def account(query = {})
55
+ request("GET", "account", nil, query, false)
56
+ end
57
+ # POST /email/request
58
+ def request_access(body = {})
59
+ request("POST", "request", body, {}, false)
60
+ end
61
+ # POST /email/credentials
62
+ def create_credential(body = {})
63
+ request("POST", "credentials", body, {}, false)
64
+ end
65
+ # GET /email/credentials
66
+ def credentials(query = {})
67
+ request("GET", "credentials", nil, query, false)
68
+ end
69
+ # DELETE /email/credentials/{id}
70
+ def revoke_credential(id)
71
+ request("DELETE", "credentials/#{encode(id)}", nil, {}, false)
72
+ end
73
+ # GET /email/domains
74
+ def domains(query = {})
75
+ request("GET", "domains", nil, query, false)
76
+ end
77
+ # POST /email/domains
78
+ def register_domain(body = {})
79
+ request("POST", "domains", body, {}, false)
80
+ end
81
+ # POST /email/domains/{id}/verify
82
+ def verify_domain(id, body = {})
83
+ request("POST", "domains/#{encode(id)}/verify", body, {}, false)
84
+ end
85
+ # GET /email/senders
86
+ def senders(query = {})
87
+ request("GET", "senders", nil, query, false)
88
+ end
89
+ # POST /email/senders
90
+ def request_sender(body = {})
91
+ request("POST", "senders", body, {}, false)
92
+ end
93
+ # POST /email/senders/{id}/verify
94
+ def verify_sender(id, body = {})
95
+ request("POST", "senders/#{encode(id)}/verify", body, {}, false)
96
+ end
97
+ # POST /email/recipients/verification
98
+ def request_recipient_verification(body = {})
99
+ request("POST", "recipients/verification", body, {}, false)
100
+ end
101
+ # POST /email/recipients/check
102
+ def check_recipients(body = {})
103
+ request("POST", "recipients/check", body, {}, false)
104
+ end
105
+ # GET /email/address-book
106
+ def address_book(query = {})
107
+ request("GET", "address-book", nil, query, false)
108
+ end
109
+ # GET /email/sender-profiles
110
+ def sender_profiles(query = {})
111
+ request("GET", "sender-profiles", nil, query, false)
112
+ end
113
+ # POST /email/sender-profiles
114
+ def create_sender_profile(body = {})
115
+ request("POST", "sender-profiles", body, {}, false)
116
+ end
117
+ # PATCH /email/sender-profiles/{id}
118
+ def update_sender_profile(id, body = {})
119
+ request("PATCH", "sender-profiles/#{encode(id)}", body, {}, false)
120
+ end
121
+ # DELETE /email/sender-profiles/{id}
122
+ def delete_sender_profile(id)
123
+ request("DELETE", "sender-profiles/#{encode(id)}", nil, {}, false)
124
+ end
125
+ # POST /email/address-book/import
126
+ def import_address_book(body = {})
127
+ request("POST", "address-book/import", body, {}, false)
128
+ end
129
+ # POST /email/address-book/preferences
130
+ def update_address_book_preferences(body = {})
131
+ request("POST", "address-book/preferences", body, {}, false)
132
+ end
133
+ # POST /email/send
134
+ def send(body)
135
+ request("POST", "send", body, {}, false)
136
+ end
137
+ # POST /email/quote
138
+ def quote(body = {})
139
+ request("POST", "quote", body, {}, false)
140
+ end
141
+ # GET /email/messages
142
+ def messages(query = {})
143
+ request("GET", "messages", nil, query, false)
144
+ end
145
+ # GET /email/messages/{id}
146
+ def message(id, query = {})
147
+ request("GET", "messages/#{encode(id)}", nil, query, false)
148
+ end
149
+ # POST /email/messages/{id}/cancel
150
+ def cancel_message(id, body = {})
151
+ request("POST", "messages/#{encode(id)}/cancel", body, {}, false)
152
+ end
153
+ # GET /email/inboxes
154
+ def inboxes(query = {})
155
+ request("GET", "inboxes", nil, query, false)
156
+ end
157
+ # POST /email/inboxes
158
+ def create_inbox(body = {})
159
+ request("POST", "inboxes", body, {}, false)
160
+ end
161
+ # PATCH /email/inboxes/{id}
162
+ def update_inbox(id, body = {})
163
+ request("PATCH", "inboxes/#{encode(id)}", body, {}, false)
164
+ end
165
+ # GET /email/inboxes/{id}/messages
166
+ def inbox_messages(id, query = {})
167
+ request("GET", "inboxes/#{encode(id)}/messages", nil, query, false)
168
+ end
169
+ # GET /email/inboxes/{id}/messages/{messageId}
170
+ def inbox_message(id, message_id, query = {})
171
+ request("GET", "inboxes/#{encode(id)}/messages/#{encode(message_id)}", nil, query, false)
172
+ end
173
+ # GET /email/inboxes/{id}/messages/{messageId}/raw
174
+ def raw_message(id, message_id, query = {})
175
+ request("GET", "inboxes/#{encode(id)}/messages/#{encode(message_id)}/raw", nil, query, true)
176
+ end
177
+ # DELETE /email/inboxes/{id}/messages/{messageId}
178
+ def delete_inbox_message(id, message_id)
179
+ request("DELETE", "inboxes/#{encode(id)}/messages/#{encode(message_id)}", nil, {}, false)
180
+ end
181
+ # GET /email/templates
182
+ def templates(query = {})
183
+ request("GET", "templates", nil, query, false)
184
+ end
185
+ # GET /email/templates/{id}
186
+ def template(id, query = {})
187
+ request("GET", "templates/#{encode(id)}", nil, query, false)
188
+ end
189
+ # POST /email/templates
190
+ def create_template(body = {})
191
+ request("POST", "templates", body, {}, false)
192
+ end
193
+ # PATCH /email/templates/{id}
194
+ def update_template(id, body = {})
195
+ request("PATCH", "templates/#{encode(id)}", body, {}, false)
196
+ end
197
+ # DELETE /email/templates/{id}
198
+ def delete_template(id)
199
+ request("DELETE", "templates/#{encode(id)}", nil, {}, false)
200
+ end
201
+ # GET /email/contacts
202
+ def contacts(query = {})
203
+ request("GET", "contacts", nil, query, false)
204
+ end
205
+ # POST /email/contacts
206
+ def save_contact(body = {})
207
+ request("POST", "contacts", body, {}, false)
208
+ end
209
+ # POST /email/contacts/import
210
+ def import_contacts(body = {})
211
+ request("POST", "contacts/import", body, {}, false)
212
+ end
213
+ # POST /email/contacts/{id}/unsubscribe
214
+ def unsubscribe_contact(id, body = {})
215
+ request("POST", "contacts/#{encode(id)}/unsubscribe", body, {}, false)
216
+ end
217
+ # GET /email/campaigns
218
+ def campaigns(query = {})
219
+ request("GET", "campaigns", nil, query, false)
220
+ end
221
+ # POST /email/campaigns
222
+ def create_campaign(body = {})
223
+ request("POST", "campaigns", body, {}, false)
224
+ end
225
+ # GET /email/campaigns/{id}
226
+ def campaign(id, query = {})
227
+ request("GET", "campaigns/#{encode(id)}", nil, query, false)
228
+ end
229
+ # POST /email/campaigns/{id}/quote
230
+ def quote_campaign(id, body = {})
231
+ request("POST", "campaigns/#{encode(id)}/quote", body, {}, false)
232
+ end
233
+ # POST /email/campaigns/{id}/send
234
+ def send_campaign(id, body = {})
235
+ request("POST", "campaigns/#{encode(id)}/send", body, {}, false)
236
+ end
237
+ # POST /email/campaigns/{id}/cancel
238
+ def cancel_campaign(id, body = {})
239
+ request("POST", "campaigns/#{encode(id)}/cancel", body, {}, false)
240
+ end
241
+ # GET /email/auth
242
+ def auth(query = {})
243
+ request("GET", "auth", nil, query, false)
244
+ end
245
+ end
246
+ end
@@ -1,3 +1,3 @@
1
1
  module Sendgo
2
- VERSION = "1.5.0"
2
+ VERSION = "1.6.0"
3
3
  end
data/lib/sendgo.rb CHANGED
@@ -8,6 +8,7 @@ require_relative "sendgo/brand_message"
8
8
  require_relative "sendgo/short_url"
9
9
  require_relative "sendgo/sms"
10
10
  require_relative "sendgo/management"
11
+ require_relative "sendgo/email"
11
12
  require_relative "sendgo/client"
12
13
 
13
14
  # Sendgo Ruby SDK — 카카오 알림톡/친구톡, SMS/LMS/MMS
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sendgo
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sendgo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-09-23 00:00:00.000000000 Z
11
+ date: 2026-10-03 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Sendgo API를 Ruby에서 간편하게 사용하기 위한 공식 SDK
14
14
  email:
@@ -23,6 +23,7 @@ files:
23
23
  - lib/sendgo/alimtalk.rb
24
24
  - lib/sendgo/brand_message.rb
25
25
  - lib/sendgo/client.rb
26
+ - lib/sendgo/email.rb
26
27
  - lib/sendgo/error.rb
27
28
  - lib/sendgo/friendtalk.rb
28
29
  - lib/sendgo/http_client.rb