mailtrap 2.8.0 → 2.10.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: ad3283b7e6c8af70b1ac8bb3a965868e3b312c01682615c86f7da5104add3bcb
4
- data.tar.gz: 8bab417f54c310a6a92b93ed4a1c6ca6a4cd88dae2e0614e3d2f21cda05d8cf0
3
+ metadata.gz: b3a95002cbfb65622fa2d944bf2de1d41dc25a9a7224f8d244982fdf8154776d
4
+ data.tar.gz: a228f3805ce687e8f34bd410595f6e39f9e7d73a426d1d06ca2b35ec972b5c69
5
5
  SHA512:
6
- metadata.gz: 317490e679342d1c9ac8ae12162268c4102ee410a3d4a9896237894c2a5cd39063595c613d248938a1770cd8f24c7cb4dc72f109c1dabec48f920f92f47cd89d
7
- data.tar.gz: 7da01e190af37689aff6aacdeb411f0858c7c75baf809b51266db920e2988021343e73f164728d9d9bf857139ee2f30cbf004b6c50782d7394685ef6513f05d8
6
+ metadata.gz: fcec8536fae46762bd7289b7b098f45728c9d00b79bcd1386b6e4cb384fa0fcf859136db5cc956480da7aa1cb5063ed8dbee2312ac0e2bf6c8ac4b872443e650
7
+ data.tar.gz: 127126f80cded1f05214a173b1c8e78b8acc6c39bdbf1f168b161ca221a5e4230aa11d098f26c8c188c14778a28c579ac0e2bea52bab2713c82068d43836b677
data/CHANGELOG.md CHANGED
@@ -1,25 +1,41 @@
1
+ ## [2.10.0] - 2026-03-23
2
+
3
+ - Add Email Logs API (list and get email sending logs with filters and cursor pagination)
4
+ - Add `list_each` to Sandbox Messages API for automatic pagination over all messages
5
+
6
+ ## [2.9.0] - 2026-03-13
7
+
8
+ - Add Sending Stats API
9
+ - Parse `reply_to` as a structured address field instead of passing it as a raw
10
+ header when sending with Action Mailer
11
+
1
12
  ## [2.8.0] - 2026-03-03
13
+
2
14
  - Add Account Accesses API
3
15
  - Add Billing API
4
16
 
5
- ## [2.7.0] - 2026-02-24
17
+ ## [2.7.0] - 2026-02-24
18
+
6
19
  - Add Sandbox Messages API
7
20
  - Add Sending Domains API
8
21
  - Add Sandbox Attachments API
9
22
  - Add Accounts API
10
23
 
11
24
  ## [2.6.0] - 2026-01-27
25
+
12
26
  - Add Inboxes API
13
27
  - Add Projects API
14
28
  - Models' `to_h` now returns all fields without compacting
15
29
 
16
30
  ## [2.5.0] - 2025-11-10
31
+
17
32
  - Add Contact Imports API
18
33
  - Add Suppressions API
19
34
  - Write the message IDs to the message when sending with Action Mailer
20
35
  - Fix versioning :)
21
36
 
22
37
  ## [2.4.1] - 2025-08-21
38
+
23
39
  - Set `template_uuid` and `template_variables` when building mail from `Mail::Message`
24
40
 
25
41
  ## [2.4.0] - 2025-08-04
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mailtrap (2.8.0)
4
+ mailtrap (2.10.0)
5
5
  base64
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -177,6 +177,8 @@ Email API:
177
177
  - Full Email Sending – [`full.rb`](examples/full.rb)
178
178
  - Batch Sending – [`batch.rb`](examples/batch.rb)
179
179
  - Sending Domains API – [`sending_domains_api.rb`](examples/sending_domains_api.rb)
180
+ - Sending Stats API – [`stats_api.rb`](examples/stats_api.rb)
181
+ - Email Logs API – [`email_logs_api.rb`](examples/email_logs_api.rb)
180
182
 
181
183
  Email Sandbox (Testing):
182
184
 
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mailtrap
4
+ # Data Transfer Object for an email log event (delivery, open, click, bounce, etc.)
5
+ # @see https://docs.mailtrap.io/developers/email-sending/email-logs
6
+ # @attr_reader event_type [String] One of: delivery, open, click, soft_bounce, bounce, spam, unsubscribe, suspension,
7
+ # reject
8
+ # @attr_reader created_at [String] ISO 8601 timestamp
9
+ # @attr_reader details [EmailLogEventDetails::Delivery, EmailLogEventDetails::Open, EmailLogEventDetails::Click,
10
+ # EmailLogEventDetails::Bounce, EmailLogEventDetails::Spam, EmailLogEventDetails::Unsubscribe,
11
+ # EmailLogEventDetails::Reject] Type-specific event details
12
+ EmailLogEvent = Struct.new(
13
+ :event_type,
14
+ :created_at,
15
+ :details,
16
+ keyword_init: true
17
+ )
18
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mailtrap
4
+ # Type-specific event detail structs for EmailLogEvent. Use event_type to determine which details schema applies.
5
+ # @see https://docs.mailtrap.io/developers/email-sending/email-logs
6
+ module EmailLogEventDetails
7
+ # For event_type = delivery
8
+ Delivery = Struct.new(:sending_ip, :recipient_mx, :email_service_provider, keyword_init: true)
9
+
10
+ # For event_type = open
11
+ Open = Struct.new(:web_ip_address, keyword_init: true)
12
+
13
+ # For event_type = click
14
+ Click = Struct.new(:click_url, :web_ip_address, keyword_init: true)
15
+
16
+ # For event_type = soft_bounce or bounce
17
+ Bounce = Struct.new(
18
+ :sending_ip,
19
+ :recipient_mx,
20
+ :email_service_provider,
21
+ :email_service_provider_status,
22
+ :email_service_provider_response,
23
+ :bounce_category,
24
+ keyword_init: true
25
+ )
26
+
27
+ # For event_type = spam
28
+ Spam = Struct.new(:spam_feedback_type, keyword_init: true)
29
+
30
+ # For event_type = unsubscribe
31
+ Unsubscribe = Struct.new(:web_ip_address, keyword_init: true)
32
+
33
+ # For event_type = suspension or reject
34
+ Reject = Struct.new(:reject_reason, keyword_init: true)
35
+
36
+ DETAIL_STRUCTS = {
37
+ 'delivery' => Delivery,
38
+ 'open' => Open,
39
+ 'click' => Click,
40
+ 'soft_bounce' => Bounce,
41
+ 'bounce' => Bounce,
42
+ 'spam' => Spam,
43
+ 'unsubscribe' => Unsubscribe,
44
+ 'suspension' => Reject,
45
+ 'reject' => Reject
46
+ }.freeze
47
+
48
+ # Builds the appropriate detail struct from API response.
49
+ # @param event_type [String] Known event type (delivery, open, click, etc.)
50
+ # @param hash [Hash] Symbol-keyed details from parsed JSON
51
+ # @return [Delivery, Open, Click, Bounce, Spam, Unsubscribe, Reject]
52
+ # @raise [ArgumentError] when event_type is nil or not in DETAIL_STRUCTS
53
+ def self.build(event_type, hash)
54
+ struct_class = DETAIL_STRUCTS[event_type.to_s]
55
+ raise ArgumentError, "Unknown event_type: #{event_type.inspect}" unless struct_class
56
+
57
+ attrs = hash.slice(*struct_class.members)
58
+ struct_class.new(**attrs)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mailtrap
4
+ # Data Transfer Object for an email log message (summary in list, full details when fetched by ID)
5
+ # @see https://docs.mailtrap.io/developers/email-sending/email-logs
6
+ # @attr_reader message_id [String] Message UUID
7
+ # @attr_reader status [String] delivered, not_delivered, enqueued, opted_out
8
+ # @attr_reader subject [String, nil] Email subject
9
+ # @attr_reader from [String] Sender address
10
+ # @attr_reader to [String] Recipient address
11
+ # @attr_reader sent_at [String] ISO 8601 timestamp
12
+ # @attr_reader client_ip [String, nil] Client IP that sent the email
13
+ # @attr_reader category [String, nil] Message category
14
+ # @attr_reader custom_variables [Hash, nil] Custom variables
15
+ # @attr_reader sending_stream [String] transactional or bulk
16
+ # @attr_reader sending_domain_id [Integer] Sending domain ID
17
+ # @attr_reader template_id [Integer, nil] Template ID if sent from template
18
+ # @attr_reader template_variables [Hash, nil] Template variables
19
+ # @attr_reader opens_count [Integer] Number of opens
20
+ # @attr_reader clicks_count [Integer] Number of clicks
21
+ # @attr_reader raw_message_url [String, nil] Signed URL to download raw .eml (only when fetched by ID)
22
+ # @attr_reader events [Array<EmailLogEvent>, nil] Event list (only when fetched by ID)
23
+ EmailLogMessage = Struct.new(
24
+ :message_id,
25
+ :status,
26
+ :subject,
27
+ :from,
28
+ :to,
29
+ :sent_at,
30
+ :client_ip,
31
+ :category,
32
+ :custom_variables,
33
+ :sending_stream,
34
+ :sending_domain_id,
35
+ :template_id,
36
+ :template_variables,
37
+ :opens_count,
38
+ :clicks_count,
39
+ :raw_message_url,
40
+ :events,
41
+ keyword_init: true
42
+ )
43
+ end
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_api'
4
+ require_relative 'email_log_message'
5
+ require_relative 'email_log_event'
6
+ require_relative 'email_log_event_details'
7
+ require_relative 'email_logs_list_response'
8
+
9
+ module Mailtrap
10
+ class EmailLogsAPI
11
+ include BaseAPI
12
+
13
+ self.response_class = EmailLogMessage
14
+
15
+ # Lists email logs with optional filters and cursor-based pagination.
16
+ #
17
+ # @param filters [Hash, nil] Optional filters. Top-level date keys use string values (ISO 8601);
18
+ # other keys use +{ operator:, value: }+. +value+ can be a single value or an Array for
19
+ # operators that accept multiple values (e.g. +equal+, +not_equal+, +ci_equal+, +ci_not_equal+).
20
+ # Examples:
21
+ # +{ sent_after: "2025-01-01T00:00:00Z", sent_before: "2025-01-31T23:59:59Z" }+
22
+ # +{ to: { operator: "ci_equal", value: "recipient@example.com" } }+
23
+ # +{ category: { operator: "equal", value: ["Welcome Email", "Transactional Email"] } }+
24
+ # @param search_after [String, nil] Message UUID cursor for the next page (from previous +next_page_cursor+)
25
+ # @return [EmailLogsListResponse] messages, total_count, and next_page_cursor
26
+ # @!macro api_errors
27
+ def list(filters: nil, search_after: nil)
28
+ query_params = build_list_query_params(filters:, search_after:)
29
+
30
+ response = client.get(base_path, query_params)
31
+
32
+ build_list_response(response)
33
+ end
34
+
35
+ # Iterates over all email log messages matching the filters, automatically fetching each page.
36
+ # Use this when you want to process every message without manually handling +next_page_cursor+.
37
+ #
38
+ # @param filters [Hash, nil] Same as +list+
39
+ # @yield [EmailLogMessage] Gives each message from every page when a block is given.
40
+ # @return [Enumerator<EmailLogMessage>] if no block given; otherwise the result of the block
41
+ # @!macro api_errors
42
+ def list_each(filters: nil, &block)
43
+ first_page = nil
44
+ fetch_first_page = -> { first_page ||= list(filters: filters) }
45
+ enum = Enumerator.new(-> { fetch_first_page.call.total_count }) do |yielder|
46
+ response = fetch_first_page.call
47
+ loop do
48
+ response.messages.each { |message| yielder << message }
49
+ break if response.next_page_cursor.nil?
50
+
51
+ response = list(filters: filters, search_after: response.next_page_cursor)
52
+ end
53
+ end
54
+
55
+ block ? enum.each(&block) : enum
56
+ end
57
+
58
+ # Fetches a single email log message by ID.
59
+ #
60
+ # @param sending_message_id [String] Message UUID
61
+ # @return [EmailLogMessage] Message with events and raw_message_url when available
62
+ # @!macro api_errors
63
+ def get(sending_message_id)
64
+ base_get(sending_message_id)
65
+ end
66
+
67
+ private
68
+
69
+ def base_path
70
+ "/api/accounts/#{account_id}/email_logs"
71
+ end
72
+
73
+ def build_list_query_params(filters:, search_after:)
74
+ {}.tap do |params|
75
+ params[:search_after] = search_after if search_after
76
+ params.merge!(flatten_filters(filters))
77
+ end
78
+ end
79
+
80
+ # Flattens a filters Hash into query param keys expected by the API (deepObject style).
81
+ # Scalar values => filters[key]; Hashes with :operator/:value => filters[key][operator], filters[key][value].
82
+ # When :value is an Array, the key is repeated (e.g. filters[category][value]=A&filters[category][value]=B)
83
+ # for operators that accept multiple values (e.g. equal, not_equal, ci_equal, ci_not_equal).
84
+ def flatten_filters(filters)
85
+ return {} if filters.nil? || filters.empty?
86
+
87
+ filters.each_with_object({}) do |(key, value), result|
88
+ if value.is_a?(Hash)
89
+ flatten_filter_hash(key, value, result)
90
+ else
91
+ result["filters[#{key}]"] = value.to_s
92
+ end
93
+ end
94
+ end
95
+
96
+ def flatten_filter_hash(parent_key, hash, result)
97
+ hash.each do |key, value|
98
+ if value.is_a?(Array)
99
+ result["filters[#{parent_key}][#{key}][]"] = value.map(&:to_s)
100
+ else
101
+ result["filters[#{parent_key}][#{key}]"] = value.to_s
102
+ end
103
+ end
104
+ end
105
+
106
+ def build_list_response(response)
107
+ EmailLogsListResponse.new(
108
+ messages: Array(response[:messages]).map { |item| handle_response(item) },
109
+ total_count: response[:total_count],
110
+ next_page_cursor: response[:next_page_cursor]
111
+ )
112
+ end
113
+
114
+ def handle_response(response)
115
+ build_message_entity(response)
116
+ end
117
+
118
+ def build_message_entity(hash)
119
+ attrs = hash.slice(*EmailLogMessage.members)
120
+ attrs[:events] = build_events(attrs[:events]) if attrs[:events]
121
+
122
+ EmailLogMessage.new(**attrs)
123
+ end
124
+
125
+ def build_events(events_array)
126
+ Array(events_array).map do |e|
127
+ EmailLogEvent.new(
128
+ event_type: e[:event_type],
129
+ created_at: e[:created_at],
130
+ details: EmailLogEventDetails.build(e[:event_type], e[:details])
131
+ )
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mailtrap
4
+ # Response from listing email logs (paginated)
5
+ # @see https://docs.mailtrap.io/developers/email-sending/email-logs
6
+ # @attr_reader messages [Array<EmailLogMessage>] Page of message summaries
7
+ # @attr_reader total_count [Integer] Total number of messages matching filters
8
+ # @attr_reader next_page_cursor [String, nil] Message UUID to use as search_after for next page, or nil
9
+ EmailLogsListResponse = Struct.new(
10
+ :messages,
11
+ :total_count,
12
+ :next_page_cursor,
13
+ keyword_init: true
14
+ )
15
+ end
data/lib/mailtrap/mail.rb CHANGED
@@ -17,6 +17,7 @@ module Mailtrap
17
17
  category
18
18
  customvariables
19
19
  contenttype
20
+ replyto
20
21
  ].freeze
21
22
  private_constant :SPECIAL_HEADERS
22
23
 
@@ -189,12 +190,13 @@ module Mailtrap
189
190
  # Builds a mail object from Mail::Message
190
191
  # @param message [Mail::Message]
191
192
  # @return [Mailtrap::Mail::Base]
192
- def from_message(message)
193
+ def from_message(message) # rubocop:disable Metrics/AbcSize
193
194
  Mailtrap::Mail::Base.new(
194
195
  from: prepare_addresses(message['from']).first,
195
196
  to: prepare_addresses(message['to']),
196
197
  cc: prepare_addresses(message['cc']),
197
198
  bcc: prepare_addresses(message['bcc']),
199
+ reply_to: prepare_addresses(message['reply-to']).first,
198
200
  subject: message.subject,
199
201
  text: prepare_text_part(message),
200
202
  html: prepare_html_part(message),
@@ -51,6 +51,26 @@ module Mailtrap
51
51
  base_update(message_id, { is_read: is_read })
52
52
  end
53
53
 
54
+ # Iterates over all sandbox messages, automatically fetching each page
55
+ # using cursor-based pagination. Use this when you want to process messages
56
+ # without manually handling pagination.
57
+ # @param search [String] Search query string. Matches subject, to_email, and to_name.
58
+ # @yield [SandboxMessage] Gives each message from every page when a block is given.
59
+ # @return [Enumerator<SandboxMessage>] if no block given; otherwise the result of the block
60
+ # @!macro api_errors
61
+ def list_each(search: nil, &block)
62
+ return to_enum(__method__, search: search) unless block
63
+
64
+ last_id = nil
65
+ loop do
66
+ messages = list(search: search, last_id: last_id)
67
+ break if messages.empty?
68
+
69
+ messages.each { |message| block.call(message) }
70
+ last_id = messages.last.id
71
+ end
72
+ end
73
+
54
74
  # Lists all sandbox messages for the account, limited up to 30 at once
55
75
  # @param search [String] Search query string. Matches subject, to_email, and to_name.
56
76
  # @param last_id [Integer] If specified, a page of records before last_id is returned.
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mailtrap
4
+ # Data Transfer Object for grouped Sending Stats data
5
+ # @attr_reader name [Symbol] Group type (:category, :date, :sending_domain_id, :email_service_provider)
6
+ # @attr_reader value [String, Integer] Group value (e.g., "Transactional", "2026-01-01", 1, "Gmail")
7
+ # @attr_reader stats [SendingStats] Sending stats for this group
8
+ #
9
+ SendingStatGroup = Struct.new(
10
+ :name,
11
+ :value,
12
+ :stats,
13
+ keyword_init: true
14
+ )
15
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mailtrap
4
+ # Data Transfer Object for Sending Stats data
5
+ # @see https://docs.mailtrap.io/developers/email-sending-stats
6
+ # @attr_reader delivery_count [Integer] Number of delivered emails
7
+ # @attr_reader delivery_rate [Float] Delivery rate
8
+ # @attr_reader bounce_count [Integer] Number of bounced emails
9
+ # @attr_reader bounce_rate [Float] Bounce rate
10
+ # @attr_reader open_count [Integer] Number of opened emails
11
+ # @attr_reader open_rate [Float] Open rate
12
+ # @attr_reader click_count [Integer] Number of clicked emails
13
+ # @attr_reader click_rate [Float] Click rate
14
+ # @attr_reader spam_count [Integer] Number of spam reports
15
+ # @attr_reader spam_rate [Float] Spam rate
16
+ #
17
+ SendingStats = Struct.new(
18
+ :delivery_count,
19
+ :delivery_rate,
20
+ :bounce_count,
21
+ :bounce_rate,
22
+ :open_count,
23
+ :open_rate,
24
+ :click_count,
25
+ :click_rate,
26
+ :spam_count,
27
+ :spam_rate,
28
+ keyword_init: true
29
+ )
30
+ end
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+ require_relative 'base_api'
5
+ require_relative 'sending_stats'
6
+ require_relative 'sending_stat_group'
7
+
8
+ module Mailtrap
9
+ class StatsAPI
10
+ include BaseAPI
11
+
12
+ ARRAY_FILTERS = %i[sending_domain_ids sending_streams categories email_service_providers].freeze
13
+ GROUP_KEYS = {
14
+ 'domains' => :sending_domain_id,
15
+ 'categories' => :category,
16
+ 'email_service_providers' => :email_service_provider,
17
+ 'date' => :date
18
+ }.freeze
19
+
20
+ # Get aggregated sending stats
21
+ # @param start_date [String, Date, Time] Start date for the stats period (required)
22
+ # @param end_date [String, Date, Time] End date for the stats period (required)
23
+ # @param sending_domain_ids [Array<Integer>] Filter by sending domain IDs
24
+ # @param sending_streams [Array<String>] Filter by sending streams
25
+ # @param categories [Array<String>] Filter by categories
26
+ # @param email_service_providers [Array<String>] Filter by email service providers
27
+ # @return [SendingStats] Aggregated sending stats
28
+ # @!macro api_errors
29
+ def get(start_date:, end_date:, sending_domain_ids: nil, sending_streams: nil, categories: nil, # rubocop:disable Metrics/ParameterLists
30
+ email_service_providers: nil)
31
+ query_params = build_query_params(
32
+ start_date, end_date,
33
+ { sending_domain_ids:, sending_streams:, categories:, email_service_providers: }
34
+ )
35
+ response = client.get(base_path, query_params)
36
+ build_entity(response, SendingStats)
37
+ end
38
+
39
+ # Get sending stats grouped by domain
40
+ # @param start_date [String, Date, Time] Start date for the stats period (required)
41
+ # @param end_date [String, Date, Time] End date for the stats period (required)
42
+ # @param sending_domain_ids [Array<Integer>] Filter by sending domain IDs
43
+ # @param sending_streams [Array<String>] Filter by sending streams
44
+ # @param categories [Array<String>] Filter by categories
45
+ # @param email_service_providers [Array<String>] Filter by email service providers
46
+ # @return [Array<SendingStatGroup>] Array of SendingStatGroup structs with sending_domain_id and stats
47
+ # @!macro api_errors
48
+ def by_domain(start_date:, end_date:, sending_domain_ids: nil, sending_streams: nil, categories: nil, # rubocop:disable Metrics/ParameterLists
49
+ email_service_providers: nil)
50
+ grouped_stats('domains', start_date, end_date,
51
+ { sending_domain_ids:, sending_streams:, categories:, email_service_providers: })
52
+ end
53
+
54
+ # Get sending stats grouped by category
55
+ # @param start_date [String, Date, Time] Start date for the stats period (required)
56
+ # @param end_date [String, Date, Time] End date for the stats period (required)
57
+ # @param sending_domain_ids [Array<Integer>] Filter by sending domain IDs
58
+ # @param sending_streams [Array<String>] Filter by sending streams
59
+ # @param categories [Array<String>] Filter by categories
60
+ # @param email_service_providers [Array<String>] Filter by email service providers
61
+ # @return [Array<SendingStatGroup>] Array of SendingStatGroup structs with category and stats
62
+ # @!macro api_errors
63
+ def by_category(start_date:, end_date:, sending_domain_ids: nil, sending_streams: nil, categories: nil, # rubocop:disable Metrics/ParameterLists
64
+ email_service_providers: nil)
65
+ grouped_stats('categories', start_date, end_date,
66
+ { sending_domain_ids:, sending_streams:, categories:, email_service_providers: })
67
+ end
68
+
69
+ # Get sending stats grouped by email service provider
70
+ # @param start_date [String, Date, Time] Start date for the stats period (required)
71
+ # @param end_date [String, Date, Time] End date for the stats period (required)
72
+ # @param sending_domain_ids [Array<Integer>] Filter by sending domain IDs
73
+ # @param sending_streams [Array<String>] Filter by sending streams
74
+ # @param categories [Array<String>] Filter by categories
75
+ # @param email_service_providers [Array<String>] Filter by email service providers
76
+ # @return [Array<SendingStatGroup>] Array of SendingStatGroup structs with email_service_provider and stats
77
+ # @!macro api_errors
78
+ def by_email_service_provider(start_date:, end_date:, sending_domain_ids: nil, sending_streams: nil, # rubocop:disable Metrics/ParameterLists
79
+ categories: nil, email_service_providers: nil)
80
+ grouped_stats('email_service_providers', start_date, end_date,
81
+ { sending_domain_ids:, sending_streams:, categories:, email_service_providers: })
82
+ end
83
+
84
+ # Get sending stats grouped by date
85
+ # @param start_date [String, Date, Time] Start date for the stats period (required)
86
+ # @param end_date [String, Date, Time] End date for the stats period (required)
87
+ # @param sending_domain_ids [Array<Integer>] Filter by sending domain IDs
88
+ # @param sending_streams [Array<String>] Filter by sending streams
89
+ # @param categories [Array<String>] Filter by categories
90
+ # @param email_service_providers [Array<String>] Filter by email service providers
91
+ # @return [Array<SendingStatGroup>] Array of SendingStatGroup structs with date and stats
92
+ # @!macro api_errors
93
+ def by_date(start_date:, end_date:, sending_domain_ids: nil, sending_streams: nil, categories: nil, # rubocop:disable Metrics/ParameterLists
94
+ email_service_providers: nil)
95
+ grouped_stats('date', start_date, end_date,
96
+ { sending_domain_ids:, sending_streams:, categories:, email_service_providers: })
97
+ end
98
+
99
+ private
100
+
101
+ def grouped_stats(group, start_date, end_date, filters)
102
+ query_params = build_query_params(start_date, end_date, filters)
103
+ response = client.get("#{base_path}/#{group}", query_params)
104
+ group_key = GROUP_KEYS.fetch(group)
105
+
106
+ response.map do |item|
107
+ SendingStatGroup.new(
108
+ name: group_key,
109
+ value: item[group_key],
110
+ stats: build_entity(item[:stats], SendingStats)
111
+ )
112
+ end
113
+ end
114
+
115
+ def build_query_params(start_date, end_date, filters)
116
+ params = { start_date: normalize_date(start_date), end_date: normalize_date(end_date) }
117
+
118
+ ARRAY_FILTERS.each do |filter_key|
119
+ values = filters[filter_key]
120
+ params["#{filter_key}[]"] = values if values
121
+ end
122
+
123
+ params
124
+ end
125
+
126
+ def normalize_date(value)
127
+ case value
128
+ when Date
129
+ value.iso8601
130
+ when Time
131
+ value.strftime('%F')
132
+ when String
133
+ unless /\A\d{4}-\d{2}-\d{2}\z/.match?(value)
134
+ raise ArgumentError,
135
+ "Invalid date: #{value.inspect}. Expected a Date, Time, or String in YYYY-MM-DD format."
136
+ end
137
+
138
+ Date.iso8601(value).iso8601
139
+ else
140
+ raise ArgumentError,
141
+ "Invalid date: #{value.inspect}. Expected a Date, Time, or String in YYYY-MM-DD format."
142
+ end
143
+ rescue Date::Error
144
+ raise ArgumentError, "Invalid date: #{value.inspect}. Expected a Date, Time, or String in YYYY-MM-DD format."
145
+ end
146
+
147
+ def base_path
148
+ "/api/accounts/#{account_id}/stats"
149
+ end
150
+ end
151
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mailtrap
4
- VERSION = '2.8.0'
4
+ VERSION = '2.10.0'
5
5
  end
data/lib/mailtrap.rb CHANGED
@@ -14,10 +14,12 @@ require_relative 'mailtrap/contact_fields_api'
14
14
  require_relative 'mailtrap/contact_imports_api'
15
15
  require_relative 'mailtrap/suppressions_api'
16
16
  require_relative 'mailtrap/sending_domains_api'
17
+ require_relative 'mailtrap/email_logs_api'
17
18
  require_relative 'mailtrap/projects_api'
18
19
  require_relative 'mailtrap/inboxes_api'
19
20
  require_relative 'mailtrap/sandbox_messages_api'
20
21
  require_relative 'mailtrap/sandbox_attachments_api'
22
+ require_relative 'mailtrap/stats_api'
21
23
 
22
24
  module Mailtrap
23
25
  # @!macro api_errors
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mailtrap
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.8.0
4
+ version: 2.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Railsware Products Studio LLC
@@ -63,6 +63,11 @@ files:
63
63
  - lib/mailtrap/contact_lists_api.rb
64
64
  - lib/mailtrap/contacts_api.rb
65
65
  - lib/mailtrap/contacts_import_request.rb
66
+ - lib/mailtrap/email_log_event.rb
67
+ - lib/mailtrap/email_log_event_details.rb
68
+ - lib/mailtrap/email_log_message.rb
69
+ - lib/mailtrap/email_logs_api.rb
70
+ - lib/mailtrap/email_logs_list_response.rb
66
71
  - lib/mailtrap/email_template.rb
67
72
  - lib/mailtrap/email_templates_api.rb
68
73
  - lib/mailtrap/errors.rb
@@ -79,6 +84,9 @@ files:
79
84
  - lib/mailtrap/sandbox_messages_api.rb
80
85
  - lib/mailtrap/sending_domain.rb
81
86
  - lib/mailtrap/sending_domains_api.rb
87
+ - lib/mailtrap/sending_stat_group.rb
88
+ - lib/mailtrap/sending_stats.rb
89
+ - lib/mailtrap/stats_api.rb
82
90
  - lib/mailtrap/suppression.rb
83
91
  - lib/mailtrap/suppressions_api.rb
84
92
  - lib/mailtrap/version.rb