mailtrap 2.11.0 → 2.12.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: 9b63e81a019d2e0e7e4361de7ca3c511ffc3d0c292dc8db17c1866e986f57f18
4
- data.tar.gz: 1b1efad54b339d0271d8c36970c1c668a345e31611a22cb3782ebc6556bd6543
3
+ metadata.gz: 40fcde70d63616c345252736a68e3de8b1f0cfa7c1cf615cb0cace7e2158c752
4
+ data.tar.gz: 5e3ab5dd73034c6fc752f0d13b683e767d6f29f170c1f2aeed5fbffedc9bef1c
5
5
  SHA512:
6
- metadata.gz: 8e03088d16735f15494b4eee20f6c58dd5a9ec0fb031836326b086687f5cc415d1c841744f27f6ca46c9203952d6abd989ed2f75c4b550fac53b58b62d314838
7
- data.tar.gz: 63fc1ce090a0ee019693ed36794f5319f7b94caa29302ca9a2f7920b7db92dc611aeed74bd9ba0de4853e5626d108a520c11e0e1eb51dcf83418e1ff3b1105f7
6
+ metadata.gz: b971a8cc52133ce0aed38ed202742fdb45e4be73256abe284e40a1d4ac94b729674cd4faea526397685f26d80fcd307669777941b29b1a28fcbd821adbf6beba
7
+ data.tar.gz: 00bc7f6c7db02b4d70f45070e250ad726b72150eb976dcf4476f72b58a44ad99d1f59a6ac89033cc5269d2a5f13987a10a8d1cd4d8d6dc0ed4d894e1134baade
data/CHANGELOG.md CHANGED
@@ -1,3 +1,25 @@
1
+ ## [2.12.0] - 2026-07-24
2
+
3
+ ## What's Changed
4
+ * MT-22678: Add name search filter to contact lists listing by @Rabsztok in https://github.com/mailtrap/mailtrap-ruby/pull/116
5
+ * Add Inbound Email v2 API support by @mklocek in https://github.com/mailtrap/mailtrap-ruby/pull/117
6
+
7
+
8
+ **Full Changelog**: https://github.com/mailtrap/mailtrap-ruby/compare/v2.11.1...v2.12.0
9
+
10
+ ## [2.11.1] - 2026-07-02
11
+
12
+ ## What's Changed
13
+ * Release v2.11.0 by @IgorDobryn in https://github.com/mailtrap/mailtrap-ruby/pull/109
14
+ * MT-22022: Add webhook signature verification helper by @Rabsztok in https://github.com/mailtrap/mailtrap-ruby/pull/110
15
+ * Add draft-release workflow placeholder by @IgorDobryn in https://github.com/mailtrap/mailtrap-ruby/pull/111
16
+ * Implement draft-release workflow by @IgorDobryn in https://github.com/mailtrap/mailtrap-ruby/pull/112
17
+
18
+ ## New Contributors
19
+ * @Rabsztok made their first contribution in https://github.com/mailtrap/mailtrap-ruby/pull/110
20
+
21
+ **Full Changelog**: https://github.com/mailtrap/mailtrap-ruby/compare/v2.11.0...v2.11.1
22
+
1
23
  ## [2.11.0] - 2026-05-14
2
24
  ## What's Changed
3
25
  * Update sandboxes api by @IgorDobryn in https://github.com/mailtrap/mailtrap-ruby/pull/103
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mailtrap (2.11.0)
4
+ mailtrap (2.12.0)
5
5
  base64
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -8,7 +8,6 @@
8
8
 
9
9
  This client uses API v2, for v1 refer to [this documentation](https://mailtrap.docs.apiary.io/)
10
10
 
11
-
12
11
  ## Prerequisites
13
12
 
14
13
  To get the most out of this official Mailtrap.io Ruby SDK:
@@ -176,7 +175,7 @@ Email API:
176
175
 
177
176
  - Full Email Sending – [`full.rb`](examples/full.rb)
178
177
  - Batch Sending – [`batch.rb`](examples/batch.rb)
179
- - Sending Domains API – [`sending_domains_api.rb`](examples/sending_domains_api.rb)
178
+ - Sending Domains API – [`sending_domains_api.rb`](examples/sending_domains_api.rb)
180
179
  - Sending Stats API – [`stats_api.rb`](examples/stats_api.rb)
181
180
  - Email Logs API – [`email_logs_api.rb`](examples/email_logs_api.rb)
182
181
  - Webhooks API – [`webhooks_api.rb`](examples/webhooks_api.rb)
@@ -188,6 +187,13 @@ Email Sandbox (Testing):
188
187
  - Sandbox Messages CRUD - [`sandbox_messages_api.rb`](examples/sandbox_messages_api.rb)
189
188
  - Sandbox Attachments API - [`sandbox_attachments_api.rb`](examples/sandbox_attachments_api.rb)
190
189
 
190
+ Inbound Email:
191
+
192
+ - Inbound Folders API – [`inbound_folders_api.rb`](examples/inbound_folders_api.rb)
193
+ - Inbound Inboxes API – [`inbound_inboxes_api.rb`](examples/inbound_inboxes_api.rb)
194
+ - Inbound Messages API (list, get, delete, reply, reply_all, forward) – [`inbound_messages_api.rb`](examples/inbound_messages_api.rb)
195
+ - Inbound Threads API – [`inbound_threads_api.rb`](examples/inbound_threads_api.rb)
196
+
191
197
  Contact management:
192
198
 
193
199
  - Contacts CRUD & Listing – [`contacts_api.rb`](examples/contacts_api.rb)
@@ -199,6 +205,7 @@ General:
199
205
  - Billing API – [`billing_api.rb`](examples/billing_api.rb)
200
206
  - Templates API – [`email_templates_api.rb`](examples/email_templates_api.rb)
201
207
  - Action Mailer – [`action_mailer.rb`](examples/action_mailer.rb)
208
+ - Verifying webhook signatures – [`webhooks_signature_verification.ru`](examples/webhooks_signature_verification.ru)
202
209
 
203
210
  ## Migration guide v1 → v2
204
211
 
@@ -213,14 +220,14 @@ Bug reports and pull requests are welcome on [GitHub](https://github.com/railswa
213
220
  ## Development
214
221
 
215
222
  After checking out the repo, run `bin/setup` to install dependencies. Then, run
216
- `rake spec` to run the tests. You can also run `bin/console` for an interactive
223
+ `rake spec` to run the tests. You can also run `bin/console` for an interactive
217
224
  prompt that will allow you to experiment.
218
225
 
219
226
  To install this gem onto your local machine, run `bundle exec rake install`.
220
227
 
221
228
  To release a new version, update the version number in `version.rb`, and then
222
229
  run `bundle exec rake release`, which will create a git tag for the version,
223
- push git commits and the created tag, and push the `.gem` file to
230
+ push git commits and the created tag, and push the `.gem` file to
224
231
  [rubygems.org](https://rubygems.org).
225
232
 
226
233
  To run the documentation server, first generate the documentation with
@@ -241,4 +248,4 @@ Everyone interacting in the Mailtrap project's codebases, issue trackers, chat r
241
248
 
242
249
  ## Compatibility with previous releases
243
250
 
244
- Versions of this package up to 2.0.2 were an [unofficial client](https://github.com/vchin/mailtrap-client) developed by [@vchin](https://github.com/vchin). Package version 3 is a completely new package.
251
+ Versions of this package up to 2.0.2 were an [unofficial client](https://github.com/vchin/mailtrap-client) developed by [@vchin](https://github.com/vchin). Package version 3 is a completely new package.
@@ -49,10 +49,14 @@ module Mailtrap
49
49
  end
50
50
 
51
51
  # Lists all contact lists for the account
52
+ # @param search [String] Filters contact lists by name (case-insensitive prefix match)
52
53
  # @return [Array<ContactList>] Array of contact list objects
53
54
  # @!macro api_errors
54
- def list
55
- base_list
55
+ def list(search: nil)
56
+ query_params = {}
57
+ query_params[:search] = search unless search.nil?
58
+
59
+ base_list(query_params)
56
60
  end
57
61
 
58
62
  private
@@ -6,6 +6,10 @@ module Mailtrap
6
6
  # @attr_reader message_id [String] Message UUID
7
7
  # @attr_reader status [String] delivered, not_delivered, enqueued, opted_out
8
8
  # @attr_reader subject [String, nil] Email subject
9
+ # @attr_reader rfc_message_id [String, nil] Value of the RFC 5322 Message-ID header
10
+ # @attr_reader in_reply_to [String, nil] Value of the RFC 5322 In-Reply-To header
11
+ # @attr_reader references [Array<String>] Values of the RFC 5322 References header
12
+ # @attr_reader thread_id [String, nil] ID of the inbound thread this message belongs to, if any
9
13
  # @attr_reader from [String] Sender address
10
14
  # @attr_reader to [String] Recipient address
11
15
  # @attr_reader sent_at [String] ISO 8601 timestamp
@@ -24,6 +28,10 @@ module Mailtrap
24
28
  :message_id,
25
29
  :status,
26
30
  :subject,
31
+ :rfc_message_id,
32
+ :in_reply_to,
33
+ :references,
34
+ :thread_id,
27
35
  :from,
28
36
  :to,
29
37
  :sent_at,
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mailtrap
4
+ # Data Transfer Object for an inbound message attachment
5
+ # @see https://docs.mailtrap.io/developers/inbound
6
+ # @attr_reader attachment_id [String] The attachment ID
7
+ # @attr_reader size [Integer, nil] The attachment size in bytes
8
+ # @attr_reader filename [String, nil] The attachment filename
9
+ # @attr_reader content_type [String, nil] The attachment MIME type
10
+ # @attr_reader content_disposition [String, nil] attachment or inline
11
+ # @attr_reader content_id [String, nil] The content ID for inline attachments
12
+ # @attr_reader download_url [String, nil] Signed URL to download the attachment (only when fetched by ID)
13
+ # @attr_reader download_url_expires_at [String, nil] When the download URL expires (only when fetched by ID)
14
+ # rubocop:disable Lint/StructNewOverride -- +size+ is an API field that shadows Struct#size
15
+ InboundAttachment = Struct.new(
16
+ :attachment_id,
17
+ :size,
18
+ :filename,
19
+ :content_type,
20
+ :content_disposition,
21
+ :content_id,
22
+ :download_url,
23
+ :download_url_expires_at,
24
+ keyword_init: true
25
+ )
26
+ # rubocop:enable Lint/StructNewOverride
27
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mailtrap
4
+ # Data Transfer Object for an inbound folder
5
+ # @see https://docs.mailtrap.io/developers/inbound
6
+ # @attr_reader id [Integer] The folder ID
7
+ # @attr_reader name [String] The folder name
8
+ InboundFolder = Struct.new(
9
+ :id,
10
+ :name,
11
+ keyword_init: true
12
+ )
13
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_api'
4
+ require_relative 'inbound_folder'
5
+
6
+ module Mailtrap
7
+ class InboundFoldersAPI
8
+ include BaseAPI
9
+
10
+ self.supported_options = %i[name]
11
+ self.response_class = InboundFolder
12
+
13
+ # Inbound is scoped to the token's account, so no account_id is required.
14
+ # @param client [Mailtrap::Client] The client instance
15
+ def initialize(client = Mailtrap::Client.new)
16
+ @client = client
17
+ end
18
+
19
+ # Lists all inbound folders in the account
20
+ # @return [Array<InboundFolder>] Array of folders
21
+ # @!macro api_errors
22
+ def list
23
+ base_list
24
+ end
25
+
26
+ # Retrieves a specific inbound folder
27
+ # @param folder_id [Integer] The folder ID
28
+ # @return [InboundFolder] Folder object
29
+ # @!macro api_errors
30
+ def get(folder_id)
31
+ base_get(folder_id)
32
+ end
33
+
34
+ # Creates a new inbound folder
35
+ # @param [Hash] options The parameters to create
36
+ # @option options [String] :name The folder name
37
+ # @return [InboundFolder] Created folder
38
+ # @!macro api_errors
39
+ # @raise [ArgumentError] If invalid options are provided
40
+ def create(options)
41
+ base_create(options)
42
+ end
43
+
44
+ # Updates an inbound folder
45
+ # @param folder_id [Integer] The folder ID
46
+ # @param [Hash] options The parameters to update
47
+ # @option options [String] :name The folder name
48
+ # @return [InboundFolder] Updated folder
49
+ # @!macro api_errors
50
+ # @raise [ArgumentError] If invalid options are provided
51
+ def update(folder_id, options)
52
+ base_update(folder_id, options)
53
+ end
54
+
55
+ # Deletes an inbound folder along with all of its inboxes
56
+ # @param folder_id [Integer] The folder ID
57
+ # @return nil
58
+ # @!macro api_errors
59
+ def delete(folder_id)
60
+ base_delete(folder_id)
61
+ end
62
+
63
+ private
64
+
65
+ def base_path
66
+ '/api/inbound/folders'
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mailtrap
4
+ # Data Transfer Object for an inbound inbox
5
+ # @see https://docs.mailtrap.io/developers/inbound
6
+ # @attr_reader id [Integer] The inbox ID
7
+ # @attr_reader name [String] The inbox name
8
+ # @attr_reader address [String] The inbox's inbound address
9
+ # @attr_reader domain_id [Integer] The sending domain the inbox is attached to
10
+ InboundInbox = Struct.new(
11
+ :id,
12
+ :name,
13
+ :address,
14
+ :domain_id,
15
+ keyword_init: true
16
+ )
17
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_api'
4
+ require_relative 'inbound_inbox'
5
+
6
+ module Mailtrap
7
+ class InboundInboxesAPI
8
+ include BaseAPI
9
+
10
+ self.response_class = InboundInbox
11
+
12
+ CREATE_OPTIONS = %i[name domain_id].freeze
13
+ UPDATE_OPTIONS = %i[name].freeze
14
+
15
+ # Inbound is scoped to the token's account, so no account_id is required.
16
+ # @param client [Mailtrap::Client] The client instance
17
+ def initialize(client = Mailtrap::Client.new)
18
+ @client = client
19
+ end
20
+
21
+ # Lists all inboxes in a folder
22
+ # @param folder_id [Integer] The folder ID
23
+ # @return [Array<InboundInbox>] Array of inboxes
24
+ # @!macro api_errors
25
+ def list(folder_id)
26
+ client.get(inboxes_path(folder_id)).map { |item| handle_response(item) }
27
+ end
28
+
29
+ # Retrieves a specific inbox
30
+ # @param folder_id [Integer] The folder ID
31
+ # @param inbox_id [Integer] The inbox ID
32
+ # @return [InboundInbox] Inbox object
33
+ # @!macro api_errors
34
+ def get(folder_id, inbox_id)
35
+ handle_response(client.get("#{inboxes_path(folder_id)}/#{inbox_id}"))
36
+ end
37
+
38
+ # Creates a new inbox in a folder
39
+ # @param folder_id [Integer] The folder ID
40
+ # @param [Hash] options The parameters to create
41
+ # @option options [String] :name The inbox name
42
+ # @option options [Integer] :domain_id Attach to a custom domain; omit for a Mailtrap-hosted inbox
43
+ # @return [InboundInbox] Created inbox
44
+ # @!macro api_errors
45
+ # @raise [ArgumentError] If invalid options are provided
46
+ def create(folder_id, options)
47
+ validate_options!(options, CREATE_OPTIONS)
48
+ handle_response(client.post(inboxes_path(folder_id), options))
49
+ end
50
+
51
+ # Updates an inbox
52
+ # @param folder_id [Integer] The folder ID
53
+ # @param inbox_id [Integer] The inbox ID
54
+ # @param [Hash] options The parameters to update
55
+ # @option options [String] :name The inbox name
56
+ # @return [InboundInbox] Updated inbox
57
+ # @!macro api_errors
58
+ # @raise [ArgumentError] If invalid options are provided
59
+ def update(folder_id, inbox_id, options)
60
+ validate_options!(options, UPDATE_OPTIONS)
61
+ handle_response(client.patch("#{inboxes_path(folder_id)}/#{inbox_id}", options))
62
+ end
63
+
64
+ # Deletes an inbox
65
+ # @param folder_id [Integer] The folder ID
66
+ # @param inbox_id [Integer] The inbox ID
67
+ # @return nil
68
+ # @!macro api_errors
69
+ def delete(folder_id, inbox_id)
70
+ client.delete("#{inboxes_path(folder_id)}/#{inbox_id}")
71
+ end
72
+
73
+ private
74
+
75
+ def inboxes_path(folder_id)
76
+ "/api/inbound/folders/#{folder_id}/inboxes"
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mailtrap
4
+ # Data Transfer Object for an inbound message (summary in list, full details when fetched by ID)
5
+ # @see https://docs.mailtrap.io/developers/inbound
6
+ # @attr_reader id [String] The Mailtrap object ID (not the RFC Message-ID header)
7
+ # @attr_reader inbox_id [Integer] The inbox the message was received in
8
+ # @attr_reader from [String, nil] Sender address
9
+ # @attr_reader to [Array<String>] Recipient addresses
10
+ # @attr_reader cc [Array<String>] Cc addresses
11
+ # @attr_reader bcc [Array<String>] Bcc addresses
12
+ # @attr_reader reply_to [String, nil] Reply-To address
13
+ # @attr_reader subject [String, nil] Email subject
14
+ # @attr_reader rfc_message_id [String, nil] Value of the RFC 5322 Message-ID header
15
+ # @attr_reader in_reply_to [String, nil] Value of the RFC 5322 In-Reply-To header
16
+ # @attr_reader references [Array<String>] Values of the RFC 5322 References header
17
+ # @attr_reader headers [Hash, nil] Raw message headers
18
+ # @attr_reader size [Integer, nil] Total message size in bytes
19
+ # @attr_reader html_size [Integer, nil] HTML body size in bytes
20
+ # @attr_reader text_size [Integer, nil] Text body size in bytes
21
+ # @attr_reader received_at [String] ISO 8601 timestamp when the message was received
22
+ # @attr_reader thread_id [String, nil] ID of the thread this message belongs to, if any
23
+ # @attr_reader attachments [Array<InboundAttachment>] The message attachments
24
+ # @attr_reader raw_message_url [String, nil] Signed URL to download raw .eml (only when fetched by ID)
25
+ # @attr_reader raw_message_expires_at [String, nil] When the raw message URL expires (only when fetched by ID)
26
+ # @attr_reader html_body [String, nil] Decoded HTML body (only when fetched by ID)
27
+ # @attr_reader text_body [String, nil] Decoded text body (only when fetched by ID)
28
+ # rubocop:disable Lint/StructNewOverride -- +size+ is an API field that shadows Struct#size
29
+ InboundMessage = Struct.new(
30
+ :id,
31
+ :inbox_id,
32
+ :from,
33
+ :to,
34
+ :cc,
35
+ :bcc,
36
+ :reply_to,
37
+ :subject,
38
+ :rfc_message_id,
39
+ :in_reply_to,
40
+ :references,
41
+ :headers,
42
+ :size,
43
+ :html_size,
44
+ :text_size,
45
+ :received_at,
46
+ :thread_id,
47
+ :attachments,
48
+ :raw_message_url,
49
+ :raw_message_expires_at,
50
+ :html_body,
51
+ :text_body,
52
+ keyword_init: true
53
+ )
54
+ # rubocop:enable Lint/StructNewOverride
55
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_api'
4
+ require_relative 'inbound_message'
5
+ require_relative 'inbound_attachment'
6
+ require_relative 'inbound_messages_list_response'
7
+ require_relative 'inbound_send_result'
8
+
9
+ module Mailtrap
10
+ class InboundMessagesAPI
11
+ include BaseAPI
12
+
13
+ self.response_class = InboundMessage
14
+
15
+ SEND_OPTIONS = %i[from to cc bcc reply_to text html category attachments headers custom_variables].freeze
16
+
17
+ # Inbound is scoped to the token's account, so no account_id is required.
18
+ # @param client [Mailtrap::Client] The client instance
19
+ def initialize(client = Mailtrap::Client.new)
20
+ @client = client
21
+ end
22
+
23
+ # Lists messages in an inbox (cursor-paginated)
24
+ # @param inbox_id [Integer] The inbox ID
25
+ # @param last_id [String, nil] Cursor from the previous response's +last_id+ for the next page
26
+ # @return [InboundMessagesListResponse] data, total_count, and last_id
27
+ # @!macro api_errors
28
+ def list(inbox_id, last_id: nil)
29
+ query_params = last_id ? { last_id: } : {}
30
+ response = client.get(messages_path(inbox_id), query_params)
31
+
32
+ InboundMessagesListResponse.new(
33
+ data: Array(response[:data]).map { |item| build_message(item) },
34
+ total_count: response[:total_count],
35
+ last_id: response[:last_id]
36
+ )
37
+ end
38
+
39
+ # Fetches a single message with its full body and attachment download URLs
40
+ # @param inbox_id [Integer] The inbox ID
41
+ # @param message_id [String] The message ID
42
+ # @return [InboundMessage] Message details
43
+ # @!macro api_errors
44
+ def get(inbox_id, message_id)
45
+ build_message(client.get("#{messages_path(inbox_id)}/#{message_id}"))
46
+ end
47
+
48
+ # Deletes a message
49
+ # @param inbox_id [Integer] The inbox ID
50
+ # @param message_id [String] The message ID
51
+ # @return nil
52
+ # @!macro api_errors
53
+ def delete(inbox_id, message_id)
54
+ client.delete("#{messages_path(inbox_id)}/#{message_id}")
55
+ end
56
+
57
+ # Replies to a message (sends to the original sender)
58
+ # @param inbox_id [Integer] The inbox ID
59
+ # @param message_id [String] The message ID
60
+ # @param [Hash] options The send parameters (see {SEND_OPTIONS})
61
+ # @return [InboundSendResult] The sent message IDs
62
+ # @!macro api_errors
63
+ # @raise [ArgumentError] If invalid options are provided
64
+ def reply(inbox_id, message_id, options)
65
+ send_action(inbox_id, message_id, 'reply', options)
66
+ end
67
+
68
+ # Replies to a message and copies the original's other recipients
69
+ # @param (see #reply)
70
+ # @return [InboundSendResult] The sent message IDs
71
+ # @!macro api_errors
72
+ # @raise [ArgumentError] If invalid options are provided
73
+ def reply_all(inbox_id, message_id, options)
74
+ send_action(inbox_id, message_id, 'reply_all', options)
75
+ end
76
+
77
+ # Forwards a message to new recipients
78
+ # @param (see #reply)
79
+ # @return [InboundSendResult] The sent message IDs
80
+ # @!macro api_errors
81
+ # @raise [ArgumentError] If invalid options are provided, or if no +to+ recipient is given
82
+ def forward(inbox_id, message_id, options)
83
+ raise ArgumentError, 'to is required for forward' if Array(options[:to]).empty?
84
+
85
+ send_action(inbox_id, message_id, 'forward', options)
86
+ end
87
+
88
+ private
89
+
90
+ def messages_path(inbox_id)
91
+ "/api/inbound/inboxes/#{inbox_id}/messages"
92
+ end
93
+
94
+ def send_action(inbox_id, message_id, action, options)
95
+ validate_options!(options, SEND_OPTIONS)
96
+ response = client.post("#{messages_path(inbox_id)}/#{message_id}/#{action}", options)
97
+
98
+ InboundSendResult.new(message_ids: response[:message_ids])
99
+ end
100
+
101
+ def build_message(hash)
102
+ attrs = hash.slice(*InboundMessage.members)
103
+ attrs[:attachments] = build_attachments(attrs[:attachments]) if attrs[:attachments]
104
+
105
+ InboundMessage.new(**attrs)
106
+ end
107
+
108
+ def build_attachments(attachments)
109
+ Array(attachments).map do |attachment|
110
+ InboundAttachment.new(**attachment.slice(*InboundAttachment.members))
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mailtrap
4
+ # Data Transfer Object for a paginated list of inbound messages
5
+ # @see https://docs.mailtrap.io/developers/inbound
6
+ # @attr_reader data [Array<InboundMessage>] The messages on this page
7
+ # @attr_reader total_count [Integer] Total number of messages in the inbox
8
+ # @attr_reader last_id [String, nil] Cursor for the next page, or nil if this is the last page
9
+ InboundMessagesListResponse = Struct.new(
10
+ :data,
11
+ :total_count,
12
+ :last_id,
13
+ keyword_init: true
14
+ )
15
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mailtrap
4
+ # Data Transfer Object for the result of a reply, reply-all, or forward.
5
+ # @see https://docs.mailtrap.io/developers/inbound
6
+ # @attr_reader message_ids [Array<String>] UUIDs of the sent messages, one per recipient
7
+ InboundSendResult = Struct.new(
8
+ :message_ids,
9
+ keyword_init: true
10
+ )
11
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mailtrap
4
+ # Data Transfer Object for an inbound thread (summary in list, with embedded messages when fetched by ID)
5
+ # @see https://docs.mailtrap.io/developers/inbound
6
+ # @attr_reader id [String] The thread ID
7
+ # @attr_reader subject [String, nil] The thread subject
8
+ # @attr_reader message_count [Integer] Number of messages in the thread
9
+ # @attr_reader size [Integer] Total size of the thread in bytes
10
+ # @attr_reader first_message_at [String] ISO 8601 timestamp of the first message
11
+ # @attr_reader last_received_at [String, nil] ISO 8601 timestamp of the last received message
12
+ # @attr_reader last_sent_at [String, nil] ISO 8601 timestamp of the last sent message
13
+ # @attr_reader last_activity_at [String] ISO 8601 timestamp of the last activity
14
+ # @attr_reader last_message_id [String, nil] ID of the most recent message
15
+ # @attr_reader senders [Array<String>] Distinct sender addresses in the thread
16
+ # @attr_reader recipients [Array<String>] Distinct recipient addresses in the thread
17
+ # @attr_reader attachments [Array<InboundAttachment>] Attachments across the thread
18
+ # @attr_reader messages [Array<InboundThreadMessage>, nil] The thread's messages (only when fetched by ID)
19
+ # rubocop:disable Lint/StructNewOverride -- +size+ is an API field that shadows Struct#size
20
+ InboundThread = Struct.new(
21
+ :id,
22
+ :subject,
23
+ :message_count,
24
+ :size,
25
+ :first_message_at,
26
+ :last_received_at,
27
+ :last_sent_at,
28
+ :last_activity_at,
29
+ :last_message_id,
30
+ :senders,
31
+ :recipients,
32
+ :attachments,
33
+ :messages,
34
+ keyword_init: true
35
+ )
36
+ # rubocop:enable Lint/StructNewOverride
37
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mailtrap
4
+ # Data Transfer Object for a message inside an inbound thread.
5
+ # Only +visibility_status+ and +direction+ are always present; +placeholder+ entries omit the rest,
6
+ # and the delivery lifecycle fields are only set on available outbound messages.
7
+ # @see https://docs.mailtrap.io/developers/inbound
8
+ # @attr_reader visibility_status [String] available or placeholder
9
+ # @attr_reader direction [String] inbound or outbound
10
+ # @attr_reader id [String, nil] The message ID
11
+ # @attr_reader message_group_id [String, nil] Groups messages sent together as one logical send
12
+ # @attr_reader subject [String, nil] Email subject
13
+ # @attr_reader rfc_message_id [String, nil] Value of the RFC 5322 Message-ID header
14
+ # @attr_reader in_reply_to [String, nil] Value of the RFC 5322 In-Reply-To header
15
+ # @attr_reader references [Array<String>, nil] Values of the RFC 5322 References header
16
+ # @attr_reader from [String, nil] Sender address
17
+ # @attr_reader to [Array<String>, nil] Recipient addresses
18
+ # @attr_reader cc [Array<String>, nil] Cc addresses
19
+ # @attr_reader bcc [Array<String>, nil] Bcc addresses
20
+ # @attr_reader reply_to [String, nil] Reply-To address
21
+ # @attr_reader created_at [String, nil] ISO 8601 timestamp
22
+ # @attr_reader email_size [Integer, nil] Message size in bytes
23
+ # @attr_reader text_body [String, nil] Decoded text body
24
+ # @attr_reader html_body [String, nil] Decoded HTML body
25
+ # @attr_reader attachments [Array<InboundAttachment>, nil] The message attachments
26
+ # @attr_reader delivery_status [String, nil] Delivery status (outbound messages only)
27
+ # @attr_reader delivered_at [String, nil] Delivery timestamp (outbound messages only)
28
+ # @attr_reader bounced_at [String, nil] Bounce timestamp (outbound messages only)
29
+ InboundThreadMessage = Struct.new(
30
+ :visibility_status,
31
+ :direction,
32
+ :id,
33
+ :message_group_id,
34
+ :subject,
35
+ :rfc_message_id,
36
+ :in_reply_to,
37
+ :references,
38
+ :from,
39
+ :to,
40
+ :cc,
41
+ :bcc,
42
+ :reply_to,
43
+ :created_at,
44
+ :email_size,
45
+ :text_body,
46
+ :html_body,
47
+ :attachments,
48
+ :delivery_status,
49
+ :delivered_at,
50
+ :bounced_at,
51
+ keyword_init: true
52
+ )
53
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_api'
4
+ require_relative 'inbound_thread'
5
+ require_relative 'inbound_thread_message'
6
+ require_relative 'inbound_attachment'
7
+ require_relative 'inbound_threads_list_response'
8
+
9
+ module Mailtrap
10
+ class InboundThreadsAPI
11
+ include BaseAPI
12
+
13
+ self.response_class = InboundThread
14
+
15
+ # Inbound is scoped to the token's account, so no account_id is required.
16
+ # @param client [Mailtrap::Client] The client instance
17
+ def initialize(client = Mailtrap::Client.new)
18
+ @client = client
19
+ end
20
+
21
+ # Lists threads in an inbox (cursor-paginated)
22
+ # @param inbox_id [Integer] The inbox ID
23
+ # @param last_id [String, nil] Cursor from the previous response's +last_id+ for the next page
24
+ # @return [InboundThreadsListResponse] data, total_count, and last_id
25
+ # @!macro api_errors
26
+ def list(inbox_id, last_id: nil)
27
+ query_params = last_id ? { last_id: } : {}
28
+ response = client.get(threads_path(inbox_id), query_params)
29
+
30
+ InboundThreadsListResponse.new(
31
+ data: Array(response[:data]).map { |item| build_thread(item) },
32
+ total_count: response[:total_count],
33
+ last_id: response[:last_id]
34
+ )
35
+ end
36
+
37
+ # Fetches a single thread with its messages embedded (oldest first)
38
+ # @param inbox_id [Integer] The inbox ID
39
+ # @param thread_id [String] The thread ID
40
+ # @return [InboundThread] Thread with messages
41
+ # @!macro api_errors
42
+ def get(inbox_id, thread_id)
43
+ build_thread(client.get("#{threads_path(inbox_id)}/#{thread_id}"))
44
+ end
45
+
46
+ # Deletes a thread
47
+ # @param inbox_id [Integer] The inbox ID
48
+ # @param thread_id [String] The thread ID
49
+ # @return nil
50
+ # @!macro api_errors
51
+ def delete(inbox_id, thread_id)
52
+ client.delete("#{threads_path(inbox_id)}/#{thread_id}")
53
+ end
54
+
55
+ private
56
+
57
+ def threads_path(inbox_id)
58
+ "/api/inbound/inboxes/#{inbox_id}/threads"
59
+ end
60
+
61
+ def build_thread(hash)
62
+ attrs = hash.slice(*InboundThread.members)
63
+ attrs[:attachments] = build_attachments(attrs[:attachments]) if attrs[:attachments]
64
+ attrs[:messages] = build_messages(attrs[:messages]) if attrs[:messages]
65
+
66
+ InboundThread.new(**attrs)
67
+ end
68
+
69
+ def build_messages(messages)
70
+ Array(messages).map do |message|
71
+ attrs = message.slice(*InboundThreadMessage.members)
72
+ attrs[:attachments] = build_attachments(attrs[:attachments]) if attrs[:attachments]
73
+
74
+ InboundThreadMessage.new(**attrs)
75
+ end
76
+ end
77
+
78
+ def build_attachments(attachments)
79
+ Array(attachments).map do |attachment|
80
+ InboundAttachment.new(**attachment.slice(*InboundAttachment.members))
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mailtrap
4
+ # Data Transfer Object for a paginated list of inbound threads
5
+ # @see https://docs.mailtrap.io/developers/inbound
6
+ # @attr_reader data [Array<InboundThread>] The threads on this page
7
+ # @attr_reader total_count [Integer] Total number of threads in the inbox
8
+ # @attr_reader last_id [String, nil] Cursor for the next page, or nil if this is the last page
9
+ InboundThreadsListResponse = Struct.new(
10
+ :data,
11
+ :total_count,
12
+ :last_id,
13
+ keyword_init: true
14
+ )
15
+ end
@@ -17,6 +17,8 @@ module Mailtrap
17
17
  # @attr_reader health_alerts_enabled [Boolean] Whether health alerts are enabled
18
18
  # @attr_reader critical_alerts_enabled [Boolean] Whether critical alerts are enabled
19
19
  # @attr_reader alert_recipient_email [String, nil] The email address for alert recipients
20
+ # @attr_reader inbound_enabled [Boolean] Whether inbound email is enabled for this domain
21
+ # @attr_reader inbound_verified [Boolean] Whether the domain's inbound MX records are verified
20
22
  # @attr_reader permissions [Hash] The permissions for the sending domain
21
23
  #
22
24
  SendingDomain = Struct.new(
@@ -34,6 +36,8 @@ module Mailtrap
34
36
  :health_alerts_enabled,
35
37
  :critical_alerts_enabled,
36
38
  :alert_recipient_email,
39
+ :inbound_enabled,
40
+ :inbound_verified,
37
41
  :permissions,
38
42
  :created_at,
39
43
  :updated_at,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mailtrap
4
- VERSION = '2.11.0'
4
+ VERSION = '2.12.0'
5
5
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+
5
+ module Mailtrap
6
+ # Helpers for working with inbound Mailtrap webhooks.
7
+ #
8
+ # @see https://docs.mailtrap.io/email-api-smtp/advanced/webhooks#verifying-the-signature
9
+ module Webhooks
10
+ # Hex-encoded HMAC-SHA256 signature length.
11
+ SIGNATURE_HEX_LENGTH = 64
12
+
13
+ # Verifies the HMAC-SHA256 signature of a Mailtrap webhook payload.
14
+ #
15
+ # Mailtrap signs every outbound webhook by computing
16
+ # `HMAC-SHA256(signing_secret, raw_request_body)` and sending the lowercase
17
+ # hex digest in the `Mailtrap-Signature` HTTP header. Compute the same
18
+ # digest on your side and compare it in constant time.
19
+ #
20
+ # The comparison is performed with {OpenSSL.fixed_length_secure_compare} to
21
+ # avoid timing side-channels.
22
+ #
23
+ # The method never raises on inputs that could plausibly arrive over the
24
+ # wire (empty strings, wrong-length signatures, non-hex characters, missing
25
+ # secret) — it simply returns `false`. This makes it safe to call directly
26
+ # from a controller without rescuing.
27
+ #
28
+ # @param payload [String] The raw request body, exactly as received.
29
+ # **Do not** parse and re-serialize the JSON — re-encoding may reorder
30
+ # keys or alter whitespace and invalidate the signature.
31
+ # @param signature [String] The value of the `Mailtrap-Signature` HTTP
32
+ # header (lowercase hex string).
33
+ # @param signing_secret [String] The webhook's `signing_secret`, returned
34
+ # by {WebhooksAPI#create} on webhook creation.
35
+ # @return [Boolean] `true` if the signature is valid for the given payload
36
+ # and secret, `false` otherwise.
37
+ def self.verify_signature(payload:, signature:, signing_secret:)
38
+ return false unless [payload, signature, signing_secret].all? { |v| v.is_a?(String) && !v.empty? }
39
+ return false if signature.bytesize != SIGNATURE_HEX_LENGTH
40
+
41
+ expected = OpenSSL::HMAC.hexdigest('SHA256', signing_secret, payload)
42
+
43
+ OpenSSL.fixed_length_secure_compare(expected, signature)
44
+ rescue ArgumentError
45
+ # fixed_length_secure_compare raises ArgumentError on length mismatch
46
+ false
47
+ end
48
+ end
49
+ end
data/lib/mailtrap.rb CHANGED
@@ -27,6 +27,11 @@ require_relative 'mailtrap/sandbox_messages_api'
27
27
  require_relative 'mailtrap/sandbox_attachments_api'
28
28
  require_relative 'mailtrap/stats_api'
29
29
  require_relative 'mailtrap/webhooks_api'
30
+ require_relative 'mailtrap/webhooks'
31
+ require_relative 'mailtrap/inbound_folders_api'
32
+ require_relative 'mailtrap/inbound_inboxes_api'
33
+ require_relative 'mailtrap/inbound_messages_api'
34
+ require_relative 'mailtrap/inbound_threads_api'
30
35
 
31
36
  module Mailtrap
32
37
  # @!macro api_errors
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mailtrap
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.11.0
4
+ version: 2.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Railsware Products Studio LLC
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-05-14 00:00:00.000000000 Z
11
+ date: 2026-07-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -80,6 +80,19 @@ files:
80
80
  - lib/mailtrap/email_template.rb
81
81
  - lib/mailtrap/email_templates_api.rb
82
82
  - lib/mailtrap/errors.rb
83
+ - lib/mailtrap/inbound_attachment.rb
84
+ - lib/mailtrap/inbound_folder.rb
85
+ - lib/mailtrap/inbound_folders_api.rb
86
+ - lib/mailtrap/inbound_inbox.rb
87
+ - lib/mailtrap/inbound_inboxes_api.rb
88
+ - lib/mailtrap/inbound_message.rb
89
+ - lib/mailtrap/inbound_messages_api.rb
90
+ - lib/mailtrap/inbound_messages_list_response.rb
91
+ - lib/mailtrap/inbound_send_result.rb
92
+ - lib/mailtrap/inbound_thread.rb
93
+ - lib/mailtrap/inbound_thread_message.rb
94
+ - lib/mailtrap/inbound_threads_api.rb
95
+ - lib/mailtrap/inbound_threads_list_response.rb
83
96
  - lib/mailtrap/inbox.rb
84
97
  - lib/mailtrap/inboxes_api.rb
85
98
  - lib/mailtrap/mail.rb
@@ -104,6 +117,7 @@ files:
104
117
  - lib/mailtrap/suppressions_api.rb
105
118
  - lib/mailtrap/version.rb
106
119
  - lib/mailtrap/webhook.rb
120
+ - lib/mailtrap/webhooks.rb
107
121
  - lib/mailtrap/webhooks_api.rb
108
122
  - mailtrap.gemspec
109
123
  homepage: https://github.com/mailtrap/mailtrap-ruby
@@ -129,7 +143,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
129
143
  - !ruby/object:Gem::Version
130
144
  version: '0'
131
145
  requirements: []
132
- rubygems_version: 3.5.11
146
+ rubygems_version: 3.5.22
133
147
  signing_key:
134
148
  specification_version: 4
135
149
  summary: Official mailtrap.io API client