Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
79 changes: 79 additions & 0 deletions examples/email_campaigns_api.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
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',
domain_id: 4321,
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, search: '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
schedule_at = (Time.now.utc + 86_400).strftime('%Y-%m-%dT%H:%M:%S.000Z')
email_campaigns.schedule(email_campaign.id, schedule_at)
# => #<struct Mailtrap::EmailCampaign id=4567, current_state="scheduled", ...>
Comment thread
coderabbitai[bot] marked this conversation as resolved.

# 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", ...>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

# Get Email Campaign statistics (pass start_date/end_date to narrow the aggregation window)
email_campaigns.stats(email_campaign.id)
# => #<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,
# so wait until termination completes first)
sleep 1 while email_campaigns.get(email_campaign.id).current_state == 'terminating'
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
98 changes: 98 additions & 0 deletions lib/mailtrap/email_campaign.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# 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 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,
: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 domain_id [Integer] ID of the sending domain used for the campaign,
# as returned by the Sending Domains endpoints
# @attr_reader 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,
:domain_id,
: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
189 changes: 189 additions & 0 deletions lib/mailtrap/email_campaigns_api.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
# frozen_string_literal: true

require_relative 'base_api'
require_relative 'email_campaign'

module Mailtrap
class EmailCampaignsAPI
include BaseAPI

self.supported_options = %i[
name
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 search [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, search: nil, token: nil)
query_params = {}
query_params[:per_page] = per_page unless per_page.nil?
query_params[:search] = search unless search.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 [Integer] :domain_id ID of the verified sending domain (required),
# as returned by the Sending Domains endpoints
# @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 [Integer] :domain_id ID of the verified sending domain,
# as returned by the Sending Domains endpoints
# @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