nafru-email 0.1.1

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.
@@ -0,0 +1,370 @@
1
+ # Nafru Email SDK tip imzaları. Girdi/yanıt alanları API'deki gibi snake_case; yanıtlar sembol anahtarlı Hash'tir.
2
+ module Nafru
3
+ module Email
4
+ VERSION: String
5
+ DEFAULT_BASE_URL: String
6
+ MAX_RESPONSE_BYTES: Integer
7
+ SDK_VERSION: String
8
+ SCOPES: Array[String]
9
+
10
+ # --- ortak
11
+ type list_response[T] = { object: String, data: Array[T], has_more: bool }
12
+ type list_query = { ?limit: Integer, ?after: String, ?before: String }
13
+ type deleted_response = { object: String, id: String, deleted: true }
14
+ type tag = { name: String, value: String }
15
+ type ref = { object: String, id: String }
16
+ type recipients = String | Array[String]
17
+
18
+ # --- e-postalar
19
+ type attachment = { ?content: String, ?path: String, ?filename: String, ?content_type: String, ?content_id: String }
20
+ type send_email_input = {
21
+ ?from: String, to: recipients, ?cc: recipients, ?bcc: recipients, ?reply_to: recipients, ?subject: String,
22
+ ?headers: Hash[String, String], ?tags: Array[tag], ?scheduled_at: String,
23
+ ?html: String, ?text: String, ?attachments: Array[attachment],
24
+ ?template: { id: String, ?variables: Hash[String | Symbol, String | Integer | Float] }
25
+ }
26
+ type batch_email_item = {
27
+ from: String, to: recipients, ?cc: recipients, ?bcc: recipients, ?reply_to: recipients, subject: String,
28
+ ?headers: Hash[String, String], ?tags: Array[tag], ?scheduled_at: String, ?html: String, ?text: String
29
+ }
30
+ type email_list_item = {
31
+ id: String, to: Array[String], from: String, created_at: String, subject: String, bcc: Array[String]?,
32
+ cc: Array[String]?, reply_to: Array[String]?, last_event: String, scheduled_at: String?, message_id: String?
33
+ }
34
+ type email_event = { id: String, type: String, created_at: String, detail: Hash[Symbol, untyped] }
35
+ type email = {
36
+ object: "email", id: String, to: Array[String], from: String, created_at: String, subject: String,
37
+ bcc: Array[String]?, cc: Array[String]?, reply_to: Array[String]?, last_event: String, scheduled_at: String?,
38
+ message_id: String?, html: String?, text: String?, tags: Array[tag], events: Array[email_event]
39
+ }
40
+ type list_emails_query = { ?limit: Integer, ?after: String, ?before: String, ?status: String }
41
+
42
+ # --- alan adları
43
+ type domain_record = { record: String, name: String, type: String, value: String, ttl: String, status: String, ?priority: Integer }
44
+ type domain = {
45
+ id: String, name: String, status: String, created_at: String, region: String,
46
+ capabilities: { sending: String, receiving: String }, open_tracking: bool, click_tracking: bool, tls: String,
47
+ tracking_subdomain: String?
48
+ }
49
+ type domain_detail = {
50
+ object: "domain", id: String, name: String, status: String, created_at: String, region: String,
51
+ capabilities: { sending: String, receiving: String }, open_tracking: bool, click_tracking: bool, tls: String,
52
+ tracking_subdomain: String?, records: Array[domain_record]
53
+ }
54
+ type create_domain_input = { name: String, ?region: String, ?open_tracking: bool, ?click_tracking: bool, ?tls: String, ?tracking_subdomain: String }
55
+ type update_domain_input = { ?open_tracking: bool, ?click_tracking: bool, ?tls: String, ?tracking_subdomain: String? }
56
+
57
+ # --- kişiler
58
+ type property_value = String | Integer | Float | nil
59
+ type contact = { id: String, email: String, first_name: String?, last_name: String?, unsubscribed: bool, locale: String?, created_at: String }
60
+ type contact_detail = {
61
+ object: "contact", id: String, email: String, first_name: String?, last_name: String?, unsubscribed: bool,
62
+ locale: String?, created_at: String, properties: Hash[Symbol, { type: String, value: String | Integer | Float }]
63
+ }
64
+ type topic_subscription = "opt_in" | "opt_out"
65
+ type create_contact_input = {
66
+ email: String, ?first_name: String?, ?last_name: String?, ?unsubscribed: bool, ?locale: String?,
67
+ ?properties: Hash[String | Symbol, property_value], ?segments: Array[{ id: String }],
68
+ ?topics: Array[{ id: String, subscription: topic_subscription }]
69
+ }
70
+ type update_contact_input = { ?first_name: String?, ?last_name: String?, ?unsubscribed: bool, ?locale: String?, ?properties: Hash[String | Symbol, property_value] }
71
+ type list_contacts_query = { ?limit: Integer, ?after: String, ?before: String, ?search: String }
72
+ type deleted_contact = { object: "contact", contact: String, deleted: true }
73
+ type contact_property = { id: String, key: String, type: String, fallback_value: property_value, created_at: String }
74
+ type create_contact_property_input = { key: String, type: "string" | "number", ?fallback_value: property_value }
75
+ type update_contact_property_input = { ?fallback_value: property_value }
76
+ type contact_topic = { id: String, name: String, description: String?, subscription: topic_subscription, status: String }
77
+ type update_contact_topics_input = { topics: Array[{ id: String, subscription: topic_subscription }] }
78
+
79
+ # --- segmentler, konular
80
+ type segment = { id: String, name: String, created_at: String, type: String, filter: Hash[Symbol, untyped]? }
81
+ type create_segment_input = { name: String, ?filter: Hash[String | Symbol, untyped] }
82
+ type segment_ref = { object: "segment", id: String, name: String }
83
+ type topic = {
84
+ id: String, name: String, description: String?, default_subscription: topic_subscription, visibility: String,
85
+ double_opt_in: bool, created_at: String
86
+ }
87
+ type create_topic_input = { name: String, default_subscription: topic_subscription, ?description: String?, ?visibility: String, ?double_opt_in: bool }
88
+ type update_topic_input = { ?name: String, ?description: String?, ?visibility: String, ?double_opt_in: bool }
89
+
90
+ # --- kampanyalar
91
+ # Tekrar kuralı; saat (`HH:MM`) çalışma alanının saat diliminde. weekly.days: 0 = pazar … 6 = cumartesi.
92
+ # Yanıtta bilinmeyen `type` gelebilir: Hash olarak olduğu gibi döner.
93
+ type broadcast_recurrence = { type: "weekly", days: Array[Integer], time: String }
94
+ | { type: "monthly", day: Integer, time: String }
95
+ | { type: "interval", every_days: Integer, time: String }
96
+ type broadcast_data_source_input = { type: "manual" }
97
+ | { type: "url", url: String, ?header_name: String?, ?header_value: String? }
98
+ | { type: "webhook", ?regenerate_secret: bool }
99
+ type broadcast_data_source = { type: "manual" }
100
+ | { type: "url", url: String, header_name: String?, has_header_value: bool }
101
+ | { type: "webhook", trigger_url: String, secret: String }
102
+ type broadcast_list_item = {
103
+ id: String, name: String, segment_id: String, audience_id: String, subject: String, status: String,
104
+ created_at: String, scheduled_at: String?, sent_at: String?, recurrence: broadcast_recurrence?,
105
+ trigger_state: ("active" | "paused")?, next_run_at: String?, parent_id: String?
106
+ }
107
+ type broadcast = {
108
+ object: "broadcast", id: String, name: String, segment_id: String, audience_id: String, subject: String, status: String,
109
+ created_at: String, scheduled_at: String?, sent_at: String?, recurrence: broadcast_recurrence?,
110
+ trigger_state: ("active" | "paused")?, next_run_at: String?, parent_id: String?,
111
+ from: String, reply_to: Array[String]?, preview_text: String?, topic_id: String?, template_id: String?, html: String?,
112
+ text: String?, open_tracking: bool, click_tracking: bool, finished_at: String?, failure_reason: String?,
113
+ data: Hash[Symbol, untyped]?, data_source: broadcast_data_source?
114
+ }
115
+ type create_broadcast_input = {
116
+ ?name: String, segment_id: String, ?topic_id: String, from: String, ?subject: String, ?reply_to: recipients,
117
+ ?html: String, ?text: String, ?template_id: String, ?preview_text: String?, ?send: bool, ?scheduled_at: String,
118
+ ?open_tracking: bool, ?click_tracking: bool, ?data: Hash[String | Symbol, untyped]?,
119
+ ?data_source: broadcast_data_source_input?, ?recurrence: broadcast_recurrence?
120
+ }
121
+ # `nil` alan açıkça temizler (JSON null); verilmeyen alana dokunulmaz.
122
+ type update_broadcast_input = {
123
+ ?name: String, ?segment_id: String, ?topic_id: String?, ?from: String, ?subject: String, ?reply_to: Array[String],
124
+ ?html: String, ?text: String, ?preview_text: String?, ?open_tracking: bool, ?click_tracking: bool,
125
+ ?data: Hash[String | Symbol, untyped]?, ?data_source: broadcast_data_source_input?, ?recurrence: broadcast_recurrence?
126
+ }
127
+ type send_broadcast_input = { ?scheduled_at: String, ?data: Hash[String | Symbol, untyped] }
128
+
129
+ # --- şablonlar ve marka
130
+ type template_brand_override = {
131
+ ?accent_color: String, ?text_color: String, ?backdrop_color: String, ?canvas_color: String, ?font_family: String,
132
+ ?link_color: String, ?content_width: Integer, ?show_header: bool, ?show_footer: bool
133
+ }
134
+ type template_brand = { mode: "inherit" | "custom" | "none", ?override: template_brand_override? }
135
+ type template_list_item = {
136
+ id: String, name: String, alias: String?, type: String, status: String, subject: String?, current_version: Integer?,
137
+ has_unpublished_changes: bool, published_at: String?, created_at: String, updated_at: String
138
+ }
139
+ type template_variable = { key: String, type: String, fallback_value: String? }
140
+ type template = Hash[Symbol, untyped] # template_list_item + object, preview_text, html, text, design, revision, variables: Array[template_variable], brand
141
+ type create_template_input = { name: String, ?alias: String, ?subject: String?, ?preview_text: String?, html: String, ?text: String?, ?brand: template_brand }
142
+ type update_template_input = {
143
+ ?revision: Integer, ?name: String, ?alias: String?, ?subject: String?, ?preview_text: String?, ?html: String,
144
+ ?text: String?, ?brand: template_brand
145
+ }
146
+ type template_saved = { object: "template", id: String, revision: Integer }
147
+ type template_published = { object: "template", id: String, version: Integer, version_id: String, revision: Integer }
148
+ type put_template_translation_input = { ?subject: String?, ?preview_text: String?, html: String, ?text: String? }
149
+ type template_translation = { locale: String, subject: String?, preview_text: String?, design_revision: Integer, updated_at: String }
150
+ type brand_social_link = { network: String, url: String }
151
+ type brand = Hash[Symbol, untyped] # object, configured, logo_*, accent_color, text_color, …, social_links: Array[brand_social_link]
152
+ type update_brand_input = Hash[Symbol | String, untyped] # tüm alanlar isteğe bağlı; gönderilmeyen "ayarlanmadı" olur
153
+
154
+ # --- webhook, anahtar, olay
155
+ type webhook = { id: String, endpoint: String, created_at: String, status: String, events: Array[String] }
156
+ type created_webhook = { object: "webhook", id: String, signing_secret: String }
157
+ type create_webhook_input = { endpoint: String, events: Array[String] }
158
+ type update_webhook_input = { ?endpoint: String, ?events: Array[String], ?status: "enabled" | "disabled" }
159
+ type api_key = { id: String, name: String, created_at: String, last_used_at: String?, permission: String, domain_id: String?, token_prefix: String }
160
+ type create_api_key_input = { name: String, ?permission: "full_access" | "sending_access", ?domain_id: String }
161
+ type event_properties = Hash[String | Symbol, String | Integer | Float | bool | nil]
162
+ type send_event_input = { name: String, contact: { email: String } | { id: String }, ?properties: event_properties, ?occurred_at: String, ?unsubscribed: bool }
163
+ type event_item = {
164
+ id: String, name: String, contact_id: String, email: String, properties: Hash[Symbol, untyped], source: String,
165
+ occurred_at: String, created_at: String
166
+ }
167
+ type list_events_query = { ?limit: Integer, ?after: String, ?before: String, ?name: String, ?contact_id: String, ?email: String }
168
+
169
+ # --- uygulama ("Nafru ile bağlan")
170
+ type connection_status = {
171
+ object: "connection", id: String, ready: bool, missing: Array[String], hints: Array[String], scopes: Array[String],
172
+ billing_mode: "customer_pays" | "app_pays", setup_url: String, workspace: { name: String }
173
+ }
174
+ type connection_senders = { domains: Array[{ id: String, name: String }], default_from: String? }
175
+ type connection_template = { key: String, name: String, source: "workspace" | "pack", customized_from_pack: bool }
176
+
177
+ # `exchange` sonucu; `inspect`/`to_s` yenileme belirtecini göstermez.
178
+ class ConnectionGrant < Struct[untyped]
179
+ attr_accessor connection_id: String
180
+ attr_accessor refresh_token: String
181
+ attr_accessor scopes: Array[String]
182
+ def initialize: (connection_id: String, refresh_token: String, scopes: Array[String]) -> void
183
+ end
184
+
185
+ class Error < StandardError
186
+ attr_reader status_code: Integer?
187
+ attr_reader name: String
188
+ attr_reader errors: Array[{ field: String, message: String, ?i18nTranslationKey: String }]?
189
+ attr_reader retry_after: Numeric?
190
+ def initialize: (String message, name: String, ?status_code: Integer?, ?errors: Array[Hash[Symbol, String]]?, ?retry_after: Numeric?) -> void
191
+ def to_h: () -> Hash[Symbol, untyped]
192
+ end
193
+
194
+ class Response < Struct[untyped]
195
+ attr_accessor status: Integer
196
+ attr_accessor headers: Hash[String, String]
197
+ attr_accessor body: String?
198
+ attr_accessor reason: String?
199
+ end
200
+
201
+ interface _Transport
202
+ def call: (method: String, url: String, headers: Hash[String, String], body: String?, timeout: Numeric) -> Response
203
+ end
204
+
205
+ def self.paginate: [T] (^(Hash[Symbol, untyped]) -> list_response[T], ?Hash[Symbol, untyped]) -> Enumerator[T, void]
206
+ def self.create_pkce: () -> { verifier: String, challenge: String }
207
+
208
+ module Webhook
209
+ MAX_SIGNATURES: Integer
210
+ def self.verify: (payload: String, headers: { ?id: String?, ?timestamp: String?, ?signature: String? }?, secret: String,
211
+ ?tolerance_seconds: Integer, ?now: Time | Numeric) -> bool
212
+ end
213
+
214
+ class Connect
215
+ def authorize_url: (redirect_uri: String, state: String, scopes: Array[String], ?login_hint: String?) -> { url: String, code_verifier: String }
216
+ def exchange: (code: String, code_verifier: String, redirect_uri: String) -> ConnectionGrant
217
+ def revoke: (refresh_token: String) -> nil
218
+ end
219
+
220
+ class Connection
221
+ attr_reader id: String
222
+ def status: () -> connection_status
223
+ def senders: () -> untyped # .list -> connection_senders
224
+ def templates: () -> untyped # .list -> list_response[connection_template]
225
+ def emails: () -> untyped # .send / .get / .list (Resources::Emails ile aynı imza)
226
+ end
227
+
228
+ module Resources
229
+ class Emails
230
+ def send: (send_email_input input, ?idempotency_key: String?) -> { id: String }
231
+ def batch: (Array[batch_email_item] input, ?idempotency_key: String?) -> { data: Array[{ id: String }] }
232
+ def get: (String id) -> email
233
+ def list: (?list_emails_query query) -> list_response[email_list_item]
234
+ def update: (String id, { scheduled_at: String } input) -> ref
235
+ def cancel: (String id) -> ref
236
+ end
237
+
238
+ class Domains
239
+ def create: (create_domain_input input) -> domain_detail
240
+ def get: (String id) -> domain_detail
241
+ def list: (?list_query query) -> list_response[domain]
242
+ def update: (String id, update_domain_input input) -> ref
243
+ def remove: (String id) -> deleted_response
244
+ def verify: (String id) -> ref
245
+ end
246
+
247
+ class Contacts
248
+ attr_reader segments: ContactSegments
249
+ attr_reader topics: ContactTopics
250
+ def create: (create_contact_input input) -> ref
251
+ def get: (String id_or_email) -> contact_detail
252
+ def list: (?list_contacts_query query) -> list_response[contact]
253
+ def update: (String id_or_email, update_contact_input input) -> ref
254
+ def remove: (String id_or_email) -> deleted_contact
255
+ end
256
+
257
+ class ContactSegments
258
+ def list: (String id_or_email, ?list_query query) -> list_response[segment]
259
+ def add: (String id_or_email, String segment_id) -> { id: String }
260
+ def remove: (String id_or_email, String segment_id) -> { id: String, deleted: true }
261
+ end
262
+
263
+ class ContactTopics
264
+ def list: (String id_or_email, ?list_query query) -> list_response[contact_topic]
265
+ def update: (String id_or_email, update_contact_topics_input input) -> ref
266
+ end
267
+
268
+ class ContactProperties
269
+ def create: (create_contact_property_input input) -> ref
270
+ def get: (String id) -> Hash[Symbol, untyped]
271
+ def list: (?list_query query) -> list_response[contact_property]
272
+ def update: (String id, update_contact_property_input input) -> ref
273
+ def remove: (String id) -> deleted_response
274
+ end
275
+
276
+ class Segments
277
+ def create: (create_segment_input input) -> segment_ref
278
+ def get: (String id) -> Hash[Symbol, untyped]
279
+ def list: (?list_query query) -> list_response[segment]
280
+ def remove: (String id) -> deleted_response
281
+ def contacts: (String id, ?{ ?limit: Integer, ?after: String } query) -> list_response[contact]
282
+ end
283
+
284
+ class Topics
285
+ def create: (create_topic_input input) -> ref
286
+ def get: (String id) -> Hash[Symbol, untyped]
287
+ def list: (?list_query query) -> list_response[topic]
288
+ def update: (String id, update_topic_input input) -> ref
289
+ def remove: (String id) -> deleted_response
290
+ end
291
+
292
+ class Broadcasts
293
+ def create: (create_broadcast_input input) -> ref
294
+ def get: (String id) -> broadcast
295
+ def list: (?list_query query) -> list_response[broadcast_list_item]
296
+ def update: (String id, update_broadcast_input input) -> ref
297
+ def remove: (String id) -> deleted_response
298
+ def send: (String id, ?send_broadcast_input input) -> ref
299
+ def cancel: (String id) -> ref
300
+ end
301
+
302
+ class Templates
303
+ def create: (create_template_input input) -> template_saved
304
+ def get: (String id_or_alias) -> template
305
+ def list: (?list_query query) -> list_response[template_list_item]
306
+ def update: (String id_or_alias, update_template_input input) -> template_saved
307
+ def remove: (String id_or_alias) -> deleted_response
308
+ def publish: (String id_or_alias) -> template_published
309
+ def duplicate: (String id_or_alias) -> ref
310
+ def put_translation: (String id_or_alias, String locale, put_template_translation_input input) -> template_translation
311
+ def remove_translation: (String id_or_alias, String locale) -> { object: String, locale: String, deleted: true }
312
+ end
313
+
314
+ class Brand
315
+ def get: () -> brand
316
+ def update: (update_brand_input input) -> brand
317
+ end
318
+
319
+ class Webhooks
320
+ def create: (create_webhook_input input) -> created_webhook
321
+ def get: (String id) -> Hash[Symbol, untyped]
322
+ def list: (?list_query query) -> list_response[webhook]
323
+ def update: (String id, update_webhook_input input) -> ref
324
+ def remove: (String id) -> deleted_response
325
+ end
326
+
327
+ class ApiKeys
328
+ def create: (create_api_key_input input) -> { id: String, token: String }
329
+ def list: (?list_query query) -> list_response[api_key]
330
+ def remove: (String id) -> deleted_response
331
+ end
332
+
333
+ class Events
334
+ def send: (send_event_input input, ?idempotency_key: String?) -> { id: String }
335
+ def batch: (Array[send_event_input] input, ?idempotency_key: String?) -> { data: Array[{ id: String }] }
336
+ def list: (?list_events_query query) -> list_response[event_item]
337
+ end
338
+ end
339
+ end
340
+ end
341
+
342
+ # API anahtarıyla istemci (Resend tarzı). `api_key` boşsa `NAFRU_API_KEY` okunur.
343
+ class NafruEmail
344
+ def initialize: (?String? api_key, ?base_url: String?, ?transport: Nafru::Email::_Transport?, ?max_retries: Integer,
345
+ ?timeout: Numeric, ?dev_outbox: String | false | nil) -> void
346
+ attr_reader emails: Nafru::Email::Resources::Emails
347
+ attr_reader domains: Nafru::Email::Resources::Domains
348
+ attr_reader contacts: Nafru::Email::Resources::Contacts
349
+ attr_reader contact_properties: Nafru::Email::Resources::ContactProperties
350
+ attr_reader segments: Nafru::Email::Resources::Segments
351
+ attr_reader topics: Nafru::Email::Resources::Topics
352
+ attr_reader broadcasts: Nafru::Email::Resources::Broadcasts
353
+ attr_reader templates: Nafru::Email::Resources::Templates
354
+ attr_reader brand: Nafru::Email::Resources::Brand
355
+ attr_reader webhooks: Nafru::Email::Resources::Webhooks
356
+ attr_reader api_keys: Nafru::Email::Resources::ApiKeys
357
+ attr_reader events: Nafru::Email::Resources::Events
358
+ end
359
+
360
+ # "Nafru ile bağlan" (OAuth + PKCE) fabrikası.
361
+ class NafruConnect
362
+ def initialize: (client_id: String, client_secret: String, ?base_url: String?, ?web_url: String?,
363
+ ?transport: Nafru::Email::_Transport?, ?max_retries: Integer, ?timeout: Numeric,
364
+ ?dev_outbox: String | false | nil) -> void
365
+ def authorize_url: (redirect_uri: String, state: String, scopes: Array[String], ?login_hint: String?) -> { url: String, code_verifier: String }
366
+ def exchange: (code: String, code_verifier: String, redirect_uri: String) -> Nafru::Email::ConnectionGrant
367
+ def revoke: (refresh_token: String) -> nil
368
+ def connection: (String connection_id, refresh_token: String, ?on_token_refresh: ^({ refresh_token: String }) -> void)
369
+ ?{ ({ refresh_token: String }) -> void } -> Nafru::Email::Connection
370
+ end
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nafru-email
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Nafru
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-11-10 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: 'Nafru Email Ruby SDK: send transactional and marketing email from Ruby
14
+ with a Resend-compatible email API. Contacts, broadcasts, templates, custom events,
15
+ webhook verification and Connect with Nafru (OAuth). No dependencies (net/http,
16
+ json, openssl).'
17
+ email:
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - LICENSE
23
+ - README.md
24
+ - lib/nafru_email.rb
25
+ - lib/nafru_email/app.rb
26
+ - lib/nafru_email/client.rb
27
+ - lib/nafru_email/dev_outbox.rb
28
+ - lib/nafru_email/error.rb
29
+ - lib/nafru_email/http.rb
30
+ - lib/nafru_email/pagination.rb
31
+ - lib/nafru_email/resources.rb
32
+ - lib/nafru_email/version.rb
33
+ - lib/nafru_email/webhook.rb
34
+ - sig/nafru_email.rbs
35
+ homepage: https://github.com/nafrucom/nafru-email
36
+ licenses:
37
+ - MIT
38
+ metadata:
39
+ homepage_uri: https://github.com/nafrucom/nafru-email
40
+ source_code_uri: https://github.com/nafrucom/nafru-email-sdks/tree/main/ruby
41
+ bug_tracker_uri: https://github.com/nafrucom/nafru-email-sdks/issues
42
+ rubygems_mfa_required: 'true'
43
+ post_install_message:
44
+ rdoc_options: []
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: '3.1'
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ requirements: []
58
+ rubygems_version: 3.5.22
59
+ signing_key:
60
+ specification_version: 4
61
+ summary: 'Official Ruby SDK for the Nafru Email API: send transactional email from
62
+ Ruby'
63
+ test_files: []