mailganer-client 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.
Files changed (4) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +122 -0
  3. data/lib/mailganer_client.rb +441 -0
  4. metadata +76 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 50c26307907b4bef90ebe1b4daa6ffd346d0f6004388bda060ec6a2e3a13d723
4
+ data.tar.gz: 5b0d78aebd7f2d8e4b4b01822de2152535cbf777b9142dba3188192642ca5ff7
5
+ SHA512:
6
+ metadata.gz: 8bf65344a758e33f61c58b91af7c91e2ce678be97504c07fe4151e8c5484376971b5a5966811d04603df18590359049718f14eb2e23945ced487e230ac48e5b1
7
+ data.tar.gz: 72ea6ddee8baefc7979edd9cf576bcb9d1a49052ec082ce4d0a68d3a646d3503eee377888f086415ff3a8b4ff45190e2b8302487967099038e4d704dd54bc752
data/README.md ADDED
@@ -0,0 +1,122 @@
1
+ # Mailganer Client
2
+
3
+ A simple Ruby client for the Mailganer API.\
4
+ Designed to make sending requests and integrating with the service
5
+ effortless.
6
+
7
+ ## 🚀 Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ``` ruby
12
+ gem "mailganer-client"
13
+ ```
14
+
15
+ Then install:
16
+
17
+ ``` bash
18
+ bundle install
19
+ ```
20
+
21
+ Or install it manually:
22
+
23
+ ``` bash
24
+ gem install mailganer-client
25
+ ```
26
+
27
+ ## 📦 Usage
28
+
29
+ ``` ruby
30
+ require "mailganer_client"
31
+
32
+ MailganerClient.configure do |config|
33
+ config.api_key: "your-smtp-api-key",
34
+ config.smtp_login: "xxx",
35
+ config.api_key_web_portal: "your-web-portal-api-key"
36
+ end
37
+
38
+ client = MailganerClient.new
39
+
40
+ response = client.send_email(
41
+ to: "user@example.com",
42
+ from: "info@mysite.com",
43
+ subject: "Hello!",
44
+ body: "Your message goes here"
45
+ )
46
+
47
+ puts response
48
+ ```
49
+
50
+ ``` ruby
51
+
52
+ response = client.get_fbl_report_by_date(date_from: "2020-01-01", date_to: "2026-01-01")
53
+
54
+ puts response
55
+ ```
56
+
57
+
58
+ ``` ruby
59
+
60
+ response = client.send_email_smtp_v1(
61
+ type: 'template',
62
+ to: "test@test.com",
63
+ subject: "subject",
64
+ template_id: "template_id from mailganer web portal",
65
+ params: {
66
+ name: 'Test name',
67
+ unsubscribeUrl: ''
68
+ },
69
+ from: "from@mysite.com",
70
+ name_from: "Sender name from@mysite.com"
71
+ )
72
+
73
+ puts response
74
+ ```
75
+
76
+
77
+ ``` ruby
78
+
79
+ #file_path = Rails.root.join('app', 'javascript', 'src', 'public', 'img', 'test-image.jpg')
80
+ #file_path = File.expand_path("test-image.jpg", __dir__)
81
+ require 'base64'
82
+
83
+ response = client.send_email_smtp_v1(
84
+ type: 'template',
85
+ to: "test@test.com",
86
+ subject: "subject",
87
+ template_id: "template_id from mailganer web portal",
88
+ params: {
89
+ name: 'Test name'
90
+ },
91
+ from: "from@mysite.com",
92
+ body: "Hello, {{name}}",
93
+ name_from: "Sender name from@mysite.com",
94
+ attach_files: [
95
+ {
96
+ name: File.basename("file_path"),
97
+ filebody: Base64.strict_encode64(File.read("file_path"))
98
+ }
99
+ ]
100
+ )
101
+
102
+ puts response
103
+ ```
104
+
105
+ ## ⚙️ Configuration
106
+
107
+ ``` ruby
108
+ MailganerClient.configure do |config|
109
+ config.api_key: "your-smtp-api-key",
110
+ config.smtp_login: "xxx",
111
+ config.api_key_web_portal: "your-web-portal-api-key"
112
+ end
113
+ ```
114
+
115
+ ```
116
+
117
+ ## 🏗 Development
118
+
119
+ ``` bash
120
+ git clone https://github.com/yetisamurai/mailganer_client.git
121
+ cd mailganer_client
122
+ ```
@@ -0,0 +1,441 @@
1
+ # frozen_string_literal: true
2
+ require "net/http"
3
+ require "uri"
4
+ require "json"
5
+
6
+ ##
7
+ # Client for interacting with the Mailganer API.
8
+ #
9
+ # Provides sending emails, bulk mailing, stop-list operations,
10
+ # statistics, domain management, and delivery status checks.
11
+ #
12
+ # @example Sending a simple email
13
+ # client = MailganerClient.new(
14
+ # api_key: "...",
15
+ # smtp_login: "...",
16
+ # api_key_web_portal: "..."
17
+ # )
18
+ # client.send_email(
19
+ # to: "user@example.com",
20
+ # subject: "Hello",
21
+ # body: "Email body",
22
+ # from: "sender@example.com"
23
+ #
24
+ class MailganerClient
25
+ class ApiError < StandardError
26
+ attr_reader :code, :body
27
+ end
28
+ class StopListError < ApiError; end
29
+ class DomainNotTrustedError < ApiError; end
30
+ class AuthorizationError < ApiError; end
31
+ class BadRequestError < ApiError; end
32
+
33
+
34
+ class << self
35
+ attr_accessor :configuration
36
+ end
37
+
38
+ def self.configure
39
+ self.configuration ||= Configuration.new
40
+ yield(configuration)
41
+ end
42
+
43
+ class Configuration
44
+ attr_accessor :api_key, :smtp_login, :api_key_web_portal, :host, :debug
45
+ end
46
+
47
+ ##
48
+ # Initializes API client
49
+ #
50
+ # @param api_key [String] SMTP API key for sending
51
+ # @param smtp_login [String] SMTP login
52
+ # @param api_key_web_portal [String] API key for web portal
53
+ # @param debug [Boolean] enable HTTP debug logging
54
+ # @param host [String] base API URL
55
+ #
56
+
57
+ def initialize(
58
+ api_key: MailganerClient.configuration&.api_key,
59
+ smtp_login: MailganerClient.configuration&.smtp_login,
60
+ api_key_web_portal: MailganerClient.configuration&.api_key_web_portal,
61
+ host: MailganerClient.configuration&.host || "https://api.samotpravil.ru/",
62
+ debug: MailganerClient.configuration&.debug || false
63
+ )
64
+ @api_key = api_key
65
+ @api_key_web_portal = api_key_web_portal
66
+ @host = host.chomp('/') + '/'
67
+ @smtp_login = smtp_login
68
+ @debug = debug
69
+ end
70
+
71
+ private
72
+
73
+ ##
74
+ # Executes a HTTP request
75
+ #
76
+ # @param method [String] HTTP method (GET/POST)
77
+ # @param endpoint [String] API endpoint
78
+ # @param data [Hash,nil] request body
79
+ # @param without_content_type [Boolean] remove Content-Type header
80
+ #
81
+ # @return [Hash] parsed JSON response
82
+ # @raise [ApiError] if API returns an error
83
+ #
84
+
85
+ def request(method, endpoint, data = nil, without_content_type = false)
86
+ uri = URI.join(@host, endpoint)
87
+
88
+ if (method.upcase == 'GET' && data)
89
+ uri.query = URI.encode_www_form(data)
90
+ end
91
+
92
+ http = Net::HTTP.new(uri.host, uri.port)
93
+ http.use_ssl = uri.scheme == 'https'
94
+ http.read_timeout = 10
95
+
96
+ req = case method.upcase
97
+ when 'GET' then Net::HTTP::Get.new(uri)
98
+ when 'POST' then Net::HTTP::Post.new(uri)
99
+ else raise ApiError, "Unsupported method #{method}"
100
+ end
101
+
102
+ if !without_content_type
103
+ req['Content-Type'] = 'application/json'
104
+ end
105
+ req['Authorization'] = @api_key
106
+ req['Mg-Api-Key'] = @api_key_web_portal
107
+
108
+ req.body = data.to_json if data
109
+
110
+ if (@debug)
111
+ puts "==== HTTP DEBUG ===="
112
+ puts "Method: #{method.upcase}"
113
+ puts "URL: #{uri}"
114
+ puts "Headers: #{req.each_header.to_h}"
115
+ puts "Body: #{req.body}" if req.body
116
+ puts "==================="
117
+ end
118
+
119
+ begin
120
+ res = http.request(req)
121
+ rescue Timeout::Error
122
+ raise ApiError, 'Request timed out'
123
+ rescue SocketError => e
124
+ raise ApiError, e&.message
125
+ end
126
+
127
+ json = JSON.parse(res.body, symbolize_names: true)
128
+
129
+ unless res.code.to_i == 200 && json[:status].to_s.downcase == "ok"
130
+ message = json[:message] || "API error"
131
+
132
+ if message.include?("550 bounced check filter")
133
+ raise StopListError, message
134
+ elsif message.include?("from domain not trusted")
135
+ raise DomainNotTrustedError, message
136
+ elsif res.code.to_i == 403
137
+ raise AuthorizationError, message
138
+ elsif res.code.to_i == 400
139
+ raise BadRequestError, message
140
+ else
141
+ raise ApiError, message
142
+ end
143
+ end
144
+
145
+ json
146
+ end
147
+
148
+ ##
149
+ # Validates email format
150
+ #
151
+ # @param email [String]
152
+ # @raise [ApiError] if invalid
153
+ #
154
+
155
+ def validate_email!(email)
156
+ raise ApiError, 'Invalid email' unless email =~ URI::MailTo::EMAIL_REGEXP
157
+ end
158
+
159
+ public
160
+
161
+ ##
162
+ # Sends a simple email
163
+ #
164
+ # @param to [String] recipient email
165
+ # @param subject [String] subject line
166
+ # @param body [String,nil] message body
167
+ # @param from [String] sender email
168
+ # @param name_from [String,nil] sender name
169
+ # @param params [Hash,nil] template params
170
+ #
171
+ # @return [Hash]
172
+ #
173
+
174
+ def send_email(to:, subject:, body: nil, from:, name_from: nil, params: nil)
175
+ validate_email!(to)
176
+ validate_email!(from)
177
+
178
+ data = {
179
+ email_to: to,
180
+ subject: subject,
181
+ params: params,
182
+ message_text: body,
183
+ email_from: name_from ? "#{name_from} <#{from}>" : from,
184
+ }
185
+ request('POST', 'api/v2/mail/send', data)
186
+ end
187
+
188
+ ##
189
+ # Sends email using SMTP v1 (template or raw body)
190
+ #
191
+ # @param type [String] "template" or "body"
192
+ # @param to [String]
193
+ # @param subject [String]
194
+ # @param body [String,nil]
195
+ # @param from [String]
196
+ # @param name_from [String,nil]
197
+ # @param template_id [Integer,nil]
198
+ # @param params [Hash,nil]
199
+ # @param attach_files [Array]
200
+ #
201
+ def send_email_smtp_v1(type:, to:, subject:, body: nil, from:, name_from: nil, template_id: nil, params: nil, attach_files: [])
202
+ validate_email!(to)
203
+ validate_email!(from)
204
+
205
+ data = {
206
+ email_to: to,
207
+ subject: subject,
208
+ params: params,
209
+ check_local_stop_list: true,
210
+ track_open: true,
211
+ track_click: true,
212
+ email_from: name_from ? "#{name_from} <#{from}>" : from,
213
+ attach_files: attach_files,
214
+ x_track_id: "#{@smtp_login}-#{Time.now.to_i}-#{SecureRandom.hex(6)}",
215
+ }
216
+
217
+ case type
218
+ when 'template'
219
+ data[:template_id] = template_id
220
+ when 'body'
221
+ data[:message_text] = body
222
+ else
223
+ raise ApiError, "Unsupported type #{type}; select type = template or type = body"
224
+ end
225
+
226
+ request('POST', "api/v1/smtp_send?key=#{@api_key}", data)
227
+ end
228
+
229
+ ##
230
+ # Sends a bulk email package
231
+ #
232
+ # @param users [Array<Hash>] recipient data
233
+ # @param subject [String]
234
+ # @param body [String]
235
+ # @param from [String]
236
+ # @param name_from [String,nil]
237
+ #
238
+ def send_emails_package(users:, subject:, body:, from:, name_from: nil)
239
+ validate_email!(from)
240
+
241
+ # "users": [
242
+ # {
243
+ # "emailto": "to1@domain.com", // Имейл получателя
244
+ # "name": "Вася", // любые переменные
245
+ # "field1": "400",
246
+ # "products": [
247
+ # {
248
+ # "name":"foo1",
249
+ # "price":"bar1",
250
+ # "link":"baz1"
251
+ # },
252
+ # {
253
+ # "name":"foo2",
254
+ # "price":"bar2",
255
+ # "link":"baz2"
256
+ # }
257
+ # ] // пример вложенного массива
258
+ # },
259
+ # {
260
+ # "emailto": "to2@domain.com",
261
+ # "string_array": [
262
+ # {"name": "foo1"},
263
+ # {"name": "foo2"}
264
+ # ] // пример массива строк
265
+ # },
266
+ # {
267
+ # ...
268
+ # }
269
+ # ] // массив с получателями
270
+
271
+ data = {
272
+ email_from: from,
273
+ name_from: name_from,
274
+ subject: subject,
275
+ check_local_stop_list: true,
276
+ track_open: true,
277
+ track_click: true,
278
+ message_text: body,
279
+ users: users
280
+ }
281
+
282
+ request('POST', "api/v1/add_json_package?key=#{@api_key}", data)
283
+ end
284
+
285
+ ##
286
+ # Stops a bulk email package
287
+ #
288
+ # @param pack_id [Integer]
289
+ #
290
+ def stop_emails_package(pack_id:)
291
+ params = { key: @api_key, pack_id: pack_id }
292
+ request('GET', "api/v1/package_stop", params)
293
+ end
294
+
295
+
296
+ ##
297
+ # Gets status of a bulk package
298
+ #
299
+ # @param pack_id [Integer]
300
+ #
301
+ def status_emails_package(pack_id:)
302
+ params = { issuen: pack_id}
303
+ request('GET', "api/v2/package/status", params)
304
+ end
305
+
306
+ ##
307
+ # Gets delivery status of a specific message
308
+ #
309
+ # @param email [String,nil]
310
+ # @param x_track_id [String,nil]
311
+ # @param message_id [String,nil]
312
+ #
313
+ def status_email_delivery(email: nil, x_track_id: nil, message_id: nil)
314
+ params = { email: email, x_track_id: x_track_id, message_id: message_id }.compact
315
+ request('GET', "api/v2/issue/status", params)
316
+ end
317
+
318
+ ##
319
+ # Retrieves statistics
320
+ #
321
+ # @param date_from [String]
322
+ # @param date_to [String]
323
+ # @param limit [Integer]
324
+ # @param cursor_next [String,nil]
325
+ # @param timestamp_from [Integer,nil]
326
+ # @param timestamp_to [Integer,nil]
327
+ #
328
+ def get_statistics(date_from:, date_to:, limit: 100, cursor_next: nil, timestamp_from: nil, timestamp_to: nil)
329
+ #?date_from=2023-11-01&date_to=2023-11-07
330
+ #?timestamp_from=1706795600&timestamp_to=1706831999&
331
+ params = {
332
+ date_from: date_from,
333
+ date_to: date_to,
334
+ limit: limit,
335
+ cursor_next: cursor_next,
336
+ }
337
+
338
+ if timestamp_from.present? && timestamp_to.present?
339
+ params[:timestamp_from] = timestamp_from
340
+ params[:timestamp_to] = timestamp_to
341
+ elsif date_from.present? && date_to.present?
342
+ params[:date_from] = date_from
343
+ params[:date_to] = date_to
344
+ end
345
+
346
+ params.compact!
347
+ request('GET', "api/v2/issue/statistics", params)
348
+ end
349
+
350
+
351
+ ##
352
+ # Non-delivered emails by date
353
+ #
354
+ def get_non_delivery_by_date(date_from:, date_to:, limit: 5, cursor_next: nil, order: nil)
355
+ params = { date_from: date_from, date_to: date_to, limit: limit, cursor_next: cursor_next, order: order }.compact
356
+ request('GET', "api/v2/blist/report/non-delivery", params)
357
+ end
358
+
359
+ ##
360
+ # Non-delivered emails by issue
361
+ #
362
+ def get_non_delivery_by_issue(issue:, limit: 5, cursor_next: nil, order: nil)
363
+ params = { issuen: issue, limit: limit, cursor_next: cursor_next, order: order }.compact
364
+ request('GET', "api/v2/issue/report/non-delivery", params)
365
+ end
366
+
367
+ ##
368
+ # FBL (abuse complaints) by date
369
+ #
370
+ def get_fbl_report_by_date(date_from:, date_to:, limit: 5, cursor_next: nil)
371
+ params = { date_from: date_from, date_to: date_to, limit: limit, cursor_next: cursor_next }.compact
372
+ request('GET', "api/v2/blist/report/fbl", params)
373
+ end
374
+
375
+ ##
376
+ # FBL (abuse complaints) by issue
377
+ #
378
+ def get_fbl_report_by_issue(issue:, limit: 5, cursor_next: nil)
379
+ params = { issuen: issue, limit: limit, cursor_next: cursor_next }.compact
380
+ request('GET', "api/v2/issue/report/fbl?", params)
381
+ end
382
+
383
+ ##
384
+ # Searches email in stop-list
385
+ #
386
+ # @param email [String]
387
+ #
388
+ def stop_list_search(email:)
389
+ validate_email!(email)
390
+ request('GET', 'api/v2/stop-list/search', { email: email })
391
+ end
392
+
393
+ ##
394
+ # Adds email to stop-list
395
+ #
396
+ def stop_list_add(email:, mail_from:)
397
+ validate_email!(email)
398
+ request('POST', "api/v2/stop-list/add?#{URI.encode_www_form(mail_from: mail_from, email: email)}", nil, true)
399
+ end
400
+
401
+ ##
402
+ # Removes email from stop-list
403
+ #
404
+ def stop_list_remove(email:, mail_from:)
405
+ validate_email!(email)
406
+ request('POST', "api/v2/stop-list/remove?#{URI.encode_www_form(mail_from: mail_from, email: email)}", nil, true)
407
+ end
408
+
409
+
410
+ ##
411
+ # Checks domain verification status
412
+ #
413
+ def domain_check_verification(domain:,client_name:)
414
+ request('POST',"api/v2/blist/domains/verify", {domain: domain, client: client_name})
415
+ end
416
+
417
+ ##
418
+ # Adds domain
419
+ #
420
+ def domains_add(domain:)
421
+ params = { domain: domain }
422
+ request('POST',"api/v2/blist/domains/add", params)
423
+ end
424
+
425
+ ##
426
+ # Removes domain
427
+ #
428
+ def domains_remove(domain:)
429
+ params = { domain: domain }
430
+ request('POST', "api/v2/blist/domains/remove", params)
431
+ end
432
+
433
+ ##
434
+ # Lists all domains
435
+ #
436
+ # @return [Hash]
437
+ #
438
+ def domains_list
439
+ request('GET', "api/v2/blist/domains")
440
+ end
441
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mailganer-client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - yetisamurai
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-11-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: json
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: net-http
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Full Ruby wrapper for Mailganer email API, FBL, stop-list, statistics.
42
+ email:
43
+ - yetisamurai@proton.me
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files:
47
+ - README.md
48
+ files:
49
+ - README.md
50
+ - lib/mailganer_client.rb
51
+ homepage: https://github.com/yetisamurai/mailganer_client
52
+ licenses:
53
+ - CC0-1.0
54
+ metadata: {}
55
+ post_install_message:
56
+ rdoc_options:
57
+ - "--main"
58
+ - README.md
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '2.7'
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ requirements: []
72
+ rubygems_version: 3.1.6
73
+ signing_key:
74
+ specification_version: 4
75
+ summary: Ruby client for Mailganer API
76
+ test_files: []