From 382fdde50e90b0bd0472c72fb64cca70eaae44f8 Mon Sep 17 00:00:00 2001 From: Igor Dobryn Date: Tue, 28 Apr 2026 17:12:32 +0300 Subject: [PATCH 1/7] Add ApiTokensAPI#list --- examples/api_tokens_api.rb | 9 +++ lib/mailtrap.rb | 1 + lib/mailtrap/api_token.rb | 22 ++++++ lib/mailtrap/api_tokens_api.rb | 25 +++++++ ...maps_response_data_to_ApiToken_objects.yml | 74 +++++++++++++++++++ spec/mailtrap/api_token_spec.rb | 23 ++++++ spec/mailtrap/api_tokens_api_spec.rb | 21 ++++++ 7 files changed, 175 insertions(+) create mode 100644 examples/api_tokens_api.rb create mode 100644 lib/mailtrap/api_token.rb create mode 100644 lib/mailtrap/api_tokens_api.rb create mode 100644 spec/fixtures/vcr_cassettes/Mailtrap_ApiTokensAPI/_list/maps_response_data_to_ApiToken_objects.yml create mode 100644 spec/mailtrap/api_token_spec.rb create mode 100644 spec/mailtrap/api_tokens_api_spec.rb diff --git a/examples/api_tokens_api.rb b/examples/api_tokens_api.rb new file mode 100644 index 0000000..7aec743 --- /dev/null +++ b/examples/api_tokens_api.rb @@ -0,0 +1,9 @@ +require 'mailtrap' + +account_id = 3229 +client = Mailtrap::Client.new(api_key: 'your-api-key') +api_tokens = Mailtrap::ApiTokensAPI.new(account_id, client) + +# List API tokens +api_tokens.list +# => [#, ...] diff --git a/lib/mailtrap.rb b/lib/mailtrap.rb index d94e809..106e2e2 100644 --- a/lib/mailtrap.rb +++ b/lib/mailtrap.rb @@ -6,6 +6,7 @@ require_relative 'mailtrap/version' require_relative 'mailtrap/accounts_api' require_relative 'mailtrap/account_accesses_api' +require_relative 'mailtrap/api_tokens_api' require_relative 'mailtrap/billing_api' require_relative 'mailtrap/email_templates_api' require_relative 'mailtrap/contacts_api' diff --git a/lib/mailtrap/api_token.rb b/lib/mailtrap/api_token.rb new file mode 100644 index 0000000..11da232 --- /dev/null +++ b/lib/mailtrap/api_token.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Mailtrap + # Data Transfer Object for API Token + # @attr_reader id [Integer] The API token ID + # @attr_reader name [String] Token display name + # @attr_reader last_4_digits [String] Last 4 characters of the token + # @attr_reader created_by [String] Name of the user or token that created this token + # @attr_reader expires_at [String, nil] When the token expires (ISO 8601); nil if it does not expire + # @attr_reader resources [Array] Permissions granted to this token + # @attr_reader token [String, nil] Full token value — only populated by #create and #reset + ApiToken = Struct.new( + :id, + :name, + :last_4_digits, + :created_by, + :expires_at, + :resources, + :token, + keyword_init: true + ) +end diff --git a/lib/mailtrap/api_tokens_api.rb b/lib/mailtrap/api_tokens_api.rb new file mode 100644 index 0000000..4333238 --- /dev/null +++ b/lib/mailtrap/api_tokens_api.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require_relative 'base_api' +require_relative 'api_token' + +module Mailtrap + class ApiTokensAPI + include BaseAPI + + self.response_class = ApiToken + + # Lists API tokens visible to the current API token + # @return [Array] Array of API tokens + # @!macro api_errors + def list + base_list + end + + private + + def base_path + "/api/accounts/#{account_id}/api_tokens" + end + end +end diff --git a/spec/fixtures/vcr_cassettes/Mailtrap_ApiTokensAPI/_list/maps_response_data_to_ApiToken_objects.yml b/spec/fixtures/vcr_cassettes/Mailtrap_ApiTokensAPI/_list/maps_response_data_to_ApiToken_objects.yml new file mode 100644 index 0000000..8b9e29e --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Mailtrap_ApiTokensAPI/_list/maps_response_data_to_ApiToken_objects.yml @@ -0,0 +1,74 @@ +--- +http_interactions: +- request: + method: get + uri: https://mailtrap.io/api/accounts/1111111/api_tokens + 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 14:05:37 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/"7e2dfc9492802e3dbd170f4b6324efb3" + Cache-Control: + - max-age=0, private, must-revalidate + X-Runtime: + - '0.038392' + Strict-Transport-Security: + - max-age=2592000; includeSubDomains; preload + Cf-Cache-Status: + - DYNAMIC + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: ASCII-8BIT + string: '[{"resources":[{"resource_type":"account","resource_id":1719941,"access_level":100}],"id":2496915,"name":"Ruby SDK Dev","last_4_digits":"9daa","created_by":"John Doe","expires_at":null}]' + recorded_at: Tue, 28 Apr 2026 14:05:37 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/mailtrap/api_token_spec.rb b/spec/mailtrap/api_token_spec.rb new file mode 100644 index 0000000..5d06582 --- /dev/null +++ b/spec/mailtrap/api_token_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +RSpec.describe Mailtrap::ApiToken do + describe '#initialize' do + subject(:api_token) { described_class.new(attributes) } + + let(:attributes) do + { + id: 12_345, + name: 'My API Token', + last_4_digits: 'x7k9', + created_by: 'user@example.com', + expires_at: nil, + resources: [{ resource_type: 'account', resource_id: 3229, access_level: 100 }], + token: 'a1b2c3d4e5f6g7h8' + } + end + + it 'creates an api token with all attributes' do + expect(api_token).to have_attributes(attributes) + end + end +end diff --git a/spec/mailtrap/api_tokens_api_spec.rb b/spec/mailtrap/api_tokens_api_spec.rb new file mode 100644 index 0000000..43a66eb --- /dev/null +++ b/spec/mailtrap/api_tokens_api_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +RSpec.describe Mailtrap::ApiTokensAPI, :vcr do + subject(:api_tokens_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 '#list' do + subject(:list) { api_tokens_api.list } + + it 'maps response data to ApiToken objects' do + expect(list).to all(be_a(Mailtrap::ApiToken)) + expect(list.first).to have_attributes( + id: an_instance_of(Integer), + name: an_instance_of(String), + token: nil + ) + end + end +end From 596b994a9bb450f07b6bad63303682501d9bcd6c Mon Sep 17 00:00:00 2001 From: Igor Dobryn Date: Tue, 28 Apr 2026 17:46:41 +0300 Subject: [PATCH 2/7] Add PermissionsAPI#bulk_update --- examples/permissions_api.rb | 15 ++++ lib/mailtrap.rb | 1 + lib/mailtrap/client.rb | 16 ++++ lib/mailtrap/permissions_api.rb | 29 ++++++++ .../returns_the_API_success_message.yml | 73 +++++++++++++++++++ .../raises_not_found_error.yml | 71 ++++++++++++++++++ spec/mailtrap/permissions_api_spec.rb | 35 +++++++++ 7 files changed, 240 insertions(+) create mode 100644 examples/permissions_api.rb create mode 100644 lib/mailtrap/permissions_api.rb create mode 100644 spec/fixtures/vcr_cassettes/Mailtrap_PermissionsAPI/_bulk_update/returns_the_API_success_message.yml create mode 100644 spec/fixtures/vcr_cassettes/Mailtrap_PermissionsAPI/_bulk_update/when_account_access_does_not_exist/raises_not_found_error.yml create mode 100644 spec/mailtrap/permissions_api_spec.rb diff --git a/examples/permissions_api.rb b/examples/permissions_api.rb new file mode 100644 index 0000000..4133453 --- /dev/null +++ b/examples/permissions_api.rb @@ -0,0 +1,15 @@ +require 'mailtrap' + +account_id = 3229 +client = Mailtrap::Client.new(api_key: 'your-api-key') +permissions = Mailtrap::PermissionsAPI.new(account_id, client) + +# Bulk-update user/token permissions on an account access. Combine create/update with destroy: +permissions.bulk_update( + 5142, # account_access_id + [ + { resource_id: '3281', resource_type: 'account', access_level: 'viewer' }, + { resource_id: '3809', resource_type: 'inbox', _destroy: true } + ] +) +# => { message: "Permissions have been updated!" } diff --git a/lib/mailtrap.rb b/lib/mailtrap.rb index 106e2e2..7bdad11 100644 --- a/lib/mailtrap.rb +++ b/lib/mailtrap.rb @@ -7,6 +7,7 @@ require_relative 'mailtrap/accounts_api' require_relative 'mailtrap/account_accesses_api' require_relative 'mailtrap/api_tokens_api' +require_relative 'mailtrap/permissions_api' require_relative 'mailtrap/billing_api' require_relative 'mailtrap/email_templates_api' require_relative 'mailtrap/contacts_api' diff --git a/lib/mailtrap/client.rb b/lib/mailtrap/client.rb index 321db1d..db2af43 100644 --- a/lib/mailtrap/client.rb +++ b/lib/mailtrap/client.rb @@ -207,6 +207,20 @@ def patch(path, body = nil) ) end + # Performs a PUT request to the specified path + # @param path [String] The request path + # @param body [Hash] The request body + # @return [Hash, String, nil] JSON response or raw response body + # @!macro api_errors + def put(path, body = nil) + perform_request( + method: :put, + host: general_api_host, + path:, + body: + ) + end + # Performs a DELETE request to the specified path # @param path [String] The request path # @return [Hash, String, nil] JSON response or raw response body @@ -261,6 +275,8 @@ def setup_request(method, uri_or_path, body = nil) Net::HTTP::Post.new(uri_or_path) when :patch Net::HTTP::Patch.new(uri_or_path) + when :put + Net::HTTP::Put.new(uri_or_path) when :delete Net::HTTP::Delete.new(uri_or_path) else diff --git a/lib/mailtrap/permissions_api.rb b/lib/mailtrap/permissions_api.rb new file mode 100644 index 0000000..5fa7b78 --- /dev/null +++ b/lib/mailtrap/permissions_api.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require_relative 'base_api' + +module Mailtrap + class PermissionsAPI + include BaseAPI + + # Bulk-updates user or token permissions on an account access + # @param account_access_id [Integer] The account access ID + # @param permissions [Array] Array of permission entries + # - `{ resource_id:, resource_type:, access_level: }` to create or update + # - `{ resource_id:, resource_type:, _destroy: true }` to remove + # @return [Hash] API response (e.g. `{ message: 'Permissions have been updated!' }`) + # @!macro api_errors + def bulk_update(account_access_id, permissions) + client.put( + "#{base_path}/account_accesses/#{account_access_id}/permissions/bulk", + { permissions: permissions } + ) + end + + private + + def base_path + "/api/accounts/#{account_id}" + end + end +end diff --git a/spec/fixtures/vcr_cassettes/Mailtrap_PermissionsAPI/_bulk_update/returns_the_API_success_message.yml b/spec/fixtures/vcr_cassettes/Mailtrap_PermissionsAPI/_bulk_update/returns_the_API_success_message.yml new file mode 100644 index 0000000..64a1da4 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Mailtrap_PermissionsAPI/_bulk_update/returns_the_API_success_message.yml @@ -0,0 +1,73 @@ +--- +http_interactions: +- request: + method: put + uri: https://mailtrap.io/api/accounts/1111111/account_accesses/5339621/permissions/bulk + body: + encoding: UTF-8 + string: '{"permissions":[{"resource_id":"1719941","resource_type":"account","access_level":"viewer"}]}' + 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 14:43:16 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '44' + 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/"a6f38e5f5257d228ea419b1576fa9aa4" + Cache-Control: + - max-age=0, private, must-revalidate + X-Runtime: + - '0.066854' + Strict-Transport-Security: + - max-age=2592000; includeSubDomains; preload + Cf-Cache-Status: + - DYNAMIC + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: UTF-8 + string: '{"message":"Permissions have been updated!"}' + recorded_at: Tue, 28 Apr 2026 14:43:16 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/fixtures/vcr_cassettes/Mailtrap_PermissionsAPI/_bulk_update/when_account_access_does_not_exist/raises_not_found_error.yml b/spec/fixtures/vcr_cassettes/Mailtrap_PermissionsAPI/_bulk_update/when_account_access_does_not_exist/raises_not_found_error.yml new file mode 100644 index 0000000..ee772ef --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Mailtrap_PermissionsAPI/_bulk_update/when_account_access_does_not_exist/raises_not_found_error.yml @@ -0,0 +1,71 @@ +--- +http_interactions: +- request: + method: put + uri: https://mailtrap.io/api/accounts/1111111/account_accesses/-1/permissions/bulk + body: + encoding: UTF-8 + string: '{"permissions":[{"resource_id":"1719941","resource_type":"account","access_level":"viewer"}]}' + 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 14:45:02 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: + - '149' + Cache-Control: + - no-cache + X-Runtime: + - '0.021482' + 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 14:45:02 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/mailtrap/permissions_api_spec.rb b/spec/mailtrap/permissions_api_spec.rb new file mode 100644 index 0000000..809d99f --- /dev/null +++ b/spec/mailtrap/permissions_api_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +RSpec.describe Mailtrap::PermissionsAPI, :vcr do + subject(:permissions_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 '#bulk_update' do + subject(:bulk_update) { permissions_api.bulk_update(account_access_id, permissions) } + + let(:account_access_id) { 5339621 } + let(:permissions) do + [ + { resource_id: '1719941', resource_type: 'account', access_level: 'viewer' } + ] + end + + it 'returns the API success message' do + expect(bulk_update).to include(message: an_instance_of(String)) + end + + context 'when account access does not exist' do + let(:account_access_id) { -1 } + + it 'raises not found error' do + expect { bulk_update }.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 361304e5efb2021224ad5b890f9b7099f8db24c3 Mon Sep 17 00:00:00 2001 From: Igor Dobryn Date: Wed, 29 Apr 2026 14:56:40 +0300 Subject: [PATCH 3/7] Add SubAccountsAPI#create --- examples/sub_accounts_api.rb | 9 +++ lib/mailtrap.rb | 1 + lib/mailtrap/sub_account.rb | 12 +++ lib/mailtrap/sub_accounts_api.rb | 46 ++++++++++++ ...aps_response_data_to_SubAccount_object.yml | 73 +++++++++++++++++++ .../raises_a_Mailtrap_Error.yml | 49 +++++++++++++ spec/mailtrap/sub_account_spec.rb | 13 ++++ spec/mailtrap/sub_accounts_api_spec.rb | 45 ++++++++++++ spec/spec_helper.rb | 2 + 9 files changed, 250 insertions(+) create mode 100644 examples/sub_accounts_api.rb create mode 100644 lib/mailtrap/sub_account.rb create mode 100644 lib/mailtrap/sub_accounts_api.rb create mode 100644 spec/fixtures/vcr_cassettes/Mailtrap_SubAccountsAPI/_create/maps_response_data_to_SubAccount_object.yml create mode 100644 spec/fixtures/vcr_cassettes/Mailtrap_SubAccountsAPI/_create/when_name_is_missing/raises_a_Mailtrap_Error.yml create mode 100644 spec/mailtrap/sub_account_spec.rb create mode 100644 spec/mailtrap/sub_accounts_api_spec.rb diff --git a/examples/sub_accounts_api.rb b/examples/sub_accounts_api.rb new file mode 100644 index 0000000..9ae7e5f --- /dev/null +++ b/examples/sub_accounts_api.rb @@ -0,0 +1,9 @@ +require 'mailtrap' + +organization_id = 4567 +client = Mailtrap::Client.new(api_key: 'your-api-key') +sub_accounts = Mailtrap::SubAccountsAPI.new(organization_id, client) + +# Create a new sub account +sub_accounts.create(name: 'New Team Account') +# => # diff --git a/lib/mailtrap.rb b/lib/mailtrap.rb index 7bdad11..f5e52a8 100644 --- a/lib/mailtrap.rb +++ b/lib/mailtrap.rb @@ -8,6 +8,7 @@ require_relative 'mailtrap/account_accesses_api' require_relative 'mailtrap/api_tokens_api' require_relative 'mailtrap/permissions_api' +require_relative 'mailtrap/sub_accounts_api' require_relative 'mailtrap/billing_api' require_relative 'mailtrap/email_templates_api' require_relative 'mailtrap/contacts_api' diff --git a/lib/mailtrap/sub_account.rb b/lib/mailtrap/sub_account.rb new file mode 100644 index 0000000..47a8baf --- /dev/null +++ b/lib/mailtrap/sub_account.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module Mailtrap + # Data Transfer Object for an organization sub account + # @attr_reader id [Integer] The sub account ID + # @attr_reader name [String] The sub account name + SubAccount = Struct.new( + :id, + :name, + keyword_init: true + ) +end diff --git a/lib/mailtrap/sub_accounts_api.rb b/lib/mailtrap/sub_accounts_api.rb new file mode 100644 index 0000000..5990153 --- /dev/null +++ b/lib/mailtrap/sub_accounts_api.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require_relative 'base_api' +require_relative 'sub_account' + +module Mailtrap + class SubAccountsAPI + include BaseAPI + + attr_reader :organization_id + + self.supported_options = %i[name].freeze + + self.response_class = SubAccount + + # @param organization_id [Integer] The organization ID + # @param client [Mailtrap::Client] The client instance + # @raise [ArgumentError] If organization_id is nil + def initialize(organization_id, client = Mailtrap::Client.new) + raise ArgumentError, 'organization_id is required' if organization_id.nil? + + @organization_id = organization_id + @client = client + end + + # Creates a new sub account under the organization + # @param [Hash] options The parameters to create + # @option options [String] :name Name of the sub account + # @return [SubAccount] Created sub account + # @!macro api_errors + # @raise [ArgumentError] If invalid options are provided + def create(options) + base_create(options) + end + + private + + def base_path + "/api/organizations/#{organization_id}/sub_accounts" + end + + def wrap_request(options) + { account: options } + end + end +end diff --git a/spec/fixtures/vcr_cassettes/Mailtrap_SubAccountsAPI/_create/maps_response_data_to_SubAccount_object.yml b/spec/fixtures/vcr_cassettes/Mailtrap_SubAccountsAPI/_create/maps_response_data_to_SubAccount_object.yml new file mode 100644 index 0000000..4b03f13 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Mailtrap_SubAccountsAPI/_create/maps_response_data_to_SubAccount_object.yml @@ -0,0 +1,73 @@ +--- +http_interactions: +- request: + method: post + uri: https://mailtrap.io/api/organizations/2222222/sub_accounts + body: + encoding: UTF-8 + string: '{"account":{"name":"SDK Test Sub Account"}}' + 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: + - Wed, 29 Apr 2026 11:44:41 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '44' + 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/"0b27462f8e3ad4f086b8334c71808f6a" + Cache-Control: + - max-age=0, private, must-revalidate + X-Runtime: + - '0.853483' + Strict-Transport-Security: + - max-age=2592000; includeSubDomains; preload + Cf-Cache-Status: + - DYNAMIC + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: UTF-8 + string: '{"id":2704237,"name":"SDK Test Sub Account"}' + recorded_at: Wed, 29 Apr 2026 11:44:41 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/fixtures/vcr_cassettes/Mailtrap_SubAccountsAPI/_create/when_name_is_missing/raises_a_Mailtrap_Error.yml b/spec/fixtures/vcr_cassettes/Mailtrap_SubAccountsAPI/_create/when_name_is_missing/raises_a_Mailtrap_Error.yml new file mode 100644 index 0000000..8235a7d --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Mailtrap_SubAccountsAPI/_create/when_name_is_missing/raises_a_Mailtrap_Error.yml @@ -0,0 +1,49 @@ +--- +http_interactions: +- request: + method: post + uri: https://mailtrap.io/api/organizations/2222222/sub_accounts + body: + encoding: UTF-8 + string: '{"account":{}}' + 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: 400 + message: Bad Request + headers: + Date: + - Wed, 29 Apr 2026 11:44:42 GMT + Content-Type: + - text/html; charset=UTF-8 + Content-Length: + - '0' + Connection: + - keep-alive + Server: + - cloudflare + X-Runtime: + - '0.016264' + Strict-Transport-Security: + - max-age=2592000; includeSubDomains; preload + Cf-Cache-Status: + - DYNAMIC + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: UTF-8 + string: '' + recorded_at: Wed, 29 Apr 2026 11:44:42 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/mailtrap/sub_account_spec.rb b/spec/mailtrap/sub_account_spec.rb new file mode 100644 index 0000000..c1054f1 --- /dev/null +++ b/spec/mailtrap/sub_account_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +RSpec.describe Mailtrap::SubAccount do + describe '#initialize' do + subject(:sub_account) { described_class.new(attributes) } + + let(:attributes) { { id: 12_345, name: 'Development Team Account' } } + + it 'creates a sub account with all attributes' do + expect(sub_account).to have_attributes(attributes) + end + end +end diff --git a/spec/mailtrap/sub_accounts_api_spec.rb b/spec/mailtrap/sub_accounts_api_spec.rb new file mode 100644 index 0000000..46e64ca --- /dev/null +++ b/spec/mailtrap/sub_accounts_api_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +RSpec.describe Mailtrap::SubAccountsAPI, :vcr do + subject(:sub_accounts_api) { described_class.new(organization_id, client) } + + let(:organization_id) { ENV.fetch('MAILTRAP_ORGANIZATION_ID', 1_111_111) } + let(:client) { Mailtrap::Client.new(api_key: ENV.fetch('MAILTRAP_ORGANIZATION_API_KEY', 'local-org-api-key')) } + + describe '#initialize' do + it 'raises ArgumentError when organization_id is nil' do + expect { described_class.new(nil, client) } + .to raise_error(ArgumentError, 'organization_id is required') + end + end + + describe '#create' do + subject(:create) { sub_accounts_api.create(request) } + + let(:request) { { name: 'SDK Test Sub Account' } } + + it 'maps response data to SubAccount object' do + expect(create).to be_a(Mailtrap::SubAccount) + expect(create).to have_attributes( + id: an_instance_of(Integer), + name: 'SDK Test Sub Account' + ) + 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) { {} } + + it 'raises a Mailtrap::Error' do + expect { create }.to raise_error(Mailtrap::Error) + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ef1be10..678327d 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -22,7 +22,9 @@ next if interaction.request.uri =~ /localhost/ interaction.request.uri.gsub!(%r{/accounts/\d+/}, '/accounts/1111111/') + interaction.request.uri.gsub!(%r{/organizations/\d+/}, '/organizations/2222222/') interaction.response.body.gsub!(/"account_id":\d+/, '"account_id": 1111111') + interaction.response.body.gsub!(/"organization_id":\d+/, '"account_id": 2222222') interaction.response.body.gsub!(/"username":"[^"]*"/, '"username": "railsware"') interaction.response.body.gsub!(/"password":"[^"]*"/, '"password": "xxxxxxxx"') interaction.response.body.gsub!(/"email":"[^"]*"/, '"email": "welcome@rw.com"') From 7dbd6ff9e3724fd81b1f24d189613ab31f273903 Mon Sep 17 00:00:00 2001 From: Igor Dobryn Date: Wed, 29 Apr 2026 15:32:02 +0300 Subject: [PATCH 4/7] Add SubAccountsAPI#list --- examples/sub_accounts_api.rb | 4 + lib/mailtrap/sub_accounts_api.rb | 7 ++ ...ps_response_data_to_SubAccount_objects.yml | 73 +++++++++++++++++++ spec/mailtrap/sub_accounts_api_spec.rb | 14 +++- 4 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/vcr_cassettes/Mailtrap_SubAccountsAPI/_list/maps_response_data_to_SubAccount_objects.yml diff --git a/examples/sub_accounts_api.rb b/examples/sub_accounts_api.rb index 9ae7e5f..f24790f 100644 --- a/examples/sub_accounts_api.rb +++ b/examples/sub_accounts_api.rb @@ -4,6 +4,10 @@ client = Mailtrap::Client.new(api_key: 'your-api-key') sub_accounts = Mailtrap::SubAccountsAPI.new(organization_id, client) +# List sub accounts under the organization +sub_accounts.list +# => [#, ...] + # Create a new sub account sub_accounts.create(name: 'New Team Account') # => # diff --git a/lib/mailtrap/sub_accounts_api.rb b/lib/mailtrap/sub_accounts_api.rb index 5990153..e71ceec 100644 --- a/lib/mailtrap/sub_accounts_api.rb +++ b/lib/mailtrap/sub_accounts_api.rb @@ -23,6 +23,13 @@ def initialize(organization_id, client = Mailtrap::Client.new) @client = client end + # Lists all sub accounts under the organization + # @return [Array] Array of sub accounts + # @!macro api_errors + def list + base_list + end + # Creates a new sub account under the organization # @param [Hash] options The parameters to create # @option options [String] :name Name of the sub account diff --git a/spec/fixtures/vcr_cassettes/Mailtrap_SubAccountsAPI/_list/maps_response_data_to_SubAccount_objects.yml b/spec/fixtures/vcr_cassettes/Mailtrap_SubAccountsAPI/_list/maps_response_data_to_SubAccount_objects.yml new file mode 100644 index 0000000..3fbd787 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Mailtrap_SubAccountsAPI/_list/maps_response_data_to_SubAccount_objects.yml @@ -0,0 +1,73 @@ +--- +http_interactions: +- request: + method: get + uri: https://mailtrap.io/api/organizations/2222222/sub_accounts + 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: + - Wed, 29 Apr 2026 12:29:02 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/"c47bbdaaa9593d14ae0a77e4cb385c2a" + Cache-Control: + - max-age=0, private, must-revalidate + X-Runtime: + - '0.012700' + Strict-Transport-Security: + - max-age=2592000; includeSubDomains; preload + Cf-Cache-Status: + - DYNAMIC + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: ASCII-8BIT + string: '[{"id":2704237,"name":"SDK Test Sub Account"}]' + recorded_at: Wed, 29 Apr 2026 12:29:02 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/mailtrap/sub_accounts_api_spec.rb b/spec/mailtrap/sub_accounts_api_spec.rb index 46e64ca..e364d50 100644 --- a/spec/mailtrap/sub_accounts_api_spec.rb +++ b/spec/mailtrap/sub_accounts_api_spec.rb @@ -3,7 +3,7 @@ RSpec.describe Mailtrap::SubAccountsAPI, :vcr do subject(:sub_accounts_api) { described_class.new(organization_id, client) } - let(:organization_id) { ENV.fetch('MAILTRAP_ORGANIZATION_ID', 1_111_111) } + let(:organization_id) { ENV.fetch('MAILTRAP_ORGANIZATION_ID', 2_222_222) } let(:client) { Mailtrap::Client.new(api_key: ENV.fetch('MAILTRAP_ORGANIZATION_API_KEY', 'local-org-api-key')) } describe '#initialize' do @@ -13,6 +13,18 @@ end end + describe '#list' do + subject(:list) { sub_accounts_api.list } + + it 'maps response data to SubAccount objects' do + expect(list).to all(be_a(Mailtrap::SubAccount)) + expect(list.first).to have_attributes( + id: an_instance_of(Integer), + name: an_instance_of(String) + ) + end + end + describe '#create' do subject(:create) { sub_accounts_api.create(request) } From 4a4fdd441f1e5a1f3c11375dc8c8747a1bd68bb7 Mon Sep 17 00:00:00 2001 From: Igor Dobryn Date: Wed, 29 Apr 2026 15:33:11 +0300 Subject: [PATCH 5/7] Add bin/record-vcr pointing to shared credentials set, so it's eay to re-record VCP cassettes --- bin/record-vcr | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100755 bin/record-vcr diff --git a/bin/record-vcr b/bin/record-vcr new file mode 100755 index 0000000..2cba19d --- /dev/null +++ b/bin/record-vcr @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# Run RSpec against the live Mailtrap API to record (or re-record) VCR cassettes. +# Real HTTP calls are made — only run this when you intend to update the +# cassettes under spec/fixtures/vcr_cassettes/. Credentials are injected from +# 1Password at runtime. +# +# Usage: +# bin/record-vcr # all specs +# bin/record-vcr spec/mailtrap/sending_domains_api_spec.rb # single file +# bin/record-vcr spec/mailtrap/sending_domains_api_spec.rb:42 # single example +# bin/record-vcr -e "#update" # filter by name +# bin/record-vcr --org spec/mailtrap/sub_accounts_api_spec.rb # use organization API token +# +# Pass --org as the first argument to authenticate with the organization-level token +# (needed for organization-scoped endpoints like SubAccountsAPI). Default is the +# account-level token. +# +# Requires the 1Password CLI (`op`) to be installed and signed in. + +set -euo pipefail + +export MAILTRAP_ACCOUNT_ID="op://Private/Mailtrap SDK Dev API Key/account_id" +export MAILTRAP_ORGANIZATION_ID="op://Private/Mailtrap SDK Dev API Key/organization_id" +export MAILTRAP_API_KEY="op://Private/Mailtrap SDK Dev API Key/account_api_token" +export MAILTRAP_ORGANIZATION_API_KEY="op://Private/Mailtrap SDK Dev API Key/organization_api_token" + +exec op run -- bundle exec rspec "$@" From 84922ed58ea188356951f49c95fefdeea7ed5b83 Mon Sep 17 00:00:00 2001 From: Igor Dobryn Date: Thu, 30 Apr 2026 09:52:46 +0300 Subject: [PATCH 6/7] Use shared vault to record vcr --- bin/record-vcr | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bin/record-vcr b/bin/record-vcr index 2cba19d..78136b1 100755 --- a/bin/record-vcr +++ b/bin/record-vcr @@ -19,9 +19,9 @@ set -euo pipefail -export MAILTRAP_ACCOUNT_ID="op://Private/Mailtrap SDK Dev API Key/account_id" -export MAILTRAP_ORGANIZATION_ID="op://Private/Mailtrap SDK Dev API Key/organization_id" -export MAILTRAP_API_KEY="op://Private/Mailtrap SDK Dev API Key/account_api_token" -export MAILTRAP_ORGANIZATION_API_KEY="op://Private/Mailtrap SDK Dev API Key/organization_api_token" +export MAILTRAP_ACCOUNT_ID="op://Mailtrap Dev/Mailtrap SDK Dev API Key/account_id" +export MAILTRAP_ORGANIZATION_ID="op://Mailtrap Dev/Mailtrap SDK Dev API Key/organization_id" +export MAILTRAP_API_KEY="op://Mailtrap Dev/Mailtrap SDK Dev API Key/account_api_token" +export MAILTRAP_ORGANIZATION_API_KEY="op://Mailtrap Dev/Mailtrap SDK Dev API Key/organization_api_token" exec op run -- bundle exec rspec "$@" From c52c422d3bab70e6840ba79deeff6381d4985e34 Mon Sep 17 00:00:00 2001 From: Igor Dobryn Date: Thu, 30 Apr 2026 09:55:21 +0300 Subject: [PATCH 7/7] Fix linter --- spec/mailtrap/permissions_api_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/mailtrap/permissions_api_spec.rb b/spec/mailtrap/permissions_api_spec.rb index 809d99f..be58083 100644 --- a/spec/mailtrap/permissions_api_spec.rb +++ b/spec/mailtrap/permissions_api_spec.rb @@ -9,7 +9,7 @@ describe '#bulk_update' do subject(:bulk_update) { permissions_api.bulk_update(account_access_id, permissions) } - let(:account_access_id) { 5339621 } + let(:account_access_id) { 5_339_621 } let(:permissions) do [ { resource_id: '1719941', resource_type: 'account', access_level: 'viewer' }