From 55f9cf19754191e930c9527a965ff7c5725c93a3 Mon Sep 17 00:00:00 2001 From: Igor Dobryn Date: Tue, 28 Apr 2026 15:43:44 +0300 Subject: [PATCH 1/4] Add ContactExportsAPI#create --- examples/contact_exports_api.rb | 21 ++++++ lib/mailtrap.rb | 1 + lib/mailtrap/contact_export.rb | 18 +++++ lib/mailtrap/contact_exports_api.rb | 32 ++++++++ ..._response_data_to_ContactExport_object.yml | 73 +++++++++++++++++++ .../raises_a_Mailtrap_Error.yml | 71 ++++++++++++++++++ spec/mailtrap/contact_export_spec.rb | 21 ++++++ spec/mailtrap/contact_exports_api_spec.rb | 45 ++++++++++++ 8 files changed, 282 insertions(+) create mode 100644 examples/contact_exports_api.rb create mode 100644 lib/mailtrap/contact_export.rb create mode 100644 lib/mailtrap/contact_exports_api.rb create mode 100644 spec/fixtures/vcr_cassettes/Mailtrap_ContactExportsAPI/_create/maps_response_data_to_ContactExport_object.yml create mode 100644 spec/fixtures/vcr_cassettes/Mailtrap_ContactExportsAPI/_create/when_filters_are_malformed/raises_a_Mailtrap_Error.yml create mode 100644 spec/mailtrap/contact_export_spec.rb create mode 100644 spec/mailtrap/contact_exports_api_spec.rb diff --git a/examples/contact_exports_api.rb b/examples/contact_exports_api.rb new file mode 100644 index 0000000..2b89f29 --- /dev/null +++ b/examples/contact_exports_api.rb @@ -0,0 +1,21 @@ +require 'mailtrap' + +account_id = 3229 +client = Mailtrap::Client.new(api_key: 'your-api-key') +contact_exports = Mailtrap::ContactExportsAPI.new(account_id, client) + +# Create a contact export filtered by list IDs +contact_exports.create( + filters: [ + { name: 'list_id', operator: 'equal', value: [1, 2] } + ] +) +# => # + +# Create a contact export filtered by subscription status +contact_exports.create( + filters: [ + { name: 'subscription_status', operator: 'equal', value: 'subscribed' } + ] +) +# => # diff --git a/lib/mailtrap.rb b/lib/mailtrap.rb index bb25971..fc5e26c 100644 --- a/lib/mailtrap.rb +++ b/lib/mailtrap.rb @@ -12,6 +12,7 @@ require_relative 'mailtrap/contact_lists_api' require_relative 'mailtrap/contact_fields_api' require_relative 'mailtrap/contact_imports_api' +require_relative 'mailtrap/contact_exports_api' require_relative 'mailtrap/suppressions_api' require_relative 'mailtrap/sending_domains_api' require_relative 'mailtrap/company_info_api' diff --git a/lib/mailtrap/contact_export.rb b/lib/mailtrap/contact_export.rb new file mode 100644 index 0000000..9443f36 --- /dev/null +++ b/lib/mailtrap/contact_export.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Mailtrap + # Data Transfer Object for Contact Export + # @attr_reader id [Integer] The contact export ID + # @attr_reader status [String] The export status (created, started, finished) + # @attr_reader created_at [String] When the export was created + # @attr_reader updated_at [String] When the export was last updated + # @attr_reader url [String, nil] URL of the exported file (only when status is "finished") + ContactExport = Struct.new( + :id, + :status, + :created_at, + :updated_at, + :url, + keyword_init: true + ) +end diff --git a/lib/mailtrap/contact_exports_api.rb b/lib/mailtrap/contact_exports_api.rb new file mode 100644 index 0000000..28bb931 --- /dev/null +++ b/lib/mailtrap/contact_exports_api.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require_relative 'base_api' +require_relative 'contact_export' + +module Mailtrap + class ContactExportsAPI + include BaseAPI + + self.supported_options = %i[filters].freeze + + self.response_class = ContactExport + + # Creates a new contact export + # @param [Hash] options The export parameters + # @option options [Array] :filters Filters to apply to the export + # - `{ name: 'list_id', operator: 'equal', value: [Integer, ...] }` + # - `{ name: 'subscription_status', operator: 'equal', value: 'subscribed' | 'unsubscribed' }` + # @return [ContactExport] Created contact export object + # @!macro api_errors + # @raise [ArgumentError] If invalid options are provided + def create(options) + base_create(options) + end + + private + + def base_path + "/api/accounts/#{account_id}/contacts/exports" + end + end +end diff --git a/spec/fixtures/vcr_cassettes/Mailtrap_ContactExportsAPI/_create/maps_response_data_to_ContactExport_object.yml b/spec/fixtures/vcr_cassettes/Mailtrap_ContactExportsAPI/_create/maps_response_data_to_ContactExport_object.yml new file mode 100644 index 0000000..26ae00c --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Mailtrap_ContactExportsAPI/_create/maps_response_data_to_ContactExport_object.yml @@ -0,0 +1,73 @@ +--- +http_interactions: +- request: + method: post + uri: https://mailtrap.io/api/accounts/1111111/contacts/exports + body: + encoding: UTF-8 + string: '{"filters":[{"name":"list_id","operator":"equal","value":[1,2]}]}' + 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: 201 + message: Created + headers: + Date: + - Tue, 28 Apr 2026 12:40:45 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '120' + 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/"980ae684fd322575f34563688e3a19bf" + Cache-Control: + - max-age=0, private, must-revalidate + X-Runtime: + - '0.051972' + Strict-Transport-Security: + - max-age=2592000; includeSubDomains; preload + Cf-Cache-Status: + - DYNAMIC + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: UTF-8 + string: '{"id":652,"created_at":"2026-04-28T12:40:45.519Z","updated_at":"2026-04-28T12:40:45.519Z","status":"created","url":null}' + recorded_at: Tue, 28 Apr 2026 12:40:45 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/fixtures/vcr_cassettes/Mailtrap_ContactExportsAPI/_create/when_filters_are_malformed/raises_a_Mailtrap_Error.yml b/spec/fixtures/vcr_cassettes/Mailtrap_ContactExportsAPI/_create/when_filters_are_malformed/raises_a_Mailtrap_Error.yml new file mode 100644 index 0000000..630907f --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Mailtrap_ContactExportsAPI/_create/when_filters_are_malformed/raises_a_Mailtrap_Error.yml @@ -0,0 +1,71 @@ +--- +http_interactions: +- request: + method: post + uri: https://mailtrap.io/api/accounts/1111111/contacts/exports + body: + encoding: UTF-8 + string: '{"filters":"invalid"}' + 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: 422 + message: Unprocessable Entity + headers: + Date: + - Tue, 28 Apr 2026 12:40:46 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '32' + 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: + - '148' + Cache-Control: + - no-cache + X-Runtime: + - '0.027187' + Strict-Transport-Security: + - max-age=2592000; includeSubDomains; preload + Cf-Cache-Status: + - DYNAMIC + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: UTF-8 + string: '{"errors":{"filters":"invalid"}}' + recorded_at: Tue, 28 Apr 2026 12:40:46 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/mailtrap/contact_export_spec.rb b/spec/mailtrap/contact_export_spec.rb new file mode 100644 index 0000000..8972fa0 --- /dev/null +++ b/spec/mailtrap/contact_export_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +RSpec.describe Mailtrap::ContactExport do + describe '#initialize' do + subject(:contact_export) { described_class.new(attributes) } + + let(:attributes) do + { + id: 1, + status: 'started', + created_at: '2021-01-01T00:00:00Z', + updated_at: '2021-01-01T00:00:00Z', + url: nil + } + end + + it 'creates a contact export with all attributes' do + expect(contact_export).to have_attributes(attributes) + end + end +end diff --git a/spec/mailtrap/contact_exports_api_spec.rb b/spec/mailtrap/contact_exports_api_spec.rb new file mode 100644 index 0000000..ab98842 --- /dev/null +++ b/spec/mailtrap/contact_exports_api_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +RSpec.describe Mailtrap::ContactExportsAPI, :vcr do + subject(:contact_exports_api) { described_class.new(account_id, client) } + + let(:account_id) { ENV.fetch('MAILTRAP_ACCOUNT_ID', 1_111_111) } + let(:client) { Mailtrap::Client.new(api_key: ENV.fetch('MAILTRAP_API_KEY', 'local-api-key')) } + + describe '#create' do + subject(:create) { contact_exports_api.create(request) } + + let(:request) do + { + filters: [ + { name: 'list_id', operator: 'equal', value: [1, 2] } + ] + } + end + + it 'maps response data to ContactExport object' do + expect(create).to be_a(Mailtrap::ContactExport) + expect(create).to have_attributes( + id: an_instance_of(Integer), + status: 'created', + url: nil + ) + end + + context 'when invalid options are provided' do + let(:request) { { unknown_option: true } } + + it 'raises ArgumentError' do + expect { create }.to raise_error(ArgumentError, /invalid options are given/) + end + end + + context 'when filters are malformed' do + let(:request) { { filters: 'invalid' } } + + it 'raises a Mailtrap::Error' do + expect { create }.to raise_error(Mailtrap::Error) + end + end + end +end From 2a088c9413fe0e3bce905e6866d66a80fb1ac07e Mon Sep 17 00:00:00 2001 From: Igor Dobryn Date: Tue, 28 Apr 2026 15:48:28 +0300 Subject: [PATCH 2/4] Add ContactExportsAPI#get --- examples/contact_exports_api.rb | 5 ++ lib/mailtrap/contact_exports_api.rb | 8 ++ ..._response_data_to_ContactExport_object.yml | 74 +++++++++++++++++++ .../raises_not_found_error.yml | 71 ++++++++++++++++++ spec/mailtrap/contact_exports_api_spec.rb | 28 +++++++ 5 files changed, 186 insertions(+) create mode 100644 spec/fixtures/vcr_cassettes/Mailtrap_ContactExportsAPI/_get/maps_response_data_to_ContactExport_object.yml create mode 100644 spec/fixtures/vcr_cassettes/Mailtrap_ContactExportsAPI/_get/when_export_does_not_exist/raises_not_found_error.yml diff --git a/examples/contact_exports_api.rb b/examples/contact_exports_api.rb index 2b89f29..1ff1532 100644 --- a/examples/contact_exports_api.rb +++ b/examples/contact_exports_api.rb @@ -19,3 +19,8 @@ ] ) # => # + +# Get a contact export by ID — once status is "finished", `url` is the file location +contact_exports.get(1) +# => # + diff --git a/lib/mailtrap/contact_exports_api.rb b/lib/mailtrap/contact_exports_api.rb index 28bb931..8a3031b 100644 --- a/lib/mailtrap/contact_exports_api.rb +++ b/lib/mailtrap/contact_exports_api.rb @@ -11,6 +11,14 @@ class ContactExportsAPI self.response_class = ContactExport + # Retrieves a specific contact export + # @param export_id [Integer] The contact export ID + # @return [ContactExport] Contact export object + # @!macro api_errors + def get(export_id) + base_get(export_id) + end + # Creates a new contact export # @param [Hash] options The export parameters # @option options [Array] :filters Filters to apply to the export diff --git a/spec/fixtures/vcr_cassettes/Mailtrap_ContactExportsAPI/_get/maps_response_data_to_ContactExport_object.yml b/spec/fixtures/vcr_cassettes/Mailtrap_ContactExportsAPI/_get/maps_response_data_to_ContactExport_object.yml new file mode 100644 index 0000000..9be408f --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Mailtrap_ContactExportsAPI/_get/maps_response_data_to_ContactExport_object.yml @@ -0,0 +1,74 @@ +--- +http_interactions: +- request: + method: get + uri: https://mailtrap.io/api/accounts/1111111/contacts/exports/652 + 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, 28 Apr 2026 12:46:38 GMT + Content-Type: + - application/json; charset=utf-8 + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Server: + - cloudflare + Vary: + - Accept + - Accept-Encoding + 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 + X-Mailtrap-Version: + - v2 + X-Ratelimit-Limit: + - '150' + X-Ratelimit-Remaining: + - '149' + Etag: + - W/"d67f120af9be0ea9a59f5637da0cb5a5" + Cache-Control: + - max-age=0, private, must-revalidate + X-Runtime: + - '0.082833' + Strict-Transport-Security: + - max-age=2592000; includeSubDomains; preload + Cf-Cache-Status: + - DYNAMIC + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: ASCII-8BIT + string: '{"id":652,"created_at":"2026-04-28T12:40:45.519Z","updated_at":"2026-04-28T12:40:45.677Z","status":"finished","url":"https://my-export.csv.gz"}' + recorded_at: Tue, 28 Apr 2026 12:46:38 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/fixtures/vcr_cassettes/Mailtrap_ContactExportsAPI/_get/when_export_does_not_exist/raises_not_found_error.yml b/spec/fixtures/vcr_cassettes/Mailtrap_ContactExportsAPI/_get/when_export_does_not_exist/raises_not_found_error.yml new file mode 100644 index 0000000..2a0833f --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Mailtrap_ContactExportsAPI/_get/when_export_does_not_exist/raises_not_found_error.yml @@ -0,0 +1,71 @@ +--- +http_interactions: +- request: + method: get + uri: https://mailtrap.io/api/accounts/1111111/contacts/exports/-1 + 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: 404 + message: Not Found + headers: + Date: + - Tue, 28 Apr 2026 12:46:39 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '21' + 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: + - '148' + Cache-Control: + - no-cache + X-Runtime: + - '0.016933' + Strict-Transport-Security: + - max-age=2592000; includeSubDomains; preload + Cf-Cache-Status: + - DYNAMIC + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: UTF-8 + string: '{"error":"Not Found"}' + recorded_at: Tue, 28 Apr 2026 12:46:39 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/mailtrap/contact_exports_api_spec.rb b/spec/mailtrap/contact_exports_api_spec.rb index ab98842..d661d34 100644 --- a/spec/mailtrap/contact_exports_api_spec.rb +++ b/spec/mailtrap/contact_exports_api_spec.rb @@ -6,6 +6,34 @@ let(:account_id) { ENV.fetch('MAILTRAP_ACCOUNT_ID', 1_111_111) } let(:client) { Mailtrap::Client.new(api_key: ENV.fetch('MAILTRAP_API_KEY', 'local-api-key')) } + describe '#get' do + subject(:get) { contact_exports_api.get(export_id) } + + let(:export_id) { 652 } + + it 'maps response data to ContactExport object' do + expect(get).to be_a(Mailtrap::ContactExport) + expect(get).to have_attributes( + id: export_id, + status: an_instance_of(String), + created_at: an_instance_of(String), + updated_at: an_instance_of(String) + ) + end + + context 'when export does not exist' do + let(:export_id) { -1 } + + it 'raises not found error' do + expect { get }.to raise_error do |error| + expect(error).to be_a(Mailtrap::Error) + expect(error.message).to include('Not Found') + expect(error.messages.any? { |msg| msg.include?('Not Found') }).to be true + end + end + end + end + describe '#create' do subject(:create) { contact_exports_api.create(request) } From f0c37d32ab8b47606dbe09df79bd7108f245d3e7 Mon Sep 17 00:00:00 2001 From: Igor Dobryn Date: Tue, 28 Apr 2026 16:41:05 +0300 Subject: [PATCH 3/4] Add ContactEventsAPI#create --- examples/contact_events_api.rb | 21 ++++++ lib/mailtrap.rb | 1 + lib/mailtrap/contact_event.rb | 16 ++++ lib/mailtrap/contact_events_api.rb | 34 +++++++++ ...s_response_data_to_ContactEvent_object.yml | 73 +++++++++++++++++++ .../raises_not_found_error.yml | 71 ++++++++++++++++++ .../raises_a_Mailtrap_Error.yml | 71 ++++++++++++++++++ spec/mailtrap/contact_event_spec.rb | 20 +++++ spec/mailtrap/contact_events_api_spec.rb | 56 ++++++++++++++ 9 files changed, 363 insertions(+) create mode 100644 examples/contact_events_api.rb create mode 100644 lib/mailtrap/contact_event.rb create mode 100644 lib/mailtrap/contact_events_api.rb create mode 100644 spec/fixtures/vcr_cassettes/Mailtrap_ContactEventsAPI/_create/maps_response_data_to_ContactEvent_object.yml create mode 100644 spec/fixtures/vcr_cassettes/Mailtrap_ContactEventsAPI/_create/when_contact_does_not_exist/raises_not_found_error.yml create mode 100644 spec/fixtures/vcr_cassettes/Mailtrap_ContactEventsAPI/_create/when_name_is_missing/raises_a_Mailtrap_Error.yml create mode 100644 spec/mailtrap/contact_event_spec.rb create mode 100644 spec/mailtrap/contact_events_api_spec.rb diff --git a/examples/contact_events_api.rb b/examples/contact_events_api.rb new file mode 100644 index 0000000..a2abd6a --- /dev/null +++ b/examples/contact_events_api.rb @@ -0,0 +1,21 @@ +require 'mailtrap' + +account_id = 3229 +client = Mailtrap::Client.new(api_key: 'your-api-key') +contact_events = Mailtrap::ContactEventsAPI.new(account_id, client) + +# Submit a custom event for a contact (identifier can be UUID or email) +contact_events.create( + 'john.smith@example.com', + name: 'UserLogin', + params: { + user_id: 101, + user_name: 'John Smith', + is_active: true, + last_seen: nil + } +) +# => #101, ...}> diff --git a/lib/mailtrap.rb b/lib/mailtrap.rb index fc5e26c..d94e809 100644 --- a/lib/mailtrap.rb +++ b/lib/mailtrap.rb @@ -13,6 +13,7 @@ require_relative 'mailtrap/contact_fields_api' require_relative 'mailtrap/contact_imports_api' require_relative 'mailtrap/contact_exports_api' +require_relative 'mailtrap/contact_events_api' require_relative 'mailtrap/suppressions_api' require_relative 'mailtrap/sending_domains_api' require_relative 'mailtrap/company_info_api' diff --git a/lib/mailtrap/contact_event.rb b/lib/mailtrap/contact_event.rb new file mode 100644 index 0000000..8038ac2 --- /dev/null +++ b/lib/mailtrap/contact_event.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Mailtrap + # Data Transfer Object for Contact Event + # @attr_reader contact_id [String] The contact ID (UUID) + # @attr_reader contact_email [String] The contact email + # @attr_reader name [String] The event name + # @attr_reader params [Hash] The event parameters (string keys, scalar values) + ContactEvent = Struct.new( + :contact_id, + :contact_email, + :name, + :params, + keyword_init: true + ) +end diff --git a/lib/mailtrap/contact_events_api.rb b/lib/mailtrap/contact_events_api.rb new file mode 100644 index 0000000..08e093b --- /dev/null +++ b/lib/mailtrap/contact_events_api.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require_relative 'base_api' +require_relative 'contact_event' + +module Mailtrap + class ContactEventsAPI + include BaseAPI + + self.supported_options = %i[name params].freeze + + self.response_class = ContactEvent + + # Creates a contact event + # @param contact_identifier [String] The contact UUID or email address + # @param [Hash] options The event parameters + # @option options [String] :name The event name (max 255 characters) + # @option options [Hash] :params A hash of string keys and scalar values + # @return [ContactEvent] Created contact event object + # @!macro api_errors + # @raise [ArgumentError] If invalid options are provided + def create(contact_identifier, options) + validate_options!(options, supported_options) + response = client.post(base_path(contact_identifier), options) + build_entity(response, ContactEvent) + end + + private + + def base_path(contact_identifier) + "/api/accounts/#{account_id}/contacts/#{contact_identifier}/events" + end + end +end diff --git a/spec/fixtures/vcr_cassettes/Mailtrap_ContactEventsAPI/_create/maps_response_data_to_ContactEvent_object.yml b/spec/fixtures/vcr_cassettes/Mailtrap_ContactEventsAPI/_create/maps_response_data_to_ContactEvent_object.yml new file mode 100644 index 0000000..d99f83a --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Mailtrap_ContactEventsAPI/_create/maps_response_data_to_ContactEvent_object.yml @@ -0,0 +1,73 @@ +--- +http_interactions: +- request: + method: post + uri: https://mailtrap.io/api/accounts/1111111/contacts/john.smith@example.com/events + body: + encoding: UTF-8 + string: '{"name":"UserLogin","params":{"user_id":101,"is_active":true}}' + 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: 201 + message: Created + headers: + Date: + - Tue, 28 Apr 2026 13:02:24 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '155' + 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/"e77948f2f73f7c3e398437895b28c7aa" + Cache-Control: + - max-age=0, private, must-revalidate + X-Runtime: + - '0.036336' + Strict-Transport-Security: + - max-age=2592000; includeSubDomains; preload + Cf-Cache-Status: + - DYNAMIC + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: UTF-8 + string: '{"contact_id":"0199090c-b5ec-7be8-b18f-1a7c8a64f9fb","contact_email":"john.smith@example.com","name":"UserLogin","params":{"user_id":101,"is_active":true}}' + recorded_at: Tue, 28 Apr 2026 13:02:25 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/fixtures/vcr_cassettes/Mailtrap_ContactEventsAPI/_create/when_contact_does_not_exist/raises_not_found_error.yml b/spec/fixtures/vcr_cassettes/Mailtrap_ContactEventsAPI/_create/when_contact_does_not_exist/raises_not_found_error.yml new file mode 100644 index 0000000..180ddb9 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Mailtrap_ContactEventsAPI/_create/when_contact_does_not_exist/raises_not_found_error.yml @@ -0,0 +1,71 @@ +--- +http_interactions: +- request: + method: post + uri: https://mailtrap.io/api/accounts/1111111/contacts/nonexistent@example.com/events + body: + encoding: UTF-8 + string: '{"name":"UserLogin","params":{"user_id":101,"is_active":true}}' + 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: 404 + message: Not Found + headers: + Date: + - Tue, 28 Apr 2026 13:02:26 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '21' + 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: + - '147' + Cache-Control: + - no-cache + X-Runtime: + - '0.035960' + Strict-Transport-Security: + - max-age=2592000; includeSubDomains; preload + Cf-Cache-Status: + - DYNAMIC + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: UTF-8 + string: '{"error":"Not Found"}' + recorded_at: Tue, 28 Apr 2026 13:02:26 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/fixtures/vcr_cassettes/Mailtrap_ContactEventsAPI/_create/when_name_is_missing/raises_a_Mailtrap_Error.yml b/spec/fixtures/vcr_cassettes/Mailtrap_ContactEventsAPI/_create/when_name_is_missing/raises_a_Mailtrap_Error.yml new file mode 100644 index 0000000..1fe9d8b --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Mailtrap_ContactEventsAPI/_create/when_name_is_missing/raises_a_Mailtrap_Error.yml @@ -0,0 +1,71 @@ +--- +http_interactions: +- request: + method: post + uri: https://mailtrap.io/api/accounts/1111111/contacts/john.smith@example.com/events + body: + encoding: UTF-8 + string: '{"params":{"user_id":101}}' + 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: 422 + message: Unprocessable Entity + headers: + Date: + - Tue, 28 Apr 2026 13:02:25 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '57' + 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: + - '148' + Cache-Control: + - no-cache + X-Runtime: + - '0.027400' + Strict-Transport-Security: + - max-age=2592000; includeSubDomains; preload + Cf-Cache-Status: + - DYNAMIC + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: UTF-8 + string: '{"errors":{"name":["can''t be blank","must be a string"]}}' + recorded_at: Tue, 28 Apr 2026 13:02:25 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/mailtrap/contact_event_spec.rb b/spec/mailtrap/contact_event_spec.rb new file mode 100644 index 0000000..320c0fe --- /dev/null +++ b/spec/mailtrap/contact_event_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +RSpec.describe Mailtrap::ContactEvent do + describe '#initialize' do + subject(:contact_event) { described_class.new(attributes) } + + let(:attributes) do + { + contact_id: '0199090c-b5ec-7be8-b18f-1a7c8a64f9fb', + contact_email: 'john.smith@example.com', + name: 'UserLogin', + params: { 'user_id' => 101, 'is_active' => true } + } + end + + it 'creates a contact event with all attributes' do + expect(contact_event).to have_attributes(attributes) + end + end +end diff --git a/spec/mailtrap/contact_events_api_spec.rb b/spec/mailtrap/contact_events_api_spec.rb new file mode 100644 index 0000000..88bf80b --- /dev/null +++ b/spec/mailtrap/contact_events_api_spec.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +RSpec.describe Mailtrap::ContactEventsAPI, :vcr do + subject(:contact_events_api) { described_class.new(account_id, client) } + + let(:account_id) { ENV.fetch('MAILTRAP_ACCOUNT_ID', 1_111_111) } + let(:client) { Mailtrap::Client.new(api_key: ENV.fetch('MAILTRAP_API_KEY', 'local-api-key')) } + let(:contact_identifier) { 'john.smith@example.com' } + + describe '#create' do + subject(:create) { contact_events_api.create(contact_identifier, request) } + + let(:request) do + { + name: 'UserLogin', + params: { user_id: 101, is_active: true } + } + end + + it 'maps response data to ContactEvent object' do + expect(create).to be_a(Mailtrap::ContactEvent) + expect(create).to have_attributes( + contact_email: contact_identifier, + name: 'UserLogin' + ) + end + + context 'when invalid options are provided' do + let(:request) { { unknown_option: true } } + + it 'raises ArgumentError' do + expect { create }.to raise_error(ArgumentError, /invalid options are given/) + end + end + + context 'when name is missing' do + let(:request) { { params: { user_id: 101 } } } + + it 'raises a Mailtrap::Error' do + expect { create }.to raise_error(Mailtrap::Error) + end + end + + context 'when contact does not exist' do + let(:contact_identifier) { 'nonexistent@example.com' } + + it 'raises not found error' do + expect { create }.to raise_error do |error| + expect(error).to be_a(Mailtrap::Error) + expect(error.message).to include('Not Found') + expect(error.messages.any? { |msg| msg.include?('Not Found') }).to be true + end + end + end + end +end From 0571463a5bcb9e5187595032b8e510b4c5d7ee56 Mon Sep 17 00:00:00 2001 From: Igor Dobryn Date: Tue, 28 Apr 2026 16:55:18 +0300 Subject: [PATCH 4/4] Linter fixes --- examples/contact_exports_api.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/contact_exports_api.rb b/examples/contact_exports_api.rb index 1ff1532..66f3ca8 100644 --- a/examples/contact_exports_api.rb +++ b/examples/contact_exports_api.rb @@ -23,4 +23,3 @@ # Get a contact export by ID — once status is "finished", `url` is the file location contact_exports.get(1) # => # -