From 1c5cfb33ef872a8a1b64272d1d2ee2dc07df1f55 Mon Sep 17 00:00:00 2001 From: Marcin Klocek Date: Fri, 31 Jul 2026 13:33:13 +0200 Subject: [PATCH] Add inbound_receiving webhook support Backfill for the Inbound Email API webhook. Per email-sending.openapi.yml: - Webhook DTO gains an `inbound_inbox_id` (integer, nullable) member, positioned after `domain_id` to match the response schema. - WebhooksAPI#create and #update accept an optional `inbound_inbox_id`; omitting it applies the webhook to all inboxes in the account. - `webhook_type` is a plain string, so the new `inbound_receiving` value needs no enum change; docs updated to list all four types. Specs cover parsing and create/update round-trips. --- examples/webhooks_api.rb | 8 ++ lib/mailtrap/webhook.rb | 6 +- lib/mailtrap/webhooks_api.rb | 13 +++- .../maps_response_data_to_Webhook_object.yml | 73 +++++++++++++++++++ .../maps_response_data_to_Webhook_object.yml | 2 +- ...inbound_inbox_id_to_the_Webhook_object.yml | 73 +++++++++++++++++++ spec/mailtrap/webhook_spec.rb | 2 + spec/mailtrap/webhooks_api_spec.rb | 38 +++++++++- 8 files changed, 209 insertions(+), 6 deletions(-) create mode 100644 spec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_create/when_creating_an_inbound_receiving_webhook/maps_response_data_to_Webhook_object.yml create mode 100644 spec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_get/when_webhook_is_an_inbound_receiving_webhook/maps_inbound_inbox_id_to_the_Webhook_object.yml diff --git a/examples/webhooks_api.rb b/examples/webhooks_api.rb index 88956f87..a02b66f8 100644 --- a/examples/webhooks_api.rb +++ b/examples/webhooks_api.rb @@ -21,6 +21,14 @@ webhook_type: 'audit_log' ) +# Create an `inbound_receiving` webhook (linked to an inbound inbox) +webhooks_api.create( + url: 'https://example.com/mailtrap/inbound', + webhook_type: 'inbound_receiving', + payload_format: 'json', + inbound_inbox_id: 42 +) + # List all webhooks webhooks_api.list # => [#, #] diff --git a/lib/mailtrap/webhook.rb b/lib/mailtrap/webhook.rb index 66e25890..4204789f 100644 --- a/lib/mailtrap/webhook.rb +++ b/lib/mailtrap/webhook.rb @@ -6,12 +6,15 @@ module Mailtrap # @attr_reader id [Integer] The webhook ID # @attr_reader url [String] The URL that will receive webhook payloads # @attr_reader active [Boolean] Whether the webhook is active - # @attr_reader webhook_type [String] The type of webhook (`email_sending` or `audit_log`) + # @attr_reader webhook_type [String] The type of webhook + # (`email_sending`, `campaigns`, `audit_log` or `inbound_receiving`) # @attr_reader payload_format [String] The webhook payload format (`json` or `jsonlines`) # @attr_reader sending_stream [String, nil] The sending stream (`transactional` or `bulk`). # Applicable only for `email_sending` webhooks. # @attr_reader domain_id [Integer, nil] The sending domain ID the webhook is scoped to, # or nil for all domains. Applicable only for `email_sending` webhooks. + # @attr_reader inbound_inbox_id [Integer, nil] The inbound inbox ID the webhook is linked to. + # Applicable only for `inbound_receiving` webhooks. # @attr_reader event_types [Array] The event types the webhook is subscribed to. # Applicable only for `email_sending` webhooks. # @attr_reader signing_secret [String, nil] HMAC SHA-256 signing secret. Returned only on creation. @@ -23,6 +26,7 @@ module Mailtrap :payload_format, :sending_stream, :domain_id, + :inbound_inbox_id, :event_types, :signing_secret, keyword_init: true diff --git a/lib/mailtrap/webhooks_api.rb b/lib/mailtrap/webhooks_api.rb index 30dd3203..82dc0b33 100644 --- a/lib/mailtrap/webhooks_api.rb +++ b/lib/mailtrap/webhooks_api.rb @@ -7,7 +7,8 @@ module Mailtrap class WebhooksAPI include BaseAPI - self.supported_options = %i[url webhook_type active payload_format sending_stream event_types domain_id] + self.supported_options = %i[url webhook_type active payload_format sending_stream event_types domain_id + inbound_inbox_id] self.response_class = Webhook @@ -30,7 +31,8 @@ def get(webhook_id) # Creates a new webhook # @param [Hash] options The parameters to create # @option options [String] :url The URL that will receive webhook payloads - # @option options [String] :webhook_type The type of webhook (`email_sending` or `audit_log`) + # @option options [String] :webhook_type The type of webhook + # (`email_sending`, `campaigns`, `audit_log` or `inbound_receiving`) # @option options [Boolean] :active Whether the webhook is active. Defaults to true. # @option options [String] :payload_format Payload format (`json` or `jsonlines`). Defaults to `json`. # @option options [String] :sending_stream Sending stream (`transactional` or `bulk`). @@ -39,6 +41,9 @@ def get(webhook_id) # Required for `email_sending` webhook type. # @option options [Integer] :domain_id Sending domain ID to scope the webhook to. # Applicable only for `email_sending` webhooks. + # @option options [Integer] :inbound_inbox_id Inbound inbox ID to link the webhook to. + # Applicable only for `inbound_receiving` webhooks; omit to apply to all inboxes + # in the account. # @return [Webhook] Created webhook (includes `signing_secret`) # @!macro api_errors # @raise [ArgumentError] If invalid options are provided @@ -54,11 +59,13 @@ def create(options) # @option options [String] :payload_format Payload format (`json` or `jsonlines`) # @option options [Array] :event_types Event types to subscribe to. # Applicable only for `email_sending` webhooks. + # @option options [Integer] :inbound_inbox_id Inbound inbox ID to link the webhook to. + # Applicable only for `inbound_receiving` webhooks. # @return [Webhook] Updated webhook # @!macro api_errors # @raise [ArgumentError] If invalid options are provided def update(webhook_id, options) - base_update(webhook_id, options, %i[url active payload_format event_types]) + base_update(webhook_id, options, %i[url active payload_format event_types inbound_inbox_id]) end # Deletes a webhook diff --git a/spec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_create/when_creating_an_inbound_receiving_webhook/maps_response_data_to_Webhook_object.yml b/spec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_create/when_creating_an_inbound_receiving_webhook/maps_response_data_to_Webhook_object.yml new file mode 100644 index 00000000..4625d23d --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_create/when_creating_an_inbound_receiving_webhook/maps_response_data_to_Webhook_object.yml @@ -0,0 +1,73 @@ +--- +http_interactions: +- request: + method: post + uri: https://mailtrap.io/api/accounts/1111111/webhooks + body: + encoding: UTF-8 + string: '{"webhook":{"url":"https://example.com/mailtrap/inbound","webhook_type":"inbound_receiving","payload_format":"json","inbound_inbox_id":42}}' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - mailtrap-ruby (https://github.com/mailtrap/mailtrap-ruby) + Host: + - mailtrap.io + Authorization: + - Bearer + Content-Type: + - application/json + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 05 May 2026 06:28:51 GMT + Content-Type: + - application/json; charset=utf-8 + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - cloudflare + X-Frame-Options: + - SAMEORIGIN + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Permitted-Cross-Domain-Policies: + - none + Referrer-Policy: + - strict-origin-when-cross-origin + Vary: + - Accept + X-Mailtrap-Version: + - v2 + X-Ratelimit-Limit: + - '150' + X-Ratelimit-Remaining: + - '149' + Etag: + - W/"64f2974d0941249a1cff76a9a4195937" + Cache-Control: + - max-age=0, private, must-revalidate + X-Runtime: + - '0.047439' + Strict-Transport-Security: + - max-age=2592000; includeSubDomains; preload + Cf-Cache-Status: + - DYNAMIC + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: ASCII-8BIT + string: '{"data":{"signing_secret":"my-signing-secret","id":3081,"url":"https://example.com/mailtrap/inbound","active":true,"webhook_type":"inbound_receiving","payload_format":"json","sending_stream":null,"domain_id":null,"inbound_inbox_id":42,"event_types":[]}}' + recorded_at: Tue, 05 May 2026 06:28:51 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_get/maps_response_data_to_Webhook_object.yml b/spec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_get/maps_response_data_to_Webhook_object.yml index e7ef102a..6a3b5671 100644 --- a/spec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_get/maps_response_data_to_Webhook_object.yml +++ b/spec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_get/maps_response_data_to_Webhook_object.yml @@ -68,6 +68,6 @@ http_interactions: - h3=":443"; ma=86400 body: encoding: ASCII-8BIT - string: '{"data":{"id":3080,"url":"https://example.com/mailtrap/webhooks","active":false,"webhook_type":"email_sending","payload_format":"json","sending_stream":"transactional","domain_id":null,"event_types":["delivery","bounce","unsubscribe"]}}' + string: '{"data":{"id":3080,"url":"https://example.com/mailtrap/webhooks","active":false,"webhook_type":"email_sending","payload_format":"json","sending_stream":"transactional","domain_id":null,"inbound_inbox_id":null,"event_types":["delivery","bounce","unsubscribe"]}}' recorded_at: Tue, 05 May 2026 06:35:34 GMT recorded_with: VCR 6.4.0 diff --git a/spec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_get/when_webhook_is_an_inbound_receiving_webhook/maps_inbound_inbox_id_to_the_Webhook_object.yml b/spec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_get/when_webhook_is_an_inbound_receiving_webhook/maps_inbound_inbox_id_to_the_Webhook_object.yml new file mode 100644 index 00000000..5309d045 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_get/when_webhook_is_an_inbound_receiving_webhook/maps_inbound_inbox_id_to_the_Webhook_object.yml @@ -0,0 +1,73 @@ +--- +http_interactions: +- request: + method: get + uri: https://mailtrap.io/api/accounts/1111111/webhooks/3081 + body: + encoding: US-ASCII + string: '' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - mailtrap-ruby (https://github.com/mailtrap/mailtrap-ruby) + Host: + - mailtrap.io + Authorization: + - Bearer + Content-Type: + - application/json + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 05 May 2026 06:35:34 GMT + Content-Type: + - application/json; charset=utf-8 + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - cloudflare + X-Frame-Options: + - SAMEORIGIN + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Permitted-Cross-Domain-Policies: + - none + Referrer-Policy: + - strict-origin-when-cross-origin + Vary: + - Accept + X-Mailtrap-Version: + - v2 + X-Ratelimit-Limit: + - '150' + X-Ratelimit-Remaining: + - '149' + Etag: + - W/"e051559f5622f3f16fe3fb0861693ff5" + Cache-Control: + - max-age=0, private, must-revalidate + X-Runtime: + - '0.017956' + Strict-Transport-Security: + - max-age=2592000; includeSubDomains; preload + Cf-Cache-Status: + - DYNAMIC + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: ASCII-8BIT + string: '{"data":{"id":3081,"url":"https://example.com/mailtrap/inbound","active":true,"webhook_type":"inbound_receiving","payload_format":"json","sending_stream":null,"domain_id":null,"inbound_inbox_id":42,"event_types":[]}}' + recorded_at: Tue, 05 May 2026 06:35:34 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/mailtrap/webhook_spec.rb b/spec/mailtrap/webhook_spec.rb index 1ea709a5..bdde4d99 100644 --- a/spec/mailtrap/webhook_spec.rb +++ b/spec/mailtrap/webhook_spec.rb @@ -13,6 +13,7 @@ payload_format: 'json', sending_stream: 'transactional', domain_id: 435, + inbound_inbox_id: nil, event_types: %w[delivery bounce], signing_secret: 'a1b2c3d4e5f6' } @@ -27,6 +28,7 @@ payload_format: 'json', sending_stream: 'transactional', domain_id: 435, + inbound_inbox_id: nil, event_types: %w[delivery bounce], signing_secret: 'a1b2c3d4e5f6' ) diff --git a/spec/mailtrap/webhooks_api_spec.rb b/spec/mailtrap/webhooks_api_spec.rb index aa067b3e..64ecca65 100644 --- a/spec/mailtrap/webhooks_api_spec.rb +++ b/spec/mailtrap/webhooks_api_spec.rb @@ -34,10 +34,24 @@ expect(get).to be_a(Mailtrap::Webhook) expect(get).to have_attributes( id: webhook_id, - webhook_type: 'email_sending' + webhook_type: 'email_sending', + inbound_inbox_id: nil ) end + context 'when webhook is an inbound_receiving webhook' do + let(:webhook_id) { 3081 } + + it 'maps inbound_inbox_id to the Webhook object' do + expect(get).to be_a(Mailtrap::Webhook) + expect(get).to have_attributes( + id: webhook_id, + webhook_type: 'inbound_receiving', + inbound_inbox_id: 42 + ) + end + end + context 'when webhook does not exist' do let(:webhook_id) { -1 } @@ -75,6 +89,28 @@ expect(create.signing_secret).not_to be_nil end + context 'when creating an inbound_receiving webhook' do + let(:request) do + { + url: 'https://example.com/mailtrap/inbound', + webhook_type: 'inbound_receiving', + payload_format: 'json', + inbound_inbox_id: 42 + } + end + + it 'maps response data to Webhook object' do + expect(create).to be_a(Mailtrap::Webhook) + expect(create).to have_attributes( + url: 'https://example.com/mailtrap/inbound', + webhook_type: 'inbound_receiving', + payload_format: 'json', + inbound_inbox_id: 42 + ) + expect(create.signing_secret).not_to be_nil + end + end + context 'when invalid options are provided' do let(:request) { { unknown_option: true } }