-
Notifications
You must be signed in to change notification settings - Fork 7
Contact Imports API #72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
0a21de3
42839bc
3801aa6
544023c
89c9a2a
d753fb4
5c81550
eda7244
bfd6489
87d118d
618e7e3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# frozen_string_literal: true | ||
|
||
module Mailtrap | ||
# Data Transfer Object for Contact Import | ||
# @attr_reader id [Integer] The contact import ID | ||
# @attr_reader status [String] The status of the import (created, started, finished, failed) | ||
# @attr_reader created_contacts_count [Integer, nil] Number of contacts created in this import | ||
# @attr_reader updated_contacts_count [Integer, nil] Number of contacts updated in this import | ||
# @attr_reader contacts_over_limit_count [Integer, nil] Number of contacts over the allowed limit | ||
ContactImport = Struct.new( | ||
:id, | ||
:status, | ||
:created_contacts_count, | ||
:updated_contacts_count, | ||
:contacts_over_limit_count, | ||
keyword_init: true | ||
) do | ||
# @return [Hash] The contact attributes as a hash | ||
def to_h | ||
super.compact | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
# frozen_string_literal: true | ||
|
||
require_relative 'contact_import' | ||
require_relative 'contacts_import_request' | ||
|
||
module Mailtrap | ||
class ContactImportsAPI | ||
include BaseAPI | ||
|
||
self.supported_options = %i[email fields list_ids_included list_ids_excluded] | ||
|
||
self.response_class = ContactImport | ||
|
||
# Retrieves a specific contact import | ||
# @param import_id [Integer] The contact import identifier | ||
# @return [ContactImport] Contact import object | ||
# @!macro api_errors | ||
def get(import_id) | ||
base_get(import_id) | ||
end | ||
|
||
# Create contacts import | ||
# | ||
# @example Using Mailtrap::ContactsImportRequest | ||
# import_request = Mailtrap::ContactsImportRequest.new.tap do |req| | ||
# req.upsert(email: 'jane@example.com', fields: { first_name: 'Jane' }) | ||
# .add_to_lists(email: 'jane@example.com', list_ids: [1]) | ||
# .remove_from_lists(email: 'jane@example.com', list_ids: [2]) | ||
# req.upsert(email: 'john@example.com', fields: { first_name: 'John' }) | ||
# .add_to_lists(email: 'john@example.com', list_ids: [1]) | ||
# .remove_from_lists(email: 'john@example.com', list_ids: [2]) | ||
# end | ||
# contact_imports.create(import_request) | ||
# | ||
# @example Using plain hash | ||
# contact_imports.create([ | ||
# {email: 'john@example.com', fields: { first_name: 'John' }, list_ids_included: [1], list_ids_excluded: [2]}, | ||
# {email: 'jane@example.com', fields: { first_name: 'Jane' }, list_ids_included: [1], list_ids_excluded: [2]} | ||
# ]) | ||
# | ||
# @param contacts [Mailtrap::ContactsImportRequest, Array<Hash>] The contacts import request | ||
# | ||
# @return [ContactImport] Created contact import object | ||
# @!macro api_errors | ||
# @raise [ArgumentError] If invalid options are provided | ||
def create(contacts) | ||
contact_data = contacts.to_a.each do |contact| | ||
validate_options!(contact, supported_options) | ||
end | ||
response = client.post(base_path, contacts: contact_data) | ||
handle_response(response) | ||
end | ||
alias start create | ||
|
||
private | ||
|
||
def base_path | ||
"/api/accounts/#{account_id}/contacts/imports" | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,67 @@ | ||||||||||||||||||||||||||||||||
# frozen_string_literal: true | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
module Mailtrap | ||||||||||||||||||||||||||||||||
# A builder class for creating contact import requests | ||||||||||||||||||||||||||||||||
# Allows you to build a collection of contacts with their associated fields and list memberships | ||||||||||||||||||||||||||||||||
class ContactsImportRequest | ||||||||||||||||||||||||||||||||
def initialize | ||||||||||||||||||||||||||||||||
@data = Hash.new do |h, k| | ||||||||||||||||||||||||||||||||
h[k] = { email: k, fields: {}, list_ids_included: [], list_ids_excluded: [] } | ||||||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
# Creates or updates a contact with the provided email and fields | ||||||||||||||||||||||||||||||||
# @param email [String] The contact's email address | ||||||||||||||||||||||||||||||||
# @param fields [Hash] Contact fields in the format: field_merge_tag => String, Integer, Float, Boolean, or | ||||||||||||||||||||||||||||||||
# ISO-8601 date string (yyyy-mm-dd) | ||||||||||||||||||||||||||||||||
# @return [ContactsImportRequest] Returns self for method chaining | ||||||||||||||||||||||||||||||||
def upsert(email:, fields: {}) | ||||||||||||||||||||||||||||||||
validate_email!(email) | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
@data[email][:fields].merge!(fields) | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
self | ||||||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||||||
Comment on lines
+18
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nil or non-Hash fields will raise; normalize email before indexing.
Apply: - def upsert(email:, fields: {})
- validate_email!(email)
-
- @data[email][:fields].merge!(fields)
+ def upsert(email:, fields: {})
+ email = normalize_email(email)
+ validate_email!(email)
+ fields = {} if fields.nil?
+ raise ArgumentError, 'fields must be a Hash' unless fields.is_a?(Hash)
+ @data[email][:fields].merge!(fields.transform_keys(&:to_s)) Add
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
# Adds a contact to the specified lists | ||||||||||||||||||||||||||||||||
# @param email [String] The contact's email address | ||||||||||||||||||||||||||||||||
# @param list_ids [Array<Integer>] Array of list IDs to add the contact to | ||||||||||||||||||||||||||||||||
# @return [ContactsImportRequest] Returns self for method chaining | ||||||||||||||||||||||||||||||||
def add_to_lists(email:, list_ids:) | ||||||||||||||||||||||||||||||||
validate_email!(email) | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
append_list_ids email:, list_ids:, key: :list_ids_included | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
self | ||||||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||||||
Comment on lines
+30
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handle nil/invalid list_ids and normalize email before appending.
Apply: - def add_to_lists(email:, list_ids:)
- validate_email!(email)
-
- append_list_ids email:, list_ids:, key: :list_ids_included
+ def add_to_lists(email:, list_ids:)
+ email = normalize_email(email)
+ validate_email!(email)
+ list_ids = normalize_list_ids(list_ids)
+ append_list_ids email:, list_ids:, key: :list_ids_included 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
# Removes a contact from the specified lists | ||||||||||||||||||||||||||||||||
# @param email [String] The contact's email address | ||||||||||||||||||||||||||||||||
# @param list_ids [Array<Integer>] Array of list IDs to remove the contact from | ||||||||||||||||||||||||||||||||
# @return [ContactsImportRequest] Returns self for method chaining | ||||||||||||||||||||||||||||||||
def remove_from_lists(email:, list_ids:) | ||||||||||||||||||||||||||||||||
validate_email!(email) | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
append_list_ids email:, list_ids:, key: :list_ids_excluded | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
self | ||||||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||||||
Comment on lines
+42
to
+48
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mirror the same validation/normalization in remove_from_lists. Apply: - def remove_from_lists(email:, list_ids:)
- validate_email!(email)
-
- append_list_ids email:, list_ids:, key: :list_ids_excluded
+ def remove_from_lists(email:, list_ids:)
+ email = normalize_email(email)
+ validate_email!(email)
+ list_ids = normalize_list_ids(list_ids)
+ append_list_ids email:, list_ids:, key: :list_ids_excluded 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
# @return [Array<Hash>] Array of contact objects ready for import | ||||||||||||||||||||||||||||||||
def to_a | ||||||||||||||||||||||||||||||||
@data.values | ||||||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||||||
Comment on lines
+51
to
+53
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. to_a exposes live internal structures; deep copy to avoid accidental mutation. Returning references allows callers/tests to mutate builder state unexpectedly. Apply: - def to_a
- @data.values
- end
+ def to_a
+ @data.values.map do |c|
+ {
+ email: c[:email],
+ fields: c[:fields].dup,
+ list_ids_included: c[:list_ids_included].dup,
+ list_ids_excluded: c[:list_ids_excluded].dup
+ }
+ end
+ end 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
private | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
def validate_email!(email) | ||||||||||||||||||||||||||||||||
raise ArgumentError, 'email must be present' if email.nil? || email.empty? | ||||||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
def append_list_ids(email:, list_ids:, key:) | ||||||||||||||||||||||||||||||||
raise ArgumentError, 'list_ids must be present' if list_ids.empty? | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
@data[email][key] |= list_ids | ||||||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||||||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or it could be
Data.define
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We chose
Struct
for flexibility.Struct
does not require all fields to be present. For example here contact import API does not returncreated_contacts_count
when import is started. Another reasonData
class raises an error when more arguments are passed to it. So we could not add more fields to the api without breaking the ruby sdk.