From a131119cc1d19d283315e20cc01dc755c6b37d71 Mon Sep 17 00:00:00 2001 From: DagonWat Date: Tue, 17 Feb 2026 14:18:39 +0100 Subject: [PATCH 1/4] Add billing API --- CHANGELOG.md | 3 + README.md | 1 + examples/billing_api.rb | 9 +++ lib/mailtrap.rb | 1 + lib/mailtrap/billing.rb | 16 ++++ lib/mailtrap/billing_api.rb | 26 +++++++ lib/mailtrap/project.rb | 2 +- .../maps_response_data_to_Billing_object.yml | 74 ++++++++++++++++++ .../raises_authorization_error.yml | 73 ++++++++++++++++++ spec/mailtrap/billing_api_spec.rb | 36 +++++++++ spec/mailtrap/billing_spec.rb | 77 +++++++++++++++++++ 11 files changed, 317 insertions(+), 1 deletion(-) create mode 100644 examples/billing_api.rb create mode 100644 lib/mailtrap/billing.rb create mode 100644 lib/mailtrap/billing_api.rb create mode 100644 spec/fixtures/vcr_cassettes/Mailtrap_BillingAPI/_get/maps_response_data_to_Billing_object.yml create mode 100644 spec/fixtures/vcr_cassettes/Mailtrap_BillingAPI/_get/when_api_key_is_incorrect/raises_authorization_error.yml create mode 100644 spec/mailtrap/billing_api_spec.rb create mode 100644 spec/mailtrap/billing_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 269436a..529a3de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## Unreleased +- Add Billing API + ## [2.7.0] - 2026-02-24 - Add Sandbox Messages API - Add Sending Domains API diff --git a/README.md b/README.md index 84cea42..111d19b 100644 --- a/README.md +++ b/README.md @@ -192,6 +192,7 @@ Contact management: General: - Templates CRUD – [`email_templates_api.rb`](examples/email_templates_api.rb) +- Billing – [`billing_api.rb`](examples/billing_api.rb) - Action Mailer – [`action_mailer.rb`](examples/action_mailer.rb) - Accounts API – [`accounts_api.rb`](examples/accounts_api.rb) diff --git a/examples/billing_api.rb b/examples/billing_api.rb new file mode 100644 index 0000000..4862eef --- /dev/null +++ b/examples/billing_api.rb @@ -0,0 +1,9 @@ +require 'mailtrap' + +account_id = 3229 +client = Mailtrap::Client.new(api_key: 'your-api-key') +billing_api = Mailtrap::BillingAPI.new(account_id, client) + +# Get billing information for the account +billing_api.get +# => # diff --git a/lib/mailtrap.rb b/lib/mailtrap.rb index 134ed05..d186a97 100644 --- a/lib/mailtrap.rb +++ b/lib/mailtrap.rb @@ -5,6 +5,7 @@ require_relative 'mailtrap/errors' require_relative 'mailtrap/version' require_relative 'mailtrap/accounts_api' +require_relative 'mailtrap/billing_api' require_relative 'mailtrap/email_templates_api' require_relative 'mailtrap/contacts_api' require_relative 'mailtrap/contact_lists_api' diff --git a/lib/mailtrap/billing.rb b/lib/mailtrap/billing.rb new file mode 100644 index 0000000..92c5ae9 --- /dev/null +++ b/lib/mailtrap/billing.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Mailtrap + # Data Transfer Object for Billing data + # @see https://docs.mailtrap.io/developers/account-management/billing + # @attr_reader billing [Hash] The billing cycles + # @attr_reader testing [Hash] Testing subscription details + # @attr_reader sending [Hash] Sending subscription details + # + Billing = Struct.new( + :billing, + :testing, + :sending, + keyword_init: true + ) +end diff --git a/lib/mailtrap/billing_api.rb b/lib/mailtrap/billing_api.rb new file mode 100644 index 0000000..a34521b --- /dev/null +++ b/lib/mailtrap/billing_api.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require_relative 'base_api' +require_relative 'billing' + +module Mailtrap + class BillingAPI + include BaseAPI + + self.response_class = Billing + + # Show billing details for the account + # @return Billing data for account + # @!macro api_errors + def get + response = client.get(base_path) + handle_response(response) + end + + private + + def base_path + "/api/accounts/#{account_id}/billing/usage" + end + end +end diff --git a/lib/mailtrap/project.rb b/lib/mailtrap/project.rb index aed082a..f8a6a36 100644 --- a/lib/mailtrap/project.rb +++ b/lib/mailtrap/project.rb @@ -2,7 +2,7 @@ module Mailtrap # Data Transfer Object for Project - # @see https://api-docs.mailtrap.io/docs/mailtrap-api-docs/ee252e413d78a-create-project + # @see https://docs.mailtrap.io/developers/email-sandbox/projects # @attr_reader id [Integer] The project ID # @attr_reader name [String] The project name # @attr_reader share_links [Hash] Admin and viewer share links diff --git a/spec/fixtures/vcr_cassettes/Mailtrap_BillingAPI/_get/maps_response_data_to_Billing_object.yml b/spec/fixtures/vcr_cassettes/Mailtrap_BillingAPI/_get/maps_response_data_to_Billing_object.yml new file mode 100644 index 0000000..d9c3958 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Mailtrap_BillingAPI/_get/maps_response_data_to_Billing_object.yml @@ -0,0 +1,74 @@ +--- +http_interactions: +- request: + method: get + uri: https://mailtrap.io/api/accounts/1111111/billing/usage + 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, 17 Feb 2026 12:56:11 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/"fcfb7d10033fb8e579fa7eed2e69857d" + Cache-Control: + - max-age=0, private, must-revalidate + X-Runtime: + - '0.032663' + Strict-Transport-Security: + - max-age=0 + Cf-Cache-Status: + - DYNAMIC + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: ASCII-8BIT + string: '{"billing":{"cycle_start":"2026-02-06T12:59:54.000Z","cycle_end":"2026-03-06T12:59:54.000Z"},"testing":{"plan":{"name":"Team"},"usage":{"sent_messages_count":{"current":0,"limit":5000},"forwarded_messages_count":{"current":0,"limit":500}}},"sending":{"plan":{"name":"Basic + 10K"},"usage":{"sent_messages_count":{"current":0,"limit":10000}}}}' + recorded_at: Tue, 17 Feb 2026 12:56:11 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/fixtures/vcr_cassettes/Mailtrap_BillingAPI/_get/when_api_key_is_incorrect/raises_authorization_error.yml b/spec/fixtures/vcr_cassettes/Mailtrap_BillingAPI/_get/when_api_key_is_incorrect/raises_authorization_error.yml new file mode 100644 index 0000000..3deb73d --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Mailtrap_BillingAPI/_get/when_api_key_is_incorrect/raises_authorization_error.yml @@ -0,0 +1,73 @@ +--- +http_interactions: +- request: + method: get + uri: https://mailtrap.io/api/accounts/1111111/billing/usage + 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: 401 + message: Unauthorized + headers: + Date: + - Tue, 17 Feb 2026 12:56:12 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '31' + 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 + Www-Authenticate: + - Token realm="Application" + Vary: + - Accept + X-Mailtrap-Version: + - v2 + X-Ratelimit-Limit: + - '150' + X-Ratelimit-Remaining: + - '149' + Cache-Control: + - no-cache + X-Runtime: + - '0.006363' + Strict-Transport-Security: + - max-age=0 + Cf-Cache-Status: + - DYNAMIC + Alt-Svc: + - h3=":443"; ma=86400 + body: + encoding: UTF-8 + string: '{"error":"Incorrect API token"}' + recorded_at: Tue, 17 Feb 2026 12:56:12 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/mailtrap/billing_api_spec.rb b/spec/mailtrap/billing_api_spec.rb new file mode 100644 index 0000000..95c070f --- /dev/null +++ b/spec/mailtrap/billing_api_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +RSpec.describe Mailtrap::BillingAPI, :vcr do + subject(:billing_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 '#get' do + subject(:get) { billing_api.get } + + it 'maps response data to Billing object' do + expect(get).to be_a(Mailtrap::Billing) + + expect(get).to match_struct( + billing: { cycle_start: '2026-02-06T12:59:54.000Z', cycle_end: '2026-03-06T12:59:54.000Z' }, + testing: { plan: { name: 'Team' }, + usage: { sent_messages_count: { current: 0, limit: 5000 }, + forwarded_messages_count: { current: 0, limit: 500 } } }, + sending: { plan: { name: 'Basic 10K' }, usage: { sent_messages_count: { current: 0, limit: 10_000 } } } + ) + end + + context 'when api key is incorrect' do + let(:client) { Mailtrap::Client.new(api_key: 'incorrect-api-key') } + + it 'raises authorization error' do + expect { get }.to raise_error do |error| + expect(error).to be_a(Mailtrap::AuthorizationError) + expect(error.message).to include('Incorrect API token') + expect(error.messages.any? { |msg| msg.include?('Incorrect API token') }).to be true + end + end + end + end +end diff --git a/spec/mailtrap/billing_spec.rb b/spec/mailtrap/billing_spec.rb new file mode 100644 index 0000000..b2c2d06 --- /dev/null +++ b/spec/mailtrap/billing_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +RSpec.describe Mailtrap::Billing do + describe '#initialize' do + subject(:billing) { described_class.new(attributes) } + + let(:attributes) do + { + billing: { + cycle_start: '2024-02-15T21:11:59.624Z', + cycle_end: '2024-02-15T21:11:59.624Z' + }, + testing: { + plan: { + name: 'Individual' + }, + usage: { + sent_messages_count: { + current: 1234, + limit: 5000 + }, + forwarded_messages_count: { + current: 0, + limit: 100 + } + } + }, + sending: { + plan: { + name: 'Basic 10K' + }, + usage: { + sent_messages_count: { + current: 6789, + limit: 10_000 + } + } + } + } + end + + it 'creates a billing data with all attributes' do + expect(billing).to match_struct( + billing: { + cycle_start: '2024-02-15T21:11:59.624Z', + cycle_end: '2024-02-15T21:11:59.624Z' + }, + testing: { + plan: { + name: 'Individual' + }, + usage: { + sent_messages_count: { + current: 1234, + limit: 5000 + }, + forwarded_messages_count: { + current: 0, + limit: 100 + } + } + }, + sending: { + plan: { + name: 'Basic 10K' + }, + usage: { + sent_messages_count: { + current: 6789, + limit: 10_000 + } + } + } + ) + end + end +end From e81d19c243b09815c760b9039c0b4fc7bd4a69d1 Mon Sep 17 00:00:00 2001 From: Ivan Yurchanka Date: Mon, 2 Mar 2026 12:40:04 +0100 Subject: [PATCH 2/4] Rename Billing model to BillingUsage --- examples/billing_api.rb | 22 +++++++++++++++---- lib/mailtrap/billing.rb | 16 -------------- lib/mailtrap/billing_api.rb | 20 +++++------------ lib/mailtrap/billing_usage.rb | 16 ++++++++++++++ spec/mailtrap/billing_api_spec.rb | 10 ++++----- ...{billing_spec.rb => billing_usage_spec.rb} | 2 +- 6 files changed, 46 insertions(+), 40 deletions(-) delete mode 100644 lib/mailtrap/billing.rb create mode 100644 lib/mailtrap/billing_usage.rb rename spec/mailtrap/{billing_spec.rb => billing_usage_spec.rb} (97%) diff --git a/examples/billing_api.rb b/examples/billing_api.rb index 4862eef..9ffa03b 100644 --- a/examples/billing_api.rb +++ b/examples/billing_api.rb @@ -2,8 +2,22 @@ account_id = 3229 client = Mailtrap::Client.new(api_key: 'your-api-key') -billing_api = Mailtrap::BillingAPI.new(account_id, client) +billing = Mailtrap::BillingAPI.new(account_id, client) -# Get billing information for the account -billing_api.get -# => # +# Get current billing cycle usage +billing.usage +# => # diff --git a/lib/mailtrap/billing.rb b/lib/mailtrap/billing.rb deleted file mode 100644 index 92c5ae9..0000000 --- a/lib/mailtrap/billing.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -module Mailtrap - # Data Transfer Object for Billing data - # @see https://docs.mailtrap.io/developers/account-management/billing - # @attr_reader billing [Hash] The billing cycles - # @attr_reader testing [Hash] Testing subscription details - # @attr_reader sending [Hash] Sending subscription details - # - Billing = Struct.new( - :billing, - :testing, - :sending, - keyword_init: true - ) -end diff --git a/lib/mailtrap/billing_api.rb b/lib/mailtrap/billing_api.rb index a34521b..6ba89b7 100644 --- a/lib/mailtrap/billing_api.rb +++ b/lib/mailtrap/billing_api.rb @@ -1,26 +1,18 @@ # frozen_string_literal: true require_relative 'base_api' -require_relative 'billing' +require_relative 'billing_usage' module Mailtrap class BillingAPI include BaseAPI - self.response_class = Billing - - # Show billing details for the account - # @return Billing data for account + # Get current billing cycle usage + # @return Billing usage data for account # @!macro api_errors - def get - response = client.get(base_path) - handle_response(response) - end - - private - - def base_path - "/api/accounts/#{account_id}/billing/usage" + def usage + response = client.get("/api/accounts/#{account_id}/billing/usage") + build_entity(response, BillingUsage) end end end diff --git a/lib/mailtrap/billing_usage.rb b/lib/mailtrap/billing_usage.rb new file mode 100644 index 0000000..d6648c2 --- /dev/null +++ b/lib/mailtrap/billing_usage.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Mailtrap + # Data Transfer Object for Billing Usage data + # @see https://docs.mailtrap.io/developers/account-management/billing + # @attr_reader billing [Hash] The billing cycle details + # @attr_reader testing [Hash] Testing subscription usage + # @attr_reader sending [Hash] Sending subscription usage + # + BillingUsage = Struct.new( + :billing, + :testing, + :sending, + keyword_init: true + ) +end diff --git a/spec/mailtrap/billing_api_spec.rb b/spec/mailtrap/billing_api_spec.rb index 95c070f..36a8b20 100644 --- a/spec/mailtrap/billing_api_spec.rb +++ b/spec/mailtrap/billing_api_spec.rb @@ -1,18 +1,18 @@ # frozen_string_literal: true RSpec.describe Mailtrap::BillingAPI, :vcr do - subject(:billing_api) { described_class.new(account_id, client) } + subject(:billing) { 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 '#get' do - subject(:get) { billing_api.get } + subject(:usage) { billing.usage } it 'maps response data to Billing object' do - expect(get).to be_a(Mailtrap::Billing) + expect(usage).to be_a(Mailtrap::BillingUsage) - expect(get).to match_struct( + expect(usage).to match_struct( billing: { cycle_start: '2026-02-06T12:59:54.000Z', cycle_end: '2026-03-06T12:59:54.000Z' }, testing: { plan: { name: 'Team' }, usage: { sent_messages_count: { current: 0, limit: 5000 }, @@ -25,7 +25,7 @@ let(:client) { Mailtrap::Client.new(api_key: 'incorrect-api-key') } it 'raises authorization error' do - expect { get }.to raise_error do |error| + expect { usage }.to raise_error do |error| expect(error).to be_a(Mailtrap::AuthorizationError) expect(error.message).to include('Incorrect API token') expect(error.messages.any? { |msg| msg.include?('Incorrect API token') }).to be true diff --git a/spec/mailtrap/billing_spec.rb b/spec/mailtrap/billing_usage_spec.rb similarity index 97% rename from spec/mailtrap/billing_spec.rb rename to spec/mailtrap/billing_usage_spec.rb index b2c2d06..dd3d20b 100644 --- a/spec/mailtrap/billing_spec.rb +++ b/spec/mailtrap/billing_usage_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe Mailtrap::Billing do +RSpec.describe Mailtrap::BillingUsage do describe '#initialize' do subject(:billing) { described_class.new(attributes) } From 0170d270171a45982bb6918690a42fd4948f7ec3 Mon Sep 17 00:00:00 2001 From: Ivan Yurchanka Date: Mon, 2 Mar 2026 12:45:51 +0100 Subject: [PATCH 3/4] Fix documentation --- README.md | 2 +- lib/mailtrap/billing_api.rb | 2 +- .../{_get => _usage}/maps_response_data_to_Billing_object.yml | 0 .../when_api_key_is_incorrect/raises_authorization_error.yml | 0 spec/mailtrap/billing_api_spec.rb | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) rename spec/fixtures/vcr_cassettes/Mailtrap_BillingAPI/{_get => _usage}/maps_response_data_to_Billing_object.yml (100%) rename spec/fixtures/vcr_cassettes/Mailtrap_BillingAPI/{_get => _usage}/when_api_key_is_incorrect/raises_authorization_error.yml (100%) diff --git a/README.md b/README.md index 111d19b..5b4d72f 100644 --- a/README.md +++ b/README.md @@ -192,9 +192,9 @@ Contact management: General: - Templates CRUD – [`email_templates_api.rb`](examples/email_templates_api.rb) -- Billing – [`billing_api.rb`](examples/billing_api.rb) - Action Mailer – [`action_mailer.rb`](examples/action_mailer.rb) - Accounts API – [`accounts_api.rb`](examples/accounts_api.rb) +- Billing API – [`billing_api.rb`](examples/billing_api.rb) ## Migration guide v1 → v2 diff --git a/lib/mailtrap/billing_api.rb b/lib/mailtrap/billing_api.rb index 6ba89b7..0dc7495 100644 --- a/lib/mailtrap/billing_api.rb +++ b/lib/mailtrap/billing_api.rb @@ -8,7 +8,7 @@ class BillingAPI include BaseAPI # Get current billing cycle usage - # @return Billing usage data for account + # @return [BillingUsage] Billing usage data for account # @!macro api_errors def usage response = client.get("/api/accounts/#{account_id}/billing/usage") diff --git a/spec/fixtures/vcr_cassettes/Mailtrap_BillingAPI/_get/maps_response_data_to_Billing_object.yml b/spec/fixtures/vcr_cassettes/Mailtrap_BillingAPI/_usage/maps_response_data_to_Billing_object.yml similarity index 100% rename from spec/fixtures/vcr_cassettes/Mailtrap_BillingAPI/_get/maps_response_data_to_Billing_object.yml rename to spec/fixtures/vcr_cassettes/Mailtrap_BillingAPI/_usage/maps_response_data_to_Billing_object.yml diff --git a/spec/fixtures/vcr_cassettes/Mailtrap_BillingAPI/_get/when_api_key_is_incorrect/raises_authorization_error.yml b/spec/fixtures/vcr_cassettes/Mailtrap_BillingAPI/_usage/when_api_key_is_incorrect/raises_authorization_error.yml similarity index 100% rename from spec/fixtures/vcr_cassettes/Mailtrap_BillingAPI/_get/when_api_key_is_incorrect/raises_authorization_error.yml rename to spec/fixtures/vcr_cassettes/Mailtrap_BillingAPI/_usage/when_api_key_is_incorrect/raises_authorization_error.yml diff --git a/spec/mailtrap/billing_api_spec.rb b/spec/mailtrap/billing_api_spec.rb index 36a8b20..fe82587 100644 --- a/spec/mailtrap/billing_api_spec.rb +++ b/spec/mailtrap/billing_api_spec.rb @@ -6,7 +6,7 @@ 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 + describe '#usage' do subject(:usage) { billing.usage } it 'maps response data to Billing object' do From bb0f7b844b687491c84d01332fc195ce6058576f Mon Sep 17 00:00:00 2001 From: Ivan Yurchanka Date: Mon, 2 Mar 2026 17:24:07 +0100 Subject: [PATCH 4/4] Re-arrange general APIs --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cb4ab2e..b90693a 100644 --- a/README.md +++ b/README.md @@ -191,11 +191,11 @@ Contact management: General: -- Templates CRUD – [`email_templates_api.rb`](examples/email_templates_api.rb) -- Action Mailer – [`action_mailer.rb`](examples/action_mailer.rb) - Accounts API – [`accounts_api.rb`](examples/accounts_api.rb) -- Account Accesses – [`account_accesses_api.rb`](examples/account_accesses_api.rb) +- Account Accesses API – [`account_accesses_api.rb`](examples/account_accesses_api.rb) - Billing API – [`billing_api.rb`](examples/billing_api.rb) +- Templates API – [`email_templates_api.rb`](examples/email_templates_api.rb) +- Action Mailer – [`action_mailer.rb`](examples/action_mailer.rb) ## Migration guide v1 → v2