unisender-ruby 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +97 -0
- data/CODE_OF_CONDUCT.md +90 -0
- data/CONTRIBUTING.md +23 -2
- data/README.md +53 -90
- data/SECURITY.md +13 -0
- data/docs/api.md +395 -210
- data/docs/execution.md +89 -0
- data/docs/import-recovery.md +41 -0
- data/docs/live-contracts.md +34 -0
- data/docs/migration-0.2.md +129 -0
- data/docs/rails.md +248 -0
- data/docs/releases.md +22 -0
- data/docs/testing.md +83 -0
- data/docs/usage.md +144 -0
- data/docs/webhooks.md +41 -0
- data/examples/campaign.rb +5 -6
- data/examples/campaign_lifecycle.rb +28 -0
- data/examples/check_connection.rb +1 -1
- data/examples/events.rb +1 -1
- data/examples/export.rb +20 -6
- data/examples/import.rb +3 -2
- data/examples/import_recovery.rb +70 -0
- data/examples/quickstart.rb +2 -1
- data/examples/subscribe.rb +2 -2
- data/lib/unisender/acceptance.rb +104 -0
- data/lib/unisender/attachment.rb +47 -0
- data/lib/unisender/batch_report.rb +115 -0
- data/lib/unisender/batch_summary.rb +87 -0
- data/lib/unisender/batches.rb +41 -12
- data/lib/unisender/campaign_workflow.rb +72 -0
- data/lib/unisender/client.rb +65 -33
- data/lib/unisender/client_configuration.rb +50 -0
- data/lib/unisender/client_execution.rb +43 -0
- data/lib/unisender/client_state.rb +31 -0
- data/lib/unisender/client_validation.rb +97 -0
- data/lib/unisender/configuration.rb +151 -0
- data/lib/unisender/contact_records.rb +251 -0
- data/lib/unisender/diagnostic_urls.rb +68 -0
- data/lib/unisender/diagnostics.rb +83 -0
- data/lib/unisender/download_target.rb +61 -0
- data/lib/unisender/download_transfer.rb +43 -0
- data/lib/unisender/downloader.rb +99 -0
- data/lib/unisender/error_diagnostics.rb +83 -0
- data/lib/unisender/errors.rb +125 -8
- data/lib/unisender/event_batches.rb +40 -0
- data/lib/unisender/event_payload.rb +50 -22
- data/lib/unisender/events.rb +21 -5
- data/lib/unisender/execution_context.rb +114 -0
- data/lib/unisender/form.rb +20 -12
- data/lib/unisender/helpers.rb +70 -10
- data/lib/unisender/hooks.rb +79 -0
- data/lib/unisender/import_cells.rb +62 -0
- data/lib/unisender/input_types.rb +30 -0
- data/lib/unisender/inputs.rb +262 -0
- data/lib/unisender/iteration.rb +15 -0
- data/lib/unisender/iterators.rb +60 -0
- data/lib/unisender/mail_inputs.rb +37 -0
- data/lib/unisender/named_inputs.rb +52 -0
- data/lib/unisender/observation.rb +30 -0
- data/lib/unisender/operation_contracts.rb +106 -0
- data/lib/unisender/operations/contacts_1.rb +4 -3
- data/lib/unisender/operations/contacts_2.rb +23 -4
- data/lib/unisender/operations/messages_1.rb +7 -4
- data/lib/unisender/operations/messages_2.rb +8 -3
- data/lib/unisender/operations/messages_3.rb +1 -1
- data/lib/unisender/operations/notes_1.rb +1 -1
- data/lib/unisender/operations/statistics_1.rb +3 -3
- data/lib/unisender/operations/templates_1.rb +2 -2
- data/lib/unisender/outcomes.rb +320 -0
- data/lib/unisender/page_response.rb +48 -0
- data/lib/unisender/paginated_operations.rb +43 -2
- data/lib/unisender/pagination.rb +53 -20
- data/lib/unisender/persistent_transport.rb +104 -0
- data/lib/unisender/rate_limiter.rb +72 -0
- data/lib/unisender/record.rb +210 -0
- data/lib/unisender/record_rows.rb +69 -0
- data/lib/unisender/records.rb +614 -0
- data/lib/unisender/request_execution.rb +108 -0
- data/lib/unisender/response.rb +13 -10
- data/lib/unisender/result_registry.rb +72 -0
- data/lib/unisender/results.rb +105 -0
- data/lib/unisender/retry_policy.rb +10 -16
- data/lib/unisender/serialization.rb +54 -0
- data/lib/unisender/statistic_records.rb +129 -0
- data/lib/unisender/task_response.rb +85 -0
- data/lib/unisender/task_waiter.rb +25 -11
- data/lib/unisender/testing/assertions.rb +48 -0
- data/lib/unisender/testing/fixtures.rb +24 -0
- data/lib/unisender/testing.rb +91 -0
- data/lib/unisender/transport.rb +17 -9
- data/lib/unisender/validation.rb +11 -9
- data/lib/unisender/version.rb +1 -1
- data/lib/unisender/webhooks/tokens.rb +38 -0
- data/lib/unisender/webhooks.rb +230 -0
- data/lib/unisender.rb +42 -0
- data/sig/acceptance.rbs +30 -0
- data/sig/attachment.rbs +15 -0
- data/sig/batch_summary.rbs +38 -0
- data/sig/campaign_workflow.rbs +20 -0
- data/sig/configuration.rbs +62 -0
- data/sig/connection_controls.rbs +55 -0
- data/sig/diagnostics.rbs +37 -0
- data/sig/domain_records.rbs +96 -0
- data/sig/downloads.rbs +53 -0
- data/sig/execution.rbs +52 -0
- data/sig/execution_context.rbs +46 -0
- data/sig/hooks.rbs +18 -0
- data/sig/inputs.rbs +84 -0
- data/sig/iterators.rbs +11 -0
- data/sig/mail_inputs.rbs +8 -0
- data/sig/manifest.yaml +11 -0
- data/sig/operation_contracts.rbs +28 -0
- data/sig/operations.rbs +73 -69
- data/sig/outcomes.rbs +90 -0
- data/sig/record.rbs +77 -0
- data/sig/records.rbs +182 -0
- data/sig/serialization.rbs +13 -0
- data/sig/tasks.rbs +26 -0
- data/sig/testing.rbs +54 -0
- data/sig/unisender.rbs +128 -39
- data/sig/webhooks.rbs +55 -0
- metadata +88 -4
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Unisender
|
|
4
|
+
# Named mail headers merge without silently replacing caller-supplied values.
|
|
5
|
+
module MailInputs
|
|
6
|
+
def self.normalize(params)
|
|
7
|
+
extra = headers(params)
|
|
8
|
+
result = params.except(:reply_to, :priority)
|
|
9
|
+
result[:headers] = merge(params[:headers], extra) unless extra.empty?
|
|
10
|
+
result
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.headers(params)
|
|
14
|
+
{ "Reply-To" => header(params[:reply_to]), "Priority" => header(params[:priority]) }.compact
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.header(value)
|
|
18
|
+
return nil if value.nil?
|
|
19
|
+
unless value.is_a?(String) && !value.match?(/[\r\n]/)
|
|
20
|
+
raise ValidationError.new(field: "headers", code: :invalid_header)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
value
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.merge(original, extra)
|
|
27
|
+
return extra if original.nil?
|
|
28
|
+
|
|
29
|
+
names = extra.keys.map(&:downcase)
|
|
30
|
+
unless original.is_a?(Hash) && original.keys.none? { |key| names.include?(key.to_s.downcase) }
|
|
31
|
+
raise ValidationError.new(field: "headers", code: :conflicting_input)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
original.merge(extra)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Unisender
|
|
4
|
+
# Closed choices are validated only for named operations.
|
|
5
|
+
module NamedInputs
|
|
6
|
+
# @type var empty: Hash[Symbol, Array[String]]
|
|
7
|
+
empty = {}
|
|
8
|
+
EMPTY_ENUMS = empty.freeze
|
|
9
|
+
FLAGS = {
|
|
10
|
+
"setHook" => %i[single_event],
|
|
11
|
+
"getContact" => %i[include_lists include_fields include_details],
|
|
12
|
+
"createCampaign" => %i[track_read track_links track_ga],
|
|
13
|
+
"createEmailMessage" => %i[generate_text], "sendEmail" => %i[track_read track_links error_checking],
|
|
14
|
+
"importContacts" => %i[overwrite_tags overwrite_lists], "getVisitedLinks" => %i[group],
|
|
15
|
+
"getSubscriberNotes" => %i[is_pinned]
|
|
16
|
+
}.transform_values(&:freeze).freeze
|
|
17
|
+
ENUMS = {
|
|
18
|
+
"sendEmail" => { images_as: %w[attachments only_links user_default].freeze }.freeze,
|
|
19
|
+
"setHook" => { event_format: %w[json_post json_post_gzip http_get], status: %w[active disabled] },
|
|
20
|
+
"getTemplates" => { type: %w[user system].freeze }.freeze,
|
|
21
|
+
"listTemplates" => { type: %w[user system].freeze }.freeze,
|
|
22
|
+
"exclude" => { contact_type: %w[email phone].freeze }.freeze,
|
|
23
|
+
"unsubscribe" => { contact_type: %w[email phone].freeze }.freeze,
|
|
24
|
+
"getSubscriberNotes" => { order_type: %w[ASC DESC].freeze,
|
|
25
|
+
order_by: %w[created_at updated_at pinned_at].freeze }.freeze
|
|
26
|
+
}.freeze
|
|
27
|
+
|
|
28
|
+
def self.normalize(method, params)
|
|
29
|
+
FLAGS.fetch(method, []).each { |key| flag!(key, params[key], method) }
|
|
30
|
+
ENUMS.fetch(method, EMPTY_ENUMS).each do |key, choices|
|
|
31
|
+
value = params[key]
|
|
32
|
+
params[key] = choice(value, key, choices, method) unless value.nil?
|
|
33
|
+
end
|
|
34
|
+
params
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.flag!(key, value, operation)
|
|
38
|
+
return if value.nil? || value == true || value == false
|
|
39
|
+
return if value.is_a?(Integer) && (0..1).cover?(value)
|
|
40
|
+
|
|
41
|
+
raise ValidationError.new(field: key.to_s, code: :invalid_flag, operation:)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.choice(value, key, choices, operation)
|
|
45
|
+
text = value.to_s
|
|
46
|
+
text = text.upcase if key == :order_type
|
|
47
|
+
return text if (value.is_a?(String) || value.is_a?(Symbol)) && choices.include?(text)
|
|
48
|
+
|
|
49
|
+
raise ValidationError.new(field: key.to_s, code: :invalid_choice, operation:)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Unisender
|
|
4
|
+
# Metadata deliberately excludes request bodies, headers, and response payloads.
|
|
5
|
+
RequestEvent = Data.define(:operation, :attempt, :duration, :http_status, :outcome, :error_category, :request_id)
|
|
6
|
+
|
|
7
|
+
# Callback failures cannot change the outcome of an API request.
|
|
8
|
+
module Observation
|
|
9
|
+
def self.emit(callback, event)
|
|
10
|
+
return unless callback
|
|
11
|
+
|
|
12
|
+
callback.call(event)
|
|
13
|
+
rescue StandardError
|
|
14
|
+
nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.event(operation, attempt, started, response, error)
|
|
18
|
+
RequestEvent.new(operation: Error.safe_operation(operation) || "request", attempt:,
|
|
19
|
+
duration: Process.clock_gettime(Process::CLOCK_MONOTONIC) - started,
|
|
20
|
+
http_status: response&.status, outcome: error ? :failure : :success,
|
|
21
|
+
error_category: category(error), request_id: response&.headers&.[]("x-request-id"))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.category(error)
|
|
25
|
+
return nil unless error
|
|
26
|
+
|
|
27
|
+
error.is_a?(Error) ? error.category : :unexpected
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Unisender
|
|
4
|
+
# Named input shapes are checked against the public RBS in the test suite.
|
|
5
|
+
module OperationContracts
|
|
6
|
+
# rubocop:disable Layout/LineLength -- Static input contracts mirror explicit public signatures.
|
|
7
|
+
# @type var definitions: Hash[String, [String, Array[String], Hash[String, Array[String]]]]
|
|
8
|
+
definitions = {
|
|
9
|
+
"createList" => ["create_list", ["title"], { "title" => ["string"], "before_subscribe_url" => ["string"], "after_subscribe_url" => ["string"], "sub_user_rights" => ["hash"] }],
|
|
10
|
+
"deleteList" => ["delete_list", ["list_id"], { "list_id" => %w[string integer] }],
|
|
11
|
+
"exclude" => ["exclude", [], { "contact_type" => %w[string symbol], "contact" => ["string"], "email" => ["string"], "phone" => ["string"], "list_ids" => ["string", "array:string", "array:integer"] }],
|
|
12
|
+
"async/exportContacts" => ["export_contacts", [], { "notify_url" => ["string"], "list_id" => %w[string integer], "field_names" => ["array:string"], "email" => ["string"], "phone" => ["string"], "tag" => ["string"], "email_status" => ["string"], "phone_status" => ["string"], "contact_status" => ["string"] }],
|
|
13
|
+
"getContactCount" => ["get_contact_count", ["list_id"], { "list_id" => %w[string integer], "params" => ["hash"], "tag_id" => %w[string integer], "contact_type" => %w[string symbol], "search" => ["string"] }],
|
|
14
|
+
"getLists" => ["get_lists", [], {}],
|
|
15
|
+
"getTotalContactsCount" => ["get_total_contacts_count", ["login"], { "login" => ["string"] }],
|
|
16
|
+
"importContacts" => ["import_contacts", [], { "field_names" => ["array:string"], "data" => ["array:array:parameter"], "records" => ["enumerable"], "overwrite_tags" => %w[boolean integer], "overwrite_lists" => %w[boolean integer], "telegram_bot_channel_id" => ["string"] }],
|
|
17
|
+
"subscribe" => ["subscribe", ["list_ids"], { "list_ids" => ["string", "array:string", "array:integer"], "fields" => ["hash"], "email" => ["string"], "tags" => ["string", "array:string", "array:integer"], "double_optin" => %w[integer symbol], "overwrite" => %w[integer symbol] }],
|
|
18
|
+
"updateList" => ["update_list", %w[list_id title], { "list_id" => %w[string integer], "title" => ["string"], "before_subscribe_url" => ["string"], "after_subscribe_url" => ["string"], "sub_user_rights" => ["hash"] }],
|
|
19
|
+
"isContactInLists" => ["is_contact_in_lists", %w[email list_ids condition], { "email" => ["string"], "list_ids" => ["string", "array:string", "array:integer"], "condition" => ["string"] }],
|
|
20
|
+
"getContact" => ["get_contact", ["email"], { "email" => ["string"], "include_lists" => %w[boolean integer], "include_fields" => %w[boolean integer], "include_details" => %w[boolean integer] }],
|
|
21
|
+
"unsubscribe" => ["unsubscribe", [], { "contact_type" => %w[string symbol], "contact" => ["string"], "email" => ["string"], "phone" => ["string"], "list_ids" => ["string", "array:string", "array:integer"] }],
|
|
22
|
+
"cancelCampaign" => ["cancel_campaign", ["campaign_id"], { "campaign_id" => %w[string integer] }],
|
|
23
|
+
"checkEmail" => ["check_email", ["email_id"], { "email_id" => ["string", "array:string", "array:integer"] }],
|
|
24
|
+
"checkSms" => ["check_sms", ["sms_id"], { "sms_id" => %w[string integer] }],
|
|
25
|
+
"createCampaign" => ["create_campaign", ["message_id"], { "message_id" => %w[string integer], "start_time" => %w[string time], "timezone" => ["string"], "track_read" => %w[boolean integer], "track_links" => %w[boolean integer], "contacts" => ["string", "array:string", "array:integer"], "contacts_url" => ["string"], "track_ga" => %w[boolean integer], "payment_limit" => %w[integer float], "payment_currency" => ["string"], "ga_medium" => ["string"], "ga_source" => ["string"], "ga_campaign" => ["string"], "ga_content" => ["string"], "ga_term" => ["string"] }],
|
|
26
|
+
"createEmailMessage" => ["create_email_message", %w[list_id sender_name sender_email], { "sender_name" => ["string"], "sender_email" => ["string"], "list_id" => %w[string integer], "subject" => ["string"], "body" => ["string"], "text_body" => ["string"], "generate_text" => %w[boolean integer], "tag" => ["string"], "attachments" => ["hash", "attachment", "array:attachment"], "lang" => ["string"], "template_id" => %w[string integer], "system_template_id" => %w[string integer], "wrap_type" => ["string"], "categories" => ["string", "array:string", "array:integer"] }],
|
|
27
|
+
"createSmsMessage" => ["create_sms_message", %w[sender body list_id], { "sender" => ["string"], "body" => ["string"], "list_id" => %w[string integer], "tag" => ["string"] }],
|
|
28
|
+
"deleteMessage" => ["delete_message", ["message_id"], { "message_id" => %w[string integer] }],
|
|
29
|
+
"getActualMessageVersion" => ["get_actual_message_version", ["message_id"], { "message_id" => %w[string integer] }],
|
|
30
|
+
"getWebVersion" => ["get_web_version", ["campaign_id"], { "campaign_id" => %w[string integer] }],
|
|
31
|
+
"sendEmail" => ["send_email", %w[email subject body list_id sender_name sender_email], { "email" => ["string"], "sender_name" => ["string"], "sender_email" => ["string"], "subject" => ["string"], "body" => ["string"], "list_id" => %w[string integer], "attachments" => ["hash", "attachment", "array:attachment"], "lang" => ["string"], "track_read" => %w[boolean integer], "track_links" => %w[boolean integer], "cc" => ["string"], "headers" => %w[string hash], "images_as" => %w[string symbol], "ref_key" => ["string"], "error_checking" => %w[boolean integer], "metadata" => ["hash"], "reply_to" => ["string"], "priority" => ["string"] }],
|
|
32
|
+
"sendSms" => ["send_sms", %w[phone sender text], { "phone" => ["string", "array:string"], "sender" => ["string"], "text" => ["string"] }],
|
|
33
|
+
"sendTestEmail" => ["send_test_email", %w[email id], { "email" => ["string", "array:string", "array:integer"], "id" => %w[string integer] }],
|
|
34
|
+
"updateEmailMessage" => ["update_email_message", ["id"], { "id" => %w[string integer], "sender_name" => ["string"], "sender_email" => ["string"], "subject" => ["string"], "body" => ["string"], "list_id" => %w[string integer], "text_body" => ["string"], "lang" => ["string"], "categories" => ["string", "array:string", "array:integer"] }],
|
|
35
|
+
"updateOptInEmail" => ["update_opt_in_email", %w[sender_name sender_email subject body list_id], { "sender_name" => ["string"], "sender_email" => ["string"], "subject" => ["string"], "body" => ["string"], "list_id" => %w[string integer] }],
|
|
36
|
+
"getSenderDomainList" => ["get_sender_domain_list", ["username"], { "username" => ["string"], "domain" => ["string"], "limit" => ["integer"], "offset" => ["integer"] }],
|
|
37
|
+
"getCampaignCommonStats" => ["get_campaign_common_stats", ["campaign_id"], { "campaign_id" => %w[string integer] }],
|
|
38
|
+
"async/getCampaignDeliveryStats" => ["get_campaign_delivery_stats", ["campaign_id"], { "campaign_id" => %w[string integer], "notify_url" => ["string"], "changed_since" => %w[string time], "field_ids" => ["array:string", "array:integer"] }],
|
|
39
|
+
"getCampaignStatus" => ["get_campaign_status", ["campaign_id"], { "campaign_id" => %w[string integer] }],
|
|
40
|
+
"getMessages" => ["get_messages", %w[date_from date_to], { "date_from" => %w[string time], "date_to" => %w[string time], "limit" => ["integer"], "offset" => ["integer"] }],
|
|
41
|
+
"getVisitedLinks" => ["get_visited_links", ["campaign_id"], { "campaign_id" => %w[string integer], "group" => %w[boolean integer] }],
|
|
42
|
+
"listMessages" => ["list_messages", %w[date_from date_to], { "date_from" => %w[string time], "date_to" => %w[string time], "limit" => ["integer"], "offset" => ["integer"] }],
|
|
43
|
+
"getCampaigns" => ["get_campaigns", ["from"], { "from" => %w[string time], "to" => %w[string time], "limit" => ["integer"], "offset" => ["integer"] }],
|
|
44
|
+
"getMessage" => ["get_message", ["id"], { "id" => %w[string integer] }],
|
|
45
|
+
"async/getTaskResult" => ["get_task_result", ["task_uuid"], { "task_uuid" => ["string"] }],
|
|
46
|
+
"createEmailTemplate" => ["create_email_template", %w[title subject body], { "title" => ["string"], "subject" => ["string"], "body" => ["string"], "description" => ["string"], "text_body" => ["string"], "lang" => ["string"] }],
|
|
47
|
+
"deleteTemplate" => ["delete_template", ["template_id"], { "template_id" => %w[string integer] }],
|
|
48
|
+
"getTemplate" => ["get_template", [], { "template_id" => %w[string integer], "system_template_id" => %w[string integer] }],
|
|
49
|
+
"getTemplates" => ["get_templates", [], { "type" => %w[string symbol], "date_from" => %w[string time], "date_to" => %w[string time], "limit" => ["integer"], "offset" => ["integer"] }],
|
|
50
|
+
"listTemplates" => ["list_templates", [], { "type" => %w[string symbol], "date_from" => %w[string time], "date_to" => %w[string time], "limit" => ["integer"], "offset" => ["integer"] }],
|
|
51
|
+
"updateEmailTemplate" => ["update_email_template", ["template_id"], { "template_id" => %w[string integer], "title" => ["string"], "description" => ["string"], "subject" => ["string"], "body" => ["string"], "text_body" => ["string"], "lang" => ["string"] }],
|
|
52
|
+
"createField" => ["create_field", %w[name type], { "name" => ["string"], "type" => ["string"], "public_name" => ["string"] }],
|
|
53
|
+
"deleteField" => ["delete_field", ["id"], { "id" => %w[string integer] }],
|
|
54
|
+
"deleteTag" => ["delete_tag", ["id"], { "id" => %w[string integer] }],
|
|
55
|
+
"getFields" => ["get_fields", [], {}],
|
|
56
|
+
"getTags" => ["get_tags", [], {}],
|
|
57
|
+
"updateField" => ["update_field", %w[id name], { "id" => %w[string integer], "name" => ["string"], "public_name" => ["string"] }],
|
|
58
|
+
"getContactFieldValues" => ["get_contact_field_values", %w[email field_ids], { "email" => ["string"], "field_ids" => ["string", "array:string", "array:integer"] }],
|
|
59
|
+
"createSubscriberNote" => ["create_subscriber_note", %w[subscriber_id content], { "subscriber_id" => %w[string integer], "content" => ["string"] }],
|
|
60
|
+
"updateSubscriberNote" => ["update_subscriber_note", %w[id content], { "id" => %w[string integer], "content" => ["string"] }],
|
|
61
|
+
"deleteSubscriberNote" => ["delete_subscriber_note", ["id"], { "id" => %w[string integer] }],
|
|
62
|
+
"getSubscriberNote" => ["get_subscriber_note", ["id"], { "id" => %w[string integer] }],
|
|
63
|
+
"getSubscriberNotes" => ["get_subscriber_notes", ["subscriber_id"], { "subscriber_id" => %w[string integer], "limit" => ["integer"], "offset" => ["integer"], "order_type" => %w[string symbol], "order_by" => %w[string symbol], "is_pinned" => %w[boolean integer] }],
|
|
64
|
+
"setHook" => ["set_hook", %w[hook_url events], { "hook_url" => ["string"], "events" => ["hash"], "event_format" => %w[symbol string], "max_parallel" => ["integer"], "single_event" => %w[boolean integer], "status" => %w[symbol string] }],
|
|
65
|
+
"listHooks" => ["list_hooks", [], {}],
|
|
66
|
+
"removeHook" => ["remove_hook", ["hook_url"], { "hook_url" => ["string"] }]
|
|
67
|
+
}
|
|
68
|
+
DEFINITIONS = definitions.transform_values(&:freeze).freeze
|
|
69
|
+
# rubocop:enable Layout/LineLength
|
|
70
|
+
|
|
71
|
+
def self.wire_name(name)
|
|
72
|
+
entry = DEFINITIONS.find { |_wire, definition| definition.first == name.to_s }
|
|
73
|
+
raise ArgumentError, "Unsupported named operation" unless entry
|
|
74
|
+
|
|
75
|
+
entry.first
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def self.validate!(wire, params)
|
|
79
|
+
_name, required, types = DEFINITIONS.fetch(wire)
|
|
80
|
+
validate_keys!(wire, params)
|
|
81
|
+
params.each do |key, value|
|
|
82
|
+
InputTypes.validate!(value, types.fetch(key.to_s), key.to_s) unless value.nil?
|
|
83
|
+
end
|
|
84
|
+
Validation.required!(params, required)
|
|
85
|
+
extra = { "getContactCount" => %w[params], "subscribe" => %w[fields], "importContacts" => %w[field_names data],
|
|
86
|
+
"exclude" => %w[contact_type contact], "unsubscribe" => %w[contact_type contact] }
|
|
87
|
+
Validation.required!(params, extra.fetch(wire, []))
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def self.validate_keys!(wire, params)
|
|
91
|
+
types = DEFINITIONS.fetch(wire).last
|
|
92
|
+
return if params.keys.all? { |key| key.is_a?(Symbol) && types.key?(key.to_s) }
|
|
93
|
+
|
|
94
|
+
raise ValidationError.new(field: "parameters", code: :unknown_keyword)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def self.comma_fields(wire)
|
|
98
|
+
{
|
|
99
|
+
"subscribe" => %w[list_ids tags], "exclude" => %w[list_ids], "unsubscribe" => %w[list_ids],
|
|
100
|
+
"checkEmail" => %w[email_id], "createCampaign" => %w[contacts],
|
|
101
|
+
"createEmailMessage" => %w[categories], "updateEmailMessage" => %w[categories],
|
|
102
|
+
"sendTestEmail" => %w[email], "getContactFieldValues" => %w[field_ids]
|
|
103
|
+
}.fetch(wire, [])
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -16,8 +16,8 @@ module Unisender
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
# https://www.unisender.com/ru/support/api/contacts/exclude/
|
|
19
|
-
def exclude(contact_type
|
|
20
|
-
params = { contact_type:, contact:, list_ids: }
|
|
19
|
+
def exclude(contact_type: nil, contact: nil, email: nil, phone: nil, list_ids: nil)
|
|
20
|
+
params = ContactInputs.contact_selector({ contact_type:, contact:, email:, phone:, list_ids: }, "exclude")
|
|
21
21
|
invoke("exclude", params, required: %w[contact_type contact], comma: %w[list_ids])
|
|
22
22
|
end
|
|
23
23
|
|
|
@@ -31,7 +31,8 @@ module Unisender
|
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
# https://www.unisender.com/ru/support/api/contacts/getcontactcount/
|
|
34
|
-
def get_contact_count(list_id:, params:)
|
|
34
|
+
def get_contact_count(list_id:, params: nil, tag_id: nil, contact_type: nil, search: nil)
|
|
35
|
+
params = ContactInputs.count_filters(params, tag_id, contact_type, search)
|
|
35
36
|
params = { list_id:, params: }
|
|
36
37
|
invoke("getContactCount", params, required: %w[list_id params])
|
|
37
38
|
end
|
|
@@ -4,14 +4,26 @@ module Unisender
|
|
|
4
4
|
# Contacts operations from the official UniSender API.
|
|
5
5
|
class Client
|
|
6
6
|
# https://www.unisender.com/ru/support/api/contacts/importcontacts/
|
|
7
|
-
|
|
7
|
+
# rubocop:disable-next Metrics/ParameterLists -- Accepts either records or the documented matrix form.
|
|
8
|
+
def import_contacts(field_names: nil, data: nil, records: nil, overwrite_tags: nil, overwrite_lists: nil,
|
|
8
9
|
telegram_bot_channel_id: nil)
|
|
10
|
+
if records
|
|
11
|
+
if field_names || data
|
|
12
|
+
raise ValidationError.new(field: "records", code: :conflicting_input, operation: "importContacts")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
rows = RecordRows.bounded(records)
|
|
16
|
+
data = rows.to_a
|
|
17
|
+
field_names = rows.field_names
|
|
18
|
+
end
|
|
9
19
|
params = { field_names:, data:, overwrite_tags:, overwrite_lists:, telegram_bot_channel_id: }
|
|
10
20
|
invoke("importContacts", params, required: %w[field_names data])
|
|
11
21
|
end
|
|
12
22
|
|
|
13
23
|
# https://www.unisender.com/ru/support/api/contacts/subscribe/
|
|
14
|
-
|
|
24
|
+
# rubocop:disable-next Metrics/ParameterLists -- Adds email to the documented subscription options.
|
|
25
|
+
def subscribe(list_ids:, fields: nil, email: nil, tags: nil, double_optin: nil, overwrite: nil)
|
|
26
|
+
fields = Inputs.contact_fields(fields, email)
|
|
15
27
|
params = { list_ids:, fields:, tags:, double_optin:, overwrite: }
|
|
16
28
|
invoke("subscribe", params, required: %w[list_ids fields], comma: %w[list_ids tags])
|
|
17
29
|
end
|
|
@@ -29,6 +41,13 @@ module Unisender
|
|
|
29
41
|
invoke("isContactInLists", params, required: %w[email list_ids condition], comma: %w[list_ids])
|
|
30
42
|
end
|
|
31
43
|
|
|
44
|
+
def contact_in_lists?(email:, list_ids:, condition:)
|
|
45
|
+
wire = { any: "or", all: "and" }.fetch(condition) do
|
|
46
|
+
raise ValidationError.new(field: "condition", code: :invalid_choice, operation: "isContactInLists")
|
|
47
|
+
end
|
|
48
|
+
is_contact_in_lists(email:, list_ids:, condition: wire).result
|
|
49
|
+
end
|
|
50
|
+
|
|
32
51
|
# https://www.unisender.com/ru/support/api/contacts/getcontact/
|
|
33
52
|
def get_contact(email:, include_lists: nil, include_fields: nil, include_details: nil)
|
|
34
53
|
params = { email:, include_lists:, include_fields:, include_details: }
|
|
@@ -36,8 +55,8 @@ module Unisender
|
|
|
36
55
|
end
|
|
37
56
|
|
|
38
57
|
# https://www.unisender.com/ru/support/api/contacts/unsubscribe/
|
|
39
|
-
def unsubscribe(contact_type
|
|
40
|
-
params = { contact_type:, contact:, list_ids: }
|
|
58
|
+
def unsubscribe(contact_type: nil, contact: nil, email: nil, phone: nil, list_ids: nil)
|
|
59
|
+
params = ContactInputs.contact_selector({ contact_type:, contact:, email:, phone:, list_ids: }, "unsubscribe")
|
|
41
60
|
invoke("unsubscribe", params, required: %w[contact_type contact], comma: %w[list_ids])
|
|
42
61
|
end
|
|
43
62
|
end
|
|
@@ -29,18 +29,21 @@ module Unisender
|
|
|
29
29
|
params = { message_id:, start_time:, timezone:, track_read:, track_links:, contacts:, contacts_url:,
|
|
30
30
|
track_ga:, payment_limit:, payment_currency:, ga_medium:, ga_source:, ga_campaign:,
|
|
31
31
|
ga_content:, ga_term: }
|
|
32
|
-
invoke("createCampaign", params, required:
|
|
32
|
+
invoke("createCampaign", params, required: CAMPAIGN_REQUIRED, comma: %w[contacts])
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
# https://www.unisender.com/ru/support/api/messages/createemailmessage/
|
|
36
|
+
# rubocop:disable Layout/LineLength -- The static annotation mirrors the complete keyword signature.
|
|
37
|
+
# @type method create_email_message: (?sender_name: (String | Symbol)?, ?sender_email: (String | Symbol)?, list_id: id, ?subject: String | nil, ?body: String | nil, ?text_body: String | nil, ?generate_text: flag | nil, ?tag: String | nil, ?attachments: Hash[String, String] | Attachment | Array[Attachment] | nil, ?lang: String | Symbol | nil, ?template_id: id | nil, ?system_template_id: id | nil, ?wrap_type: String | nil, ?categories: String | Array[id] | nil) -> MessageCreatedResponse
|
|
38
|
+
# rubocop:enable Layout/LineLength
|
|
36
39
|
# rubocop:disable-next Metrics/ParameterLists -- Mirrors the documented operation.
|
|
37
|
-
def create_email_message(
|
|
38
|
-
generate_text: nil, tag: nil, attachments: nil, lang:
|
|
40
|
+
def create_email_message(list_id:, sender_name: UNSET, sender_email: UNSET, subject: nil, body: nil, text_body: nil,
|
|
41
|
+
generate_text: nil, tag: nil, attachments: nil, lang: UNSET, template_id: nil,
|
|
39
42
|
system_template_id: nil,
|
|
40
43
|
wrap_type: nil, categories: nil)
|
|
41
44
|
params = { sender_name:, sender_email:, list_id:, subject:, body:, text_body:, generate_text:, tag:,
|
|
42
45
|
attachments:, lang:, template_id:, system_template_id:, wrap_type:, categories: }
|
|
43
|
-
invoke("createEmailMessage", params, required:
|
|
46
|
+
invoke("createEmailMessage", params, required: EMAIL_REQUIRED, comma: %w[categories])
|
|
44
47
|
end
|
|
45
48
|
|
|
46
49
|
# https://www.unisender.com/ru/support/api/messages/createsmsmessage/
|
|
@@ -16,12 +16,17 @@ module Unisender
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
# https://www.unisender.com/ru/support/api/messages/sendemail/
|
|
19
|
+
# rubocop:disable Layout/LineLength -- The static annotation mirrors the complete keyword signature.
|
|
20
|
+
# @type method send_email: (email: String, ?sender_name: (String | Symbol)?, ?sender_email: (String | Symbol)?, subject: String, body: String, list_id: id, ?attachments: Hash[String, String] | Attachment | Array[Attachment] | nil, ?lang: String | Symbol | nil, ?track_read: flag | nil, ?track_links: flag | nil, ?cc: String | nil, ?headers: String | params | nil, ?images_as: String | Symbol | nil, ?ref_key: String | nil, ?error_checking: flag | nil, ?metadata: params | nil, ?reply_to: String | nil, ?priority: String | nil) -> SendResponse
|
|
21
|
+
# rubocop:enable Layout/LineLength
|
|
19
22
|
# rubocop:disable-next Metrics/ParameterLists -- Mirrors the documented operation.
|
|
20
|
-
def send_email(email:,
|
|
23
|
+
def send_email(email:, subject:, body:, list_id:, sender_name: UNSET, sender_email: UNSET,
|
|
24
|
+
attachments: nil, lang: UNSET,
|
|
21
25
|
track_read: nil, track_links: nil, cc: nil, headers: nil, images_as: nil, ref_key: nil,
|
|
22
|
-
error_checking:
|
|
26
|
+
error_checking: true, metadata: nil, reply_to: nil, priority: nil)
|
|
23
27
|
params = { email:, sender_name:, sender_email:, subject:, body:, list_id:, attachments:, lang:,
|
|
24
|
-
track_read:, track_links:, cc:, headers:, images_as:, ref_key:, error_checking:, metadata
|
|
28
|
+
track_read:, track_links:, cc:, headers:, images_as:, ref_key:, error_checking:, metadata:,
|
|
29
|
+
reply_to:, priority: }
|
|
25
30
|
invoke("sendEmail", params, required: %w[email sender_name sender_email subject body list_id])
|
|
26
31
|
end
|
|
27
32
|
|
|
@@ -6,7 +6,7 @@ module Unisender
|
|
|
6
6
|
# https://www.unisender.com/ru/support/api/messages/getsenderdomainlist/
|
|
7
7
|
def get_sender_domain_list(username:, domain: nil, limit: nil, offset: nil)
|
|
8
8
|
params = { username:, domain:, limit:, offset: }
|
|
9
|
-
PaginatedOperations.call(self, :get_sender_domain_list, params)
|
|
9
|
+
PaginatedOperations.call(self, :get_sender_domain_list, params) # : PageResponse[SenderDomainGroup]
|
|
10
10
|
end
|
|
11
11
|
end
|
|
12
12
|
end
|
|
@@ -32,7 +32,7 @@ module Unisender
|
|
|
32
32
|
def get_subscriber_notes(subscriber_id:, limit: nil, offset: nil, order_type: nil, order_by: nil,
|
|
33
33
|
is_pinned: nil)
|
|
34
34
|
params = { subscriber_id:, limit:, offset:, order_type:, order_by:, is_pinned: }
|
|
35
|
-
PaginatedOperations.call(self, :get_subscriber_notes, params)
|
|
35
|
+
PaginatedOperations.call(self, :get_subscriber_notes, params) # : PageResponse[Note]
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
38
|
end
|
|
@@ -24,7 +24,7 @@ module Unisender
|
|
|
24
24
|
# https://www.unisender.com/ru/support/api/statistics/getmessages/
|
|
25
25
|
def get_messages(date_from:, date_to:, limit: nil, offset: nil)
|
|
26
26
|
params = { date_from:, date_to:, limit:, offset: }
|
|
27
|
-
PaginatedOperations.call(self, :get_messages, params)
|
|
27
|
+
PaginatedOperations.call(self, :get_messages, params) # : PageResponse[Message]
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
# https://www.unisender.com/ru/support/api/statistics/getvisitedlinks/
|
|
@@ -36,13 +36,13 @@ module Unisender
|
|
|
36
36
|
# https://www.unisender.com/ru/support/api/statistics/listmessages/
|
|
37
37
|
def list_messages(date_from:, date_to:, limit: nil, offset: nil)
|
|
38
38
|
params = { date_from:, date_to:, limit:, offset: }
|
|
39
|
-
PaginatedOperations.call(self, :list_messages, params)
|
|
39
|
+
PaginatedOperations.call(self, :list_messages, params) # : PageResponse[Message]
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
# https://www.unisender.com/ru/support/api/statistics/getcampaigns/
|
|
43
43
|
def get_campaigns(from:, to: nil, limit: nil, offset: nil)
|
|
44
44
|
params = { from:, to:, limit:, offset: }
|
|
45
|
-
PaginatedOperations.call(self, :get_campaigns, params)
|
|
45
|
+
PaginatedOperations.call(self, :get_campaigns, params) # : PageResponse[Campaign]
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
end
|
|
@@ -25,13 +25,13 @@ module Unisender
|
|
|
25
25
|
# https://www.unisender.com/ru/support/api/templates/gettemplates/
|
|
26
26
|
def get_templates(type: nil, date_from: nil, date_to: nil, limit: nil, offset: nil)
|
|
27
27
|
params = { type:, date_from:, date_to:, limit:, offset: }
|
|
28
|
-
PaginatedOperations.call(self, :get_templates, params)
|
|
28
|
+
PaginatedOperations.call(self, :get_templates, params) # : PageResponse[Template]
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
# https://www.unisender.com/ru/support/api/templates/listtemplates/
|
|
32
32
|
def list_templates(type: nil, date_from: nil, date_to: nil, limit: nil, offset: nil)
|
|
33
33
|
params = { type:, date_from:, date_to:, limit:, offset: }
|
|
34
|
-
PaginatedOperations.call(self, :list_templates, params)
|
|
34
|
+
PaginatedOperations.call(self, :list_templates, params) # : PageResponse[Template]
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
# https://www.unisender.com/ru/support/api/templates/updateemailtemplate/
|