Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,10 @@ Contact management:

- Contacts CRUD & Listing – [`contacts_api.rb`](examples/contacts_api.rb)

Email marketing:

- Email Campaigns CRUD, Lifecycle Actions & Stats – [`email_campaigns_api.rb`](examples/email_campaigns_api.rb)

General:

- Accounts API – [`accounts_api.rb`](examples/accounts_api.rb)
Expand Down
76 changes: 76 additions & 0 deletions examples/email_campaigns_api.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
require 'mailtrap'

client = Mailtrap::Client.new(api_key: 'your-api-key')
email_campaigns = Mailtrap::EmailCampaignsAPI.new(client)

# Create a new Email Campaign (always created in the draft state)
email_campaign = email_campaigns.create(
name: 'Spring Sale',
mailsend_domain_id: 'd2313359-acb4-4b87-bce6-f5774f6a1e37',
from_display_name: 'Acme Marketing',
from_local_part: 'news',
reply_to: {
display_name: 'Acme Support',
local_part: 'support',
domain: 'acme.com'
},
template_attributes: { subject: 'Spring is here — 30% off' }
)
# => #<struct Mailtrap::EmailCampaign id=4567, name="Spring Sale", current_state="draft", ...>

# Get all Email Campaigns (paginated, newest first; filter by name)
list = email_campaigns.list(per_page: 50, name: 'Spring')
# => #<struct Mailtrap::EmailCampaignsListResponse data=[#<struct Mailtrap::EmailCampaign ...>], pagination={...}>
list.data
# => [#<struct Mailtrap::EmailCampaign id=4567, name="Spring Sale", ...>]
list.pagination
# => {:token=>1, :prev_token=>nil, :next_token=>2, ...}

# Get a single Email Campaign
email_campaign = email_campaigns.get(email_campaign.id)
# => #<struct Mailtrap::EmailCampaign id=4567, name="Spring Sale", ...>

# Update a draft Email Campaign (partial; add the design and the audience)
email_campaigns.update(
email_campaign.id,
name: 'Spring Sale (updated)',
template_attributes: {
subject: 'New subject',
body_html: '<html><body><h1>Hi {{first_name}}!</h1>' \
'<p><a href="__unsubscribe_url__">Unsubscribe</a></p></body></html>',
merge_tags: ['first_name']
},
delivery_mode: 'gradual',
delivery_options: { emails_per_hour: 1000 },
contact_list_ids: [55, 56],
contact_segment_ids: [12]
)
# => #<struct Mailtrap::EmailCampaign id=4567, name="Spring Sale (updated)", ...>

# Schedule the draft Email Campaign to start sending at a future time
email_campaigns.schedule(email_campaign.id, '2026-06-01T09:00:00.000Z')
# => #<struct Mailtrap::EmailCampaign id=4567, current_state="scheduled", ...>

# Cancel the scheduled Email Campaign (returns it to the draft state)
email_campaigns.cancel(email_campaign.id)
# => #<struct Mailtrap::EmailCampaign id=4567, current_state="draft", ...>

# Start sending the draft Email Campaign immediately
email_campaigns.start(email_campaign.id)
# => #<struct Mailtrap::EmailCampaign id=4567, current_state="started", ...>

# Terminate a sending Email Campaign
email_campaigns.terminate(email_campaign.id)
# => #<struct Mailtrap::EmailCampaign id=4567, current_state="terminating", ...>

# Reset a scheduled Email Campaign back to draft
email_campaigns.reset(email_campaign.id)
# => #<struct Mailtrap::EmailCampaign id=4567, current_state="draft", ...>

# Get Email Campaign statistics (optionally narrow the aggregation window)
email_campaigns.stats(email_campaign.id, start_date: '2026-05-01', end_date: '2026-05-31')
# => #<struct Mailtrap::EmailCampaignStats delivery_count=1450, open_count=820, delivery_rate=0.9667, ...>

# Delete an Email Campaign (returns nil; the campaign must not be in a sending state)
email_campaigns.delete(email_campaign.id)
# => nil
1 change: 1 addition & 0 deletions lib/mailtrap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
require_relative 'mailtrap/contact_exports_api'
require_relative 'mailtrap/contact_events_api'
require_relative 'mailtrap/suppressions_api'
require_relative 'mailtrap/email_campaigns_api'
require_relative 'mailtrap/sending_domains_api'
require_relative 'mailtrap/company_info_api'
require_relative 'mailtrap/email_logs_api'
Expand Down
104 changes: 104 additions & 0 deletions lib/mailtrap/email_campaign.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# frozen_string_literal: true

module Mailtrap
# Data Transfer Object for Email Campaign Stats
#
# Aggregated campaign performance metrics. All counts and rates are +0+ when the
# campaign has not been started.
# @see https://api-docs.mailtrap.io/docs/mailtrap-api-docs/email-campaigns
# @attr_reader delivery_count [Integer] Number of delivered messages
# @attr_reader open_count [Integer] Number of opened messages
# @attr_reader click_count [Integer] Number of clicked messages
# @attr_reader bounce_count [Integer] Number of bounced messages
# @attr_reader unsubscription_count [Integer] Number of unsubscriptions
# @attr_reader sent_count [Integer] Number of sent messages
# @attr_reader spam_count [Integer] Number of spam complaints
# @attr_reader message_count [Integer] Total number of messages
# @attr_reader reject_count [Integer] Number of rejected messages
# @attr_reader delivery_rate [Float] Share of sent messages that were delivered (0–1)
# @attr_reader open_rate [Float] Share of delivered messages that were opened (0–1)
# @attr_reader click_rate [Float] Share of delivered messages that were clicked (0–1)
# @attr_reader bounce_rate [Float] Share of sent messages that bounced (0–1)
# @attr_reader spam_rate [Float] Share of sent messages marked as spam (0–1)
# @attr_reader unsubscription_rate [Float] Share of delivered messages that unsubscribed (0–1)
EmailCampaignStats = Struct.new(
:delivery_count,
:open_count,
:click_count,
:bounce_count,
:unsubscription_count,
:sent_count,
:spam_count,
:message_count,
:reject_count,
:delivery_rate,
:open_rate,
:click_rate,
:bounce_rate,
:spam_rate,
:unsubscription_rate,
keyword_init: true
)

# Data Transfer Object for Email Campaign
# @see https://api-docs.mailtrap.io/docs/mailtrap-api-docs/email-campaigns
# @attr_reader id [Integer] The email campaign ID
# @attr_reader type [String] Resource type discriminator
# (+ContactsEmailCampaign+ or +RecipientsEmailCampaign+)
# @attr_reader mailsend_domain_id [String] UUID of the sending domain used for the campaign
# @attr_reader mailsend_domain_name [String] Name of the sending domain used for the campaign
# @attr_reader name [String] Campaign name
# @attr_reader from_local_part [String] Local part (before the @) of the From address
# @attr_reader from_display_name [String] Display name shown in the From header
# @attr_reader reply_to [Hash, nil] Reply-To address parts (+display_name+, +local_part+, +domain+)
# @attr_reader current_state [String] Lifecycle state (+draft+, +scheduled+, +started+, +queued+,
# +paused+, +terminating+, +under_review+, +finished+, +failed+, +failed_immediately+)
# @attr_reader current_state_metadata [Hash, nil] Metadata about the most recent state transition
# (+reason+, +error+, +errors+ as an array of +{message, rcpt_index}+ objects, +scheduled_at+)
# @attr_reader created_at [String] The creation timestamp
# @attr_reader updated_at [String] The last update timestamp
# @attr_reader last_started_at [String, nil] When the campaign was last started, or +nil+
# @attr_reader last_started_at_date [String, nil] Date the campaign was last started, present only when started
# @attr_reader recipient_total_count [Integer, nil] Total number of recipients,
# or +nil+ until the audience is resolved
# @attr_reader contact_list_ids [Array<Integer>] IDs of the contact lists included in the campaign's audience
# @attr_reader contact_segment_ids [Array<Integer>] IDs of the contact segments included in the campaign's audience
# @attr_reader delivery_mode [String] How the campaign is delivered (+rapid+ or +gradual+)
# @attr_reader delivery_options [Hash, nil] Delivery throttling options (+emails_per_hour+)
# @attr_reader template [Hash, nil] The campaign template (+id+, +subject+, +merge_tags+,
# +body_html+, +body_text+; bodies are omitted on list responses)
EmailCampaign = Struct.new(
:id,
:type,
:mailsend_domain_id,
:mailsend_domain_name,
:name,
:from_local_part,
:from_display_name,
:reply_to,
:current_state,
:current_state_metadata,
:created_at,
:updated_at,
:last_started_at,
:last_started_at_date,
:recipient_total_count,
:contact_list_ids,
:contact_segment_ids,
:delivery_mode,
:delivery_options,
:template,
keyword_init: true
)

# Response from listing email campaigns (paginated)
# @see https://api-docs.mailtrap.io/docs/mailtrap-api-docs/email-campaigns
# @attr_reader data [Array<EmailCampaign>] Page of email campaigns, newest first
# @attr_reader pagination [Hash] Page-token pagination metadata
# (+token+, +prev_token+, +next_token+, +first_url+, +prev_url+, +current_url+, +next_url+)
EmailCampaignsListResponse = Struct.new(
:data,
:pagination,
keyword_init: true
)
end
187 changes: 187 additions & 0 deletions lib/mailtrap/email_campaigns_api.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
# frozen_string_literal: true

require_relative 'base_api'
require_relative 'email_campaign'

module Mailtrap
class EmailCampaignsAPI
include BaseAPI

self.supported_options = %i[
name
mailsend_domain_id
from_display_name
from_local_part
reply_to
template_attributes
delivery_mode
delivery_options
contact_list_ids
contact_segment_ids
].freeze

self.response_class = EmailCampaign

attr_reader :client

# @param client [Mailtrap::Client] The client instance
def initialize(client = Mailtrap::Client.new)
@client = client
end

# Lists email campaigns for the account, newest first
# @param per_page [Integer, nil] Number of campaigns per page (max 100, default 50)
# @param name [String, nil] Filter campaigns by name
# @param token [Integer, nil] Page number to retrieve (page-token pagination, default 1)
# @return [EmailCampaignsListResponse] The page of campaigns and pagination metadata
# @!macro api_errors
def list(per_page: nil, name: nil, token: nil)
query_params = {}
query_params[:per_page] = per_page unless per_page.nil?
query_params[:search] = name unless name.nil?
query_params[:token] = token unless token.nil?

response = client.get(base_path, query_params)

EmailCampaignsListResponse.new(
data: Array(response[:data]).map { |item| build_entity(item, response_class) },
pagination: response[:pagination]
)
end

# Retrieves a specific email campaign
# @param email_campaign_id [Integer] The email campaign ID
# @return [EmailCampaign] Email campaign object
# @!macro api_errors
def get(email_campaign_id)
base_get(email_campaign_id)
end

# Creates a new email campaign in the +draft+ state
# @param [Hash] options The parameters to create
# @option options [String] :name Campaign name (required)
# @option options [String] :mailsend_domain_id UUID of the verified sending domain (required)
# @option options [String] :from_display_name Display name shown in the From header
# @option options [String] :from_local_part Local part (before the @) of the From address (required)
# @option options [Hash] :reply_to Reply-To address parts (+display_name+, +local_part+, +domain+)
# @option options [Hash] :template_attributes Template attributes (+subject+ (required),
# +body_html+, +body_text+, +merge_tags+)
# @option options [String] :delivery_mode How the campaign is delivered (+rapid+ or +gradual+)
# @option options [Hash] :delivery_options Delivery throttling options (+emails_per_hour+),
# applies when +delivery_mode+ is +gradual+
# @option options [Array<Integer>] :contact_list_ids IDs of contact lists to send to
# @option options [Array<Integer>] :contact_segment_ids IDs of contact segments to send to
# @return [EmailCampaign] Created email campaign
# @!macro api_errors
# @raise [ArgumentError] If invalid options are provided
def create(options)
base_create(options)
end

# Updates an existing +draft+ email campaign. Only the provided attributes are changed;
# +template_attributes+ sub-fields are also updated partially, in place.
# @param email_campaign_id [Integer] The email campaign ID
# @param [Hash] options The parameters to update; accepts the same fields as {#create}
# @option options [String] :name Campaign name
# @option options [String] :mailsend_domain_id UUID of the verified sending domain
# @option options [String] :from_display_name Display name shown in the From header
# @option options [String] :from_local_part Local part (before the @) of the From address
# @option options [Hash] :reply_to Reply-To address parts (+display_name+, +local_part+, +domain+)
# @option options [Hash] :template_attributes Template attributes (+subject+, +body_html+,
# +body_text+, +merge_tags+)
# @option options [String] :delivery_mode How the campaign is delivered (+rapid+ or +gradual+)
# @option options [Hash] :delivery_options Delivery throttling options (+emails_per_hour+),
# applies when +delivery_mode+ is +gradual+
# @option options [Array<Integer>] :contact_list_ids IDs of contact lists to send to
# @option options [Array<Integer>] :contact_segment_ids IDs of contact segments to send to
# @return [EmailCampaign] Updated email campaign
# @!macro api_errors
# @raise [ArgumentError] If invalid options are provided
def update(email_campaign_id, options)
base_update(email_campaign_id, options)
end

# Deletes an email campaign. The campaign must not be in a sending state.
# @param email_campaign_id [Integer] The email campaign ID
# @return nil
# @!macro api_errors
def delete(email_campaign_id)
base_delete(email_campaign_id)
end

# Starts sending a +draft+ campaign immediately
# @param email_campaign_id [Integer] The email campaign ID
# @return [EmailCampaign] The started email campaign
# @!macro api_errors
def start(email_campaign_id)
perform_action(email_campaign_id, :start)
end

# Schedules a +draft+ campaign to start sending at a future time.
# The time is reported back in +current_state_metadata.scheduled_at+.
# @param email_campaign_id [Integer] The email campaign ID
# @param datetime [String] When to send the campaign (ISO 8601); must be in the future
# and no more than 1 month ahead
# @return [EmailCampaign] The scheduled email campaign
# @!macro api_errors
def schedule(email_campaign_id, datetime)
perform_action(email_campaign_id, :schedule, { datetime: })
end

# Cancels a +scheduled+ campaign, returning it to the +draft+ state
# @param email_campaign_id [Integer] The email campaign ID
# @return [EmailCampaign] The cancelled email campaign
# @!macro api_errors
def cancel(email_campaign_id)
perform_action(email_campaign_id, :cancel)
end

# Terminates a campaign that is currently sending (+started+, +queued+, or +paused+),
# aborting the in-flight send
# @param email_campaign_id [Integer] The email campaign ID
# @return [EmailCampaign] The terminated email campaign
# @!macro api_errors
def terminate(email_campaign_id)
perform_action(email_campaign_id, :terminate)
end

# Resets a +scheduled+ campaign back to the +draft+ state
# @param email_campaign_id [Integer] The email campaign ID
# @return [EmailCampaign] The reset email campaign
# @!macro api_errors
def reset(email_campaign_id)
perform_action(email_campaign_id, :reset)
end

# Retrieves aggregated performance statistics for an email campaign.
# By default statistics are aggregated since the campaign was last started.
# @param email_campaign_id [Integer] The email campaign ID
# @param start_date [String, nil] Start of the aggregation window (inclusive), +YYYY-MM-DD+
# @param end_date [String, nil] End of the aggregation window (inclusive), +YYYY-MM-DD+
# @return [EmailCampaignStats] Aggregated campaign statistics
# @!macro api_errors
def stats(email_campaign_id, start_date: nil, end_date: nil)
query_params = {}
query_params[:start_date] = start_date unless start_date.nil?
query_params[:end_date] = end_date unless end_date.nil?

response = client.get("#{base_path}/#{email_campaign_id}/stats", query_params)
build_entity(response[:data], EmailCampaignStats)
end

private

def perform_action(email_campaign_id, action, body = nil)
response = client.post("#{base_path}/#{email_campaign_id}/#{action}", body)
handle_response(response)
end

def base_path
'/api/email_campaigns'
end

def handle_response(response)
build_entity(response[:data], response_class)
end
end
end
Loading
Loading