mailtrap 2.9.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: c1d2ec6f013c1c734b375a56217c2ec6373904851fdc28f25147932bf7d44225
4
- data.tar.gz: 3a7c2994b477de66e566ad9cc41c5ca39d70641fa096c92a47af4c0289de6808
3
+ metadata.gz: b3a95002cbfb65622fa2d944bf2de1d41dc25a9a7224f8d244982fdf8154776d
4
+ data.tar.gz: a228f3805ce687e8f34bd410595f6e39f9e7d73a426d1d06ca2b35ec972b5c69
5
5
  SHA512:
6
- metadata.gz: 1c2176601df89656a773772072952169caf35c0cff0d193cdab25c07956d09d9ab828b9e2995581512312f8e33f35605623b89052950752d8fc6a6c13cb3f0fc
7
- data.tar.gz: 7661abf25236f9bec0a26f5463d92074a2e76b18bb9f5fc1067c3b2f810a7eed518e1a0d211ac61c11cde4eaaeb4542376ddd5ebd9785678adb225be3838092b
6
+ metadata.gz: fcec8536fae46762bd7289b7b098f45728c9d00b79bcd1386b6e4cb384fa0fcf859136db5cc956480da7aa1cb5063ed8dbee2312ac0e2bf6c8ac4b872443e650
7
+ data.tar.gz: 127126f80cded1f05214a173b1c8e78b8acc6c39bdbf1f168b161ca221a5e4230aa11d098f26c8c188c14778a28c579ac0e2bea52bab2713c82068d43836b677
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
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
+
1
6
  ## [2.9.0] - 2026-03-13
2
7
 
3
8
  - Add Sending Stats API
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mailtrap (2.9.0)
4
+ mailtrap (2.10.0)
5
5
  base64
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -178,6 +178,7 @@ Email API:
178
178
  - Batch Sending – [`batch.rb`](examples/batch.rb)
179
179
  - Sending Domains API – [`sending_domains_api.rb`](examples/sending_domains_api.rb)
180
180
  - Sending Stats API – [`stats_api.rb`](examples/stats_api.rb)
181
+ - Email Logs API – [`email_logs_api.rb`](examples/email_logs_api.rb)
181
182
 
182
183
  Email Sandbox (Testing):
183
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
@@ -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.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mailtrap
4
- VERSION = '2.9.0'
4
+ VERSION = '2.10.0'
5
5
  end
data/lib/mailtrap.rb CHANGED
@@ -14,6 +14,7 @@ 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'
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.9.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