Skip to content

Commit 923642c

Browse files
committed
Add Contacts API functionality
1 parent 8dc916f commit 923642c

File tree

7 files changed

+630
-1
lines changed

7 files changed

+630
-1
lines changed

examples/contact.rb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
require 'bundler/setup'
2+
require 'mailtrap'
3+
account_id = 1_111_111
4+
client = Mailtrap::Client.new(api_key: 'your-api-key')
5+
6+
contact_list = Mailtrap::ContactList.new(account_id, client)
7+
list = contact_list.create(name: 'Test List')
8+
9+
contacts = Mailtrap::Contact.new(account_id, client)
10+
contact = contacts.create(email: '[email protected]', fields: { first_name: 'John Doe' }, list_ids: [list.id])
11+
puts "Created Contact: #{contact.id}"
12+
13+
contact = contacts.get(contact.id)
14+
puts "Contact: #{contact.email}"
15+
16+
contact = contacts.update(contact.id, email: '[email protected]', fields: { first_name: 'Jane Doe' }, list_ids: [2])
17+
puts "Updated Contact: #{contact.data.email}"
18+
19+
contacts.delete(contact.data.id)
20+
puts 'Contact deleted'
21+
22+
lists = contact_list.list
23+
puts "Contact Lists: #{lists}"
24+
25+
contact_list.update(list.id, name: 'Test List Updated')
26+
27+
list = contact_list.get(list.id)
28+
puts "Contact List: #{list.name}"
29+
30+
contact_list.delete(list.id)
31+
puts 'Contact List deleted'

lib/mailtrap.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,7 @@
55
require_relative 'mailtrap/errors'
66
require_relative 'mailtrap/version'
77
require_relative 'mailtrap/template'
8+
require_relative 'mailtrap/contact'
9+
require_relative 'mailtrap/contact_list'
810

911
module Mailtrap; end

lib/mailtrap/client.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,8 @@ def handle_response(response) # rubocop:disable Metrics/AbcSize, Metrics/Cycloma
150150
body = json_response(response.body)
151151
raise Mailtrap::AuthorizationError, [body[:errors] || body[:error]].flatten
152152
when Net::HTTPForbidden
153-
raise Mailtrap::RejectionError, json_response(response.body)[:errors]
153+
body = json_response(response.body)
154+
raise Mailtrap::RejectionError, [body[:errors] || body[:error]].flatten
154155
when Net::HTTPPayloadTooLarge
155156
raise Mailtrap::MailSizeError, ['message too large']
156157
when Net::HTTPTooManyRequests

lib/mailtrap/contact.rb

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# frozen_string_literal: true
2+
3+
module Mailtrap
4+
# Data Transfer Object for Contact Create Request
5+
# @attr_reader email [String] The contact's email address (required)
6+
# @attr_reader fields [Hash] Object of fields with merge tags
7+
# @attr_reader list_ids [Array<Integer>] Array of list IDs
8+
ContactCreateRequest = Struct.new(:email, :fields, :list_ids, keyword_init: true) do
9+
# @return [Hash] The contact request attributes as a hash
10+
def to_h
11+
super.compact
12+
end
13+
end
14+
15+
# Data Transfer Object for Contact Update Request
16+
# @attr_reader email [String] The contact's email address (required)
17+
# @attr_reader fields [Hash] Object of fields with merge tags
18+
# @attr_reader list_ids_included [Array<Integer>] Array of list IDs to include
19+
# @attr_reader list_ids_excluded [Array<Integer>] Array of list IDs to exclude
20+
# @attr_reader unsubscribed [Boolean] Whether to unsubscribe the contact
21+
ContactUpdateRequest = Struct.new(:email, :fields, :list_ids_included, :list_ids_excluded, :unsubscribed,
22+
keyword_init: true) do
23+
# @return [Hash] The contact request attributes as a hash
24+
def to_h
25+
super.compact
26+
end
27+
end
28+
29+
# Data Transfer Object for Contact
30+
# @attr_reader id [String] The contact ID
31+
# @attr_reader email [String] The contact's email address
32+
# @attr_reader fields [Hash] Object of fields with merge tags
33+
# @attr_reader list_ids [Array<Integer>] Array of list IDs
34+
# @attr_reader status [String] The contact status (subscribed/unsubscribed)
35+
# @attr_reader created_at [Integer] The creation timestamp
36+
# @attr_reader updated_at [Integer] The last update timestamp
37+
ContactDTO = Struct.new(
38+
:id,
39+
:email,
40+
:fields,
41+
:list_ids,
42+
:status,
43+
:created_at,
44+
:updated_at,
45+
keyword_init: true
46+
) do
47+
# @return [Hash] The contact attributes as a hash
48+
def to_h
49+
super.compact
50+
end
51+
end
52+
53+
# Data Transfer Object for Contact Update Response
54+
# @attr_reader action [String] The performed action (created/updated)
55+
# @attr_reader data [ContactDTO] The contact data
56+
ContactUpdateResponse = Struct.new(:action, :data, keyword_init: true) do
57+
def initialize(*)
58+
super
59+
self.data = ContactDTO.new(data) if data.is_a?(Hash)
60+
end
61+
62+
# @return [Hash] The response attributes as a hash
63+
def to_h
64+
super.compact
65+
end
66+
end
67+
68+
class Contact
69+
def initialize(account_id, client = Mailtrap::Client.new)
70+
@account_id = account_id
71+
@client = client
72+
end
73+
74+
# Retrieves a specific contact
75+
# @param contact_id [String] The contact identifier, which can be either a UUID or an email address
76+
# @return [ContactDTO] Contact object
77+
# @raise [Mailtrap::Error] if the contact is not found or if there's an API error
78+
# @example Get contact by UUID
79+
# contact = client.get('019706a8-9612-77be-8586-4f26816b467a')
80+
# @example Get contact by email
81+
# contact = client.get('[email protected]')
82+
def get(contact_id)
83+
response = @client.get("#{base_path}/#{contact_id}")
84+
ContactDTO.new(response[:data])
85+
end
86+
87+
# Creates a new contact
88+
# @param request [ContactCreateRequest, Hash] The contact create request object or a hash with the same attributes
89+
# @return [ContactDTO] Created contact object
90+
def create(request)
91+
normalised = request.is_a?(ContactCreateRequest) ? request : ContactCreateRequest.new(**request)
92+
response = @client.post(base_path, { contact: normalised.to_h })
93+
ContactDTO.new(response[:data])
94+
end
95+
96+
# Updates an existing contact
97+
# @param contact_id [String] The contact ID
98+
# @param request [ContactUpdateRequest, Hash] The contact update request object or a hash with the same attributes
99+
# @return [ContactUpdateResponse] Response containing the action performed and contact data
100+
def update(contact_id, request)
101+
normalised = request.is_a?(ContactUpdateRequest) ? request : ContactUpdateRequest.new(**request)
102+
response = @client.patch("#{base_path}/#{contact_id}", { contact: normalised.to_h })
103+
ContactUpdateResponse.new(response)
104+
end
105+
106+
# Deletes a contact
107+
# @param contact_id [String] The contact ID
108+
# @return [Boolean] true if successful
109+
def delete(contact_id)
110+
@client.delete("#{base_path}/#{contact_id}")
111+
end
112+
113+
private
114+
115+
def base_path
116+
"/api/accounts/#{@account_id}/contacts"
117+
end
118+
end
119+
end

lib/mailtrap/contact_list.rb

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# frozen_string_literal: true
2+
3+
module Mailtrap
4+
# Data Transfer Object for Contact List Create Request
5+
# @attr_reader name [String] The name of the contact list (required)
6+
ContactListCreateRequest = Struct.new(:name, keyword_init: true) do
7+
# @return [Hash] The contact list request attributes as a hash
8+
def to_h
9+
super.compact
10+
end
11+
end
12+
13+
# Data Transfer Object for Contact List
14+
# @attr_reader id [Integer] The contact list ID
15+
# @attr_reader name [String] The name of the contact list
16+
ContactListDTO = Struct.new(:id, :name, keyword_init: true) do
17+
# @return [Hash] The contact list attributes as a hash
18+
def to_h
19+
super.compact
20+
end
21+
end
22+
23+
class ContactList
24+
def initialize(account_id, client = Mailtrap::Client.new)
25+
@account_id = account_id
26+
@client = client
27+
end
28+
29+
# Retrieves a specific contact list
30+
# @param list_id [Integer] The contact list identifier
31+
# @return [ContactListDTO] Contact list object
32+
# @raise [Mailtrap::Error] if the contact list is not found or if there's an API error
33+
def get(list_id)
34+
response = @client.get("#{base_path}/#{list_id}")
35+
ContactListDTO.new(response)
36+
end
37+
38+
# Creates a new contact list
39+
# @param request [ContactListCreateRequest, Hash] The contact list create request object or a hash
40+
# @return [ContactListDTO] Created contact list object
41+
def create(request)
42+
normalised = request.is_a?(ContactListCreateRequest) ? request : ContactListCreateRequest.new(**request)
43+
response = @client.post(base_path, normalised.to_h)
44+
ContactListDTO.new(response)
45+
end
46+
47+
# Updates an existing contact list
48+
# @param list_id [Integer] The contact list ID
49+
# @param request [ContactListCreateRequest, Hash] The contact list update request object or a hash
50+
# @return [ContactListDTO] Updated contact list object
51+
def update(list_id, request)
52+
normalised = request.is_a?(ContactListCreateRequest) ? request : ContactListCreateRequest.new(**request)
53+
response = @client.patch("#{base_path}/#{list_id}", normalised.to_h)
54+
ContactListDTO.new(response)
55+
end
56+
57+
# Deletes a contact list
58+
# @param list_id [Integer] The contact list ID
59+
# @return [Boolean] true if successful
60+
def delete(list_id)
61+
@client.delete("#{base_path}/#{list_id}")
62+
end
63+
64+
# Lists all contact lists for the account
65+
# @return [Array<ContactListDTO>] Array of contact list objects
66+
def list
67+
response = @client.get(base_path)
68+
response.map { |list| ContactListDTO.new(list) }
69+
end
70+
71+
private
72+
73+
def base_path
74+
"/api/accounts/#{@account_id}/contacts/lists"
75+
end
76+
end
77+
end

spec/mailtrap/contact_list_spec.rb

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
RSpec.describe Mailtrap::ContactList do
2+
let(:client) { described_class.new('1111111', Mailtrap::Client.new(api_key: 'correct-api-key')) }
3+
let(:base_url) { 'https://mailtrap.io/api/accounts/1111111' }
4+
5+
around do |example|
6+
VCR.turned_off { example.run }
7+
end
8+
9+
describe '#' do
10+
let(:expected_response) do
11+
[
12+
{ 'id' => 1, 'name' => 'List 1' },
13+
{ 'id' => 2, 'name' => 'List 2' }
14+
]
15+
end
16+
17+
it 'returns all contact lists' do
18+
stub_request(:get, "#{base_url}/contacts/lists")
19+
.to_return(
20+
status: 200,
21+
body: expected_response.to_json,
22+
headers: { 'Content-Type' => 'application/json' }
23+
)
24+
25+
response = client.list
26+
expect(response).to be_a(Array)
27+
expect(response.length).to eq(2)
28+
expect(response.first).to have_attributes(id: 1, name: 'List 1')
29+
end
30+
end
31+
32+
describe '#get' do
33+
let(:contact_list_id) { 1 }
34+
let(:expected_response) do
35+
{ 'id' => 1, 'name' => 'List 1' }
36+
end
37+
38+
it 'returns a specific contact list' do
39+
stub_request(:get, "#{base_url}/contacts/lists/#{contact_list_id}")
40+
.to_return(
41+
status: 200,
42+
body: expected_response.to_json,
43+
headers: { 'Content-Type' => 'application/json' }
44+
)
45+
46+
response = client.get(contact_list_id)
47+
expect(response).to have_attributes(id: 1, name: 'List 1')
48+
end
49+
50+
it 'raises error when contact list not found' do
51+
stub_request(:get, "#{base_url}/contacts/lists/999")
52+
.to_return(
53+
status: 404,
54+
body: { 'error' => 'Not Found' }.to_json,
55+
headers: { 'Content-Type' => 'application/json' }
56+
)
57+
58+
expect { client.get(999) }.to raise_error(Mailtrap::Error)
59+
end
60+
end
61+
62+
describe '#create' do
63+
let(:contact_list_name) { 'List 1' }
64+
let(:expected_response) do
65+
{ 'id' => 1, 'name' => contact_list_name }
66+
end
67+
68+
it 'creates a new contact list' do
69+
stub_request(:post, "#{base_url}/contacts/lists")
70+
.with(
71+
body: { name: contact_list_name }.to_json
72+
)
73+
.to_return(
74+
status: 200,
75+
body: expected_response.to_json,
76+
headers: { 'Content-Type' => 'application/json' }
77+
)
78+
79+
response = client.create(name: contact_list_name)
80+
expect(response).to have_attributes(id: 1, name: contact_list_name)
81+
end
82+
83+
it 'raises error when rate limit exceeded' do
84+
stub_request(:post, "#{base_url}/contacts/lists")
85+
.with(
86+
body: { name: contact_list_name }.to_json
87+
)
88+
.to_return(
89+
status: 429,
90+
body: { 'errors' => 'Rate limit exceeded' }.to_json,
91+
headers: { 'Content-Type' => 'application/json' }
92+
)
93+
94+
expect { client.create(name: contact_list_name) }.to raise_error(Mailtrap::Error)
95+
end
96+
end
97+
98+
describe '#update' do
99+
let(:contact_list_id) { 2 }
100+
let(:new_name) { 'List 2' }
101+
let(:expected_response) do
102+
{ 'id' => 2, 'name' => new_name }
103+
end
104+
105+
it 'updates a contact list' do
106+
stub_request(:patch, "#{base_url}/contacts/lists/#{contact_list_id}")
107+
.with(
108+
body: { name: new_name }.to_json
109+
)
110+
.to_return(
111+
status: 200,
112+
body: expected_response.to_json,
113+
headers: { 'Content-Type' => 'application/json' }
114+
)
115+
116+
response = client.update(contact_list_id, name: new_name)
117+
expect(response).to have_attributes(id: 2, name: new_name)
118+
end
119+
end
120+
121+
describe '#delete' do
122+
let(:contact_list_id) { 1 }
123+
124+
it 'deletes a contact list' do
125+
stub_request(:delete, "#{base_url}/contacts/lists/#{contact_list_id}")
126+
.to_return(status: 204)
127+
128+
response = client.delete(contact_list_id)
129+
expect(response).to be true
130+
end
131+
end
132+
end

0 commit comments

Comments
 (0)