diff --git a/CHANGELOG.md b/CHANGELOG.md index 8688fb4..43ce8aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +## [1.2.16] - 2024-11-29 + +### Added + +- Metrics API support (https://github.com/mailgun/mailgun-ruby/pull/326) + ## [1.2.15] - 2024-02-13 ### Fixed diff --git a/README.md b/README.md index 3259214..b22833a 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ gem install mailgun-ruby Gemfile: ```ruby -gem 'mailgun-ruby', '~>1.2.15' +gem 'mailgun-ruby', '~>1.2.16' ``` Usage diff --git a/docs/Metrics.md b/docs/Metrics.md new file mode 100644 index 0000000..5afa729 --- /dev/null +++ b/docs/Metrics.md @@ -0,0 +1,108 @@ +Mailgun - Metrics +==================== + +This is the Mailgun Ruby *Metrics* utilities. + +The below assumes you've already installed the Mailgun Ruby SDK in to your +project. If not, go back to the master README for instructions. It currently supports +all calls except credentials. + +--- + +Mailgun collects many different events and generates event metrics which are available +in your Control Panel. This data is also available via our analytics metrics API endpoint. + +You can view additional samples in the [metrics_spec.rb](/spec/integration/metrics_spec.rb) +or the Metrics client API in [metrics.rb](/lib/metrics/metrics.rb). + +Usage +----- + +To get an instance of the Metrics client: + +```ruby +require 'mailgun' + +mg_client = Mailgun::Client.new('your-api-key', 'mailgun-api-host', 'v1') +metrics = Mailgun::Metrics.new(mg_client) +```` +--- +Get filtered metrics for an account: +```ruby +options = { + { + resolution: 'hour', + metrics: [ + 'accepted_count', + 'delivered_count', + 'clicked_rate', + 'opened_rate' + ], + include_aggregates: true, + start: 'Tue, 26 Nov 2024 20:56:50 -0500', + duration: '1m', + filter: { + AND: [ + { + attribute: 'domain', + comparator: '!=', + values: [ + { + label: 'example.com', + value: 'example.com' + } + ] + } + ] + }, + dimensions: ['time'], + end: 'Tue, 30 Nov 2024 20:56:50 -0500', + include_subaccounts: true + } +} + +metrics.account_metrics(options) +``` +--- + +Get filtered usage metrics for an account: +```ruby +options = { + resolution: 'hour', + metrics: [ + 'accepted_count', + 'delivered_count', + 'clicked_rate', + 'opened_rate' + ], + include_aggregates: true, + start: 'Tue, 26 Nov 2024 20:56:50 -0500', + duration: '1m', + filter: { + AND: [ + { + attribute: 'domain', + comparator: '!=', + values: [ + { + label: 'example.com', + value: 'example.com' + } + ] + } + ] + }, + dimensions: ['time'], + end: 'Tue, 30 Nov 2024 20:56:50 -0500', + include_subaccounts: true +} + +metrics.account_usage_metrics(options) +``` + +--- + +More Documentation +------------------ +See the official [Mailgun Domain Docs](https://documentation.mailgun.com/docs/mailgun/api-reference/openapi-final/tag/Metrics/) +for more information diff --git a/lib/mailgun.rb b/lib/mailgun.rb index a263542..c9aa6b9 100644 --- a/lib/mailgun.rb +++ b/lib/mailgun.rb @@ -18,6 +18,7 @@ require 'mailgun/templates/templates' require 'mailgun/subaccounts/subaccounts' require 'mailgun/tags/tags' +require 'mailgun/metrics/metrics' # Module for interacting with the sweet Mailgun API. # diff --git a/lib/mailgun/metrics/metrics.rb b/lib/mailgun/metrics/metrics.rb new file mode 100644 index 0000000..6f63e07 --- /dev/null +++ b/lib/mailgun/metrics/metrics.rb @@ -0,0 +1,61 @@ +require 'mailgun/exceptions/exceptions' + +module Mailgun + # A Mailgun::Metrics object is a simple interface to Mailgun Metrics. + # Uses Mailgun + class Metrics + # Public: creates a new Mailgun::Metrics instance. + # Defaults to Mailgun::Client + def initialize(client = Mailgun::Client.new(Mailgun.api_key, Mailgun.api_host || 'api.mailgun.net', 'v1')) + @client = client + end + + # Public: Post query to get account metrics + # + # options - [Hash] of + # start - [String] A start date (default: 7 days before current time). Must be in RFC 2822 format. + # end - [String] An end date (default: current time). Must be in RFC 2822 format. + # resolution - [String] A resolution in the format of 'day' 'hour' 'month'. Default is day. + # duration - [String] A duration in the format of '1d' '2h' '2m'. If duration is provided then it is calculated from the end date and overwrites the start date. + # dimensions - [Array] Attributes of the metric data such as 'subaccount'. + # metrics - [Array] Name of the metrics to receive the stats for such as 'processed_count' + # filter - [Object] + # AND: - [Array] of objects + # attribute - [String] + # comparator - [String] + # values - [Array] of objects + # label - [String] + # value - [String] + # include_subaccounts - [Boolean] Include stats from all subaccounts. + # include_aggregates - [Boolean] Include top-level aggregate metrics. + # + # Returns [Hash] Metrics + def account_metrics(options={}) + @client.post('analytics/metrics', options.to_json, { "Content-Type" => "application/json" }).to_h! + end + + # Public: Post query to get account usage metrics + # + # options - [Hash] of + # start - [String] A start date (default: 7 days before current time). Must be in RFC 2822 format. + # end - [String] An end date (default: current time). Must be in RFC 2822 format. + # resolution - [String] A resolution in the format of 'day' 'hour' 'month'. Default is day. + # duration - [String] A duration in the format of '1d' '2h' '2m'. If duration is provided then it is calculated from the end date and overwrites the start date. + # dimensions - [Array] Attributes of the metric data such as 'subaccount'. + # metrics - [Array] Name of the metrics to receive the stats for such as 'processed_count' + # filter - [Object] + # AND: - [Array] of objects + # attribute - [String] + # comparator - [String] + # values - [Array] of objects + # label - [String] + # value - [String] + # include_subaccounts - [Boolean] Include stats from all subaccounts. + # include_aggregates - [Boolean] Include top-level aggregate metrics. + # + # Returns [Hash] Metrics + def account_usage_metrics(options={}) + @client.post('analytics/usage/metrics', options.to_json, { "Content-Type" => "application/json" }).to_h! + end + end +end diff --git a/lib/mailgun/version.rb b/lib/mailgun/version.rb index 5650ed9..3eccc6c 100644 --- a/lib/mailgun/version.rb +++ b/lib/mailgun/version.rb @@ -1,4 +1,4 @@ # It's the version. Yeay! module Mailgun - VERSION = '1.2.15' + VERSION = '1.2.16' end diff --git a/spec/integration/metrics_spec.rb b/spec/integration/metrics_spec.rb new file mode 100644 index 0000000..c10bb6f --- /dev/null +++ b/spec/integration/metrics_spec.rb @@ -0,0 +1,218 @@ +require 'spec_helper' +require 'mailgun' + +vcr_opts = { cassette_name: 'metrics' } + +describe Mailgun::Metrics, vcr: vcr_opts do + let(:metrics) { Mailgun::Metrics.new(Mailgun::Client.new(APIKEY, APIHOST, 'v1')) } + + describe '#account_metrics' do + let(:options) do + { + resolution: 'hour', + metrics: [ + 'accepted_count', + 'delivered_count', + 'clicked_rate', + 'opened_rate' + ], + include_aggregates: true, + start: 'Tue, 26 Nov 2024 20:56:50 -0500', + duration: '1m', + filter: { + AND: [ + { + attribute: 'domain', + comparator: '!=', + values: [ + { + label: 'example.com', + value: 'example.com' + } + ] + } + ] + }, + dimensions: ['time'], + end: 'Tue, 30 Nov 2024 20:56:50 -0500', + include_subaccounts: true + } + end + + it 'responds with account metrics' do + expect(metrics.account_metrics(options)).to eq( + { + "start" => "Fri, 01 Nov 2024 01:00:00 +0000", + "end" => "Sun, 01 Dec 2024 01:00:00 +0000", + "resolution" => "hour", + "duration" => "1m", + "dimensions" => ["time"], + "pagination" => { + "sort" => "", "skip" => 0, "limit" => 1500, "total" => 3 + }, + "items" => [{ + "dimensions" => [{ + "dimension" => "time", + "value" => "Wed, 27 Nov 2024 12:00:00 +0000", + "display_value" => "Wed, 27 Nov 2024 12:00:00 +0000" + }], + "metrics" => { + "accepted_count" => 1, "delivered_count" => 1, "opened_rate" => "0.0000", "clicked_rate" => "0.0000" + } + }, + { + "dimensions" => [{ + "dimension" => "time", + "value" => "Wed, 27 Nov 2024 13:00:00 +0000", + "display_value" => "Wed, 27 Nov 2024 13:00:00 +0000" + }], + "metrics" => { + "accepted_count" => 1, "delivered_count" => 1, "opened_rate" => "0.0000", "clicked_rate" => "0.0000" + } + }, + { + "dimensions" => [{ + "dimension" => "time", + "value" => "Thu, 28 Nov 2024 15:00:00 +0000", + "display_value" => "Thu, 28 Nov 2024 15:00:00 +0000" + }], + "metrics" => { + "accepted_count" => 1, "delivered_count" => 1, "opened_rate" => "0.0000", "clicked_rate" => "0.0000" + } + } + ], + "aggregates" => { + "metrics" => { + "accepted_count" => 3, "delivered_count" => 3, "opened_rate" => "0.0000", "clicked_rate" => "0.0000" + } + } + } + ) + end + end + + describe '#account_usage_metrics' do + let(:options) do + { + resolution: 'hour', + metrics: [ + 'email_preview_count', + 'email_preview_failed_count', + 'email_validation_bulk_count', + 'email_validation_count', + 'email_validation_list_count', + 'email_validation_mailgun_count', + 'email_validation_mailjet_count', + 'email_validation_public_count', + 'email_validation_single_count', + 'email_validation_valid_count', + 'link_validation_count', + 'link_validation_failed_count', + 'processed_count', + 'seed_test_count' + ], + include_aggregates: true, + start: 'Tue, 26 Nov 2024 20:56:50 -0500', + duration: '1m', + filter: { + AND: [ + { + attribute: 'subaccount', + comparator: '!=', + values: [ + { + label: '12345', + value: '12345' + } + ] + } + ] + }, + dimensions: ['time'], + end: 'Tue, 28 Nov 2024 20:56:50 -0500', + include_subaccounts: true + } + end + + it 'responds with account usage metrics' do + expect(metrics.account_usage_metrics(options)).to eq( + { + "start" => "Tue, 29 Oct 2024 01:00:00 +0000", + "end" => "Fri, 29 Nov 2024 01:00:00 +0000", + "resolution" => "hour", + "duration" => "1m", + "dimensions" => ["time"], + "pagination" => { + "sort" => "", "skip" => 0, "limit" => 1500, "total" => 2 + }, + "items" => [{ + "dimensions" => [{ + "dimension" => "time", + "value" => "Wed, 27 Nov 2024 00:00:00 +0000", + "display_value" => "Wed, 27 Nov 2024 00:00:00 +0000" + }], + "metrics" => { + "processed_count" => 2, + "email_validation_count" => 0, + "email_validation_public_count" => 0, + "email_validation_valid_count" => 0, + "email_validation_single_count" => 0, + "email_validation_bulk_count" => 0, + "email_validation_list_count" => 0, + "email_validation_mailgun_count" => 0, + "email_validation_mailjet_count" => 0, + "email_preview_count" => 0, + "email_preview_failed_count" => 0, + "link_validation_count" => 0, + "link_validation_failed_count" => 0, + "seed_test_count" => 0 + } + }, + { + "dimensions" => [{ + "dimension" => "time", + "value" => "Thu, 28 Nov 2024 00:00:00 +0000", + "display_value" => "Thu, 28 Nov 2024 00:00:00 +0000" + }], + "metrics" => { + "processed_count" => 1, + "email_validation_count" => 0, + "email_validation_public_count" => 0, + "email_validation_valid_count" => 0, + "email_validation_single_count" => 0, + "email_validation_bulk_count" => 0, + "email_validation_list_count" => 0, + "email_validation_mailgun_count" => 0, + "email_validation_mailjet_count" => 0, + "email_preview_count" => 0, + "email_preview_failed_count" => 0, + "link_validation_count" => 0, + "link_validation_failed_count" => 0, + "seed_test_count" => 0 + } + } + ], + "aggregates" => { + "metrics" => { + "permanent_failed_count" => 0, + "processed_count" => 3, + "email_validation_count" => 0, + "email_validation_public_count" => 0, + "email_validation_valid_count" => 0, + "email_validation_single_count" => 0, + "email_validation_bulk_count" => 0, + "email_validation_list_count" => 0, + "email_validation_mailgun_count" => 0, + "email_validation_mailjet_count" => 0, + "email_preview_count" => 0, + "email_preview_failed_count" => 0, + "link_validation_count" => 0, + "link_validation_failed_count" => 0, + "seed_test_count" => 0 + } + } + } + ) + end + end +end diff --git a/vcr_cassettes/metrics.yml b/vcr_cassettes/metrics.yml new file mode 100644 index 0000000..de7399f --- /dev/null +++ b/vcr_cassettes/metrics.yml @@ -0,0 +1,116 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.mailgun.net/v1/analytics/metrics + body: + encoding: UTF-8 + string: '{"resolution":"hour","metrics":["accepted_count","delivered_count","clicked_rate","opened_rate"],"include_aggregates":true,"start":"Tue, + 26 Nov 2024 20:56:50 -0500","duration":"1m","filter":{"AND":[{"attribute":"domain","comparator":"!=","values":[{"label":"example.com","value":"example.com"}]}]},"dimensions":["time"],"end":"Tue, + 30 Nov 2024 20:56:50 -0500","include_subaccounts":true}' + headers: + Accept: + - "*/*" + User-Agent: + - rest-client/2.1.0 (darwin23 x86_64) ruby/3.1.4p223 + Content-Type: + - application/json + Content-Length: + - '387' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Host: + - api.mailgun.net + Authorization: + - Basic xxx + response: + status: + code: 200 + message: OK + headers: + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Origin: + - "*" + Cache-Control: + - no-store + Content-Length: + - '1006' + Content-Type: + - application/json; charset=utf-8 + Date: + - Thu, 28 Nov 2024 18:20:22 GMT + Strict-Transport-Security: + - max-age=63072000; includeSubDomains + X-Mailgun-Key-Id: + - c02fd0ba-d8dbad66 + X-Xss-Protection: + - 1; mode=block + body: + encoding: UTF-8 + string: '{"start":"Fri, 01 Nov 2024 01:00:00 +0000","end":"Sun, 01 Dec 2024 + 01:00:00 +0000","resolution":"hour","duration":"1m","dimensions":["time"],"pagination":{"sort":"","skip":0,"limit":1500,"total":3},"items":[{"dimensions":[{"dimension":"time","value":"Wed, + 27 Nov 2024 12:00:00 +0000","display_value":"Wed, 27 Nov 2024 12:00:00 +0000"}],"metrics":{"accepted_count":1,"delivered_count":1,"opened_rate":"0.0000","clicked_rate":"0.0000"}},{"dimensions":[{"dimension":"time","value":"Wed, + 27 Nov 2024 13:00:00 +0000","display_value":"Wed, 27 Nov 2024 13:00:00 +0000"}],"metrics":{"accepted_count":1,"delivered_count":1,"opened_rate":"0.0000","clicked_rate":"0.0000"}},{"dimensions":[{"dimension":"time","value":"Thu, + 28 Nov 2024 15:00:00 +0000","display_value":"Thu, 28 Nov 2024 15:00:00 +0000"}],"metrics":{"accepted_count":1,"delivered_count":1,"opened_rate":"0.0000","clicked_rate":"0.0000"}}],"aggregates":{"metrics":{"accepted_count":3,"delivered_count":3,"opened_rate":"0.0000","clicked_rate":"0.0000"}}} + + ' + http_version: + recorded_at: Thu, 28 Nov 2024 18:20:22 GMT +- request: + method: post + uri: https://api.mailgun.net/v1/analytics/usage/metrics + body: + encoding: UTF-8 + string: '{"resolution":"hour","metrics":["email_preview_count","email_preview_failed_count","email_validation_bulk_count","email_validation_count","email_validation_list_count","email_validation_mailgun_count","email_validation_mailjet_count","email_validation_public_count","email_validation_single_count","email_validation_valid_count","link_validation_count","link_validation_failed_count","processed_count","seed_test_count"],"include_aggregates":true,"start":"Tue, + 26 Nov 2024 20:56:50 -0500","duration":"1m","filter":{"AND":[{"attribute":"subaccount","comparator":"!=","values":[{"label":"12345","value":"12345"}]}]},"dimensions":["time"],"end":"Tue, + 28 Nov 2024 20:56:50 -0500","include_subaccounts":true}' + headers: + Accept: + - "*/*" + User-Agent: + - rest-client/2.1.0 (darwin23 x86_64) ruby/3.1.4p223 + Content-Type: + - application/json + Content-Length: + - '703' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Host: + - api.mailgun.net + Authorization: + - Basic xxx + response: + status: + code: 200 + message: OK + headers: + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Origin: + - "*" + Cache-Control: + - no-store + Content-Length: + - '1795' + Content-Type: + - application/json; charset=utf-8 + Date: + - Thu, 28 Nov 2024 18:20:23 GMT + Strict-Transport-Security: + - max-age=63072000; includeSubDomains + X-Mailgun-Key-Id: + - c02fd0ba-d8dbad66 + X-Xss-Protection: + - 1; mode=block + body: + encoding: UTF-8 + string: '{"start":"Tue, 29 Oct 2024 01:00:00 +0000","end":"Fri, 29 Nov 2024 + 01:00:00 +0000","resolution":"hour","duration":"1m","dimensions":["time"],"pagination":{"sort":"","skip":0,"limit":1500,"total":2},"items":[{"dimensions":[{"dimension":"time","value":"Wed, + 27 Nov 2024 00:00:00 +0000","display_value":"Wed, 27 Nov 2024 00:00:00 +0000"}],"metrics":{"processed_count":2,"email_validation_count":0,"email_validation_public_count":0,"email_validation_valid_count":0,"email_validation_single_count":0,"email_validation_bulk_count":0,"email_validation_list_count":0,"email_validation_mailgun_count":0,"email_validation_mailjet_count":0,"email_preview_count":0,"email_preview_failed_count":0,"link_validation_count":0,"link_validation_failed_count":0,"seed_test_count":0}},{"dimensions":[{"dimension":"time","value":"Thu, + 28 Nov 2024 00:00:00 +0000","display_value":"Thu, 28 Nov 2024 00:00:00 +0000"}],"metrics":{"processed_count":1,"email_validation_count":0,"email_validation_public_count":0,"email_validation_valid_count":0,"email_validation_single_count":0,"email_validation_bulk_count":0,"email_validation_list_count":0,"email_validation_mailgun_count":0,"email_validation_mailjet_count":0,"email_preview_count":0,"email_preview_failed_count":0,"link_validation_count":0,"link_validation_failed_count":0,"seed_test_count":0}}],"aggregates":{"metrics":{"permanent_failed_count":0,"processed_count":3,"email_validation_count":0,"email_validation_public_count":0,"email_validation_valid_count":0,"email_validation_single_count":0,"email_validation_bulk_count":0,"email_validation_list_count":0,"email_validation_mailgun_count":0,"email_validation_mailjet_count":0,"email_preview_count":0,"email_preview_failed_count":0,"link_validation_count":0,"link_validation_failed_count":0,"seed_test_count":0}}} + + ' + http_version: + recorded_at: Thu, 28 Nov 2024 18:20:23 GMT +recorded_with: VCR 3.0.3