openemail 0.0.1

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.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +183 -0
  3. data/lib/openemail/api_paths.rb +249 -0
  4. data/lib/openemail/client.rb +69 -0
  5. data/lib/openemail/constants.rb +851 -0
  6. data/lib/openemail/credentials.rb +53 -0
  7. data/lib/openemail/decode.rb +34 -0
  8. data/lib/openemail/defaults.rb +111 -0
  9. data/lib/openemail/endpoint.rb +144 -0
  10. data/lib/openemail/errors.rb +121 -0
  11. data/lib/openemail/http.rb +172 -0
  12. data/lib/openemail/languages.rb +42 -0
  13. data/lib/openemail/pagination.rb +63 -0
  14. data/lib/openemail/protocol.rb +169 -0
  15. data/lib/openemail/raw_body.rb +83 -0
  16. data/lib/openemail/request_path.rb +50 -0
  17. data/lib/openemail/resource.rb +70 -0
  18. data/lib/openemail/resources/account.rb +64 -0
  19. data/lib/openemail/resources/addresses.rb +38 -0
  20. data/lib/openemail/resources/analytics.rb +17 -0
  21. data/lib/openemail/resources/app_host.rb +23 -0
  22. data/lib/openemail/resources/audiences.rb +90 -0
  23. data/lib/openemail/resources/billing.rb +64 -0
  24. data/lib/openemail/resources/branding.rb +24 -0
  25. data/lib/openemail/resources/broadcasts.rb +65 -0
  26. data/lib/openemail/resources/calendar.rb +59 -0
  27. data/lib/openemail/resources/chats.rb +31 -0
  28. data/lib/openemail/resources/contacts.rb +111 -0
  29. data/lib/openemail/resources/dns_connections.rb +19 -0
  30. data/lib/openemail/resources/domains.rb +153 -0
  31. data/lib/openemail/resources/drafts.rb +35 -0
  32. data/lib/openemail/resources/emails.rb +100 -0
  33. data/lib/openemail/resources/encryption.rb +19 -0
  34. data/lib/openemail/resources/exports.rb +27 -0
  35. data/lib/openemail/resources/files.rb +95 -0
  36. data/lib/openemail/resources/forms.rb +111 -0
  37. data/lib/openemail/resources/imports.rb +86 -0
  38. data/lib/openemail/resources/keys.rb +127 -0
  39. data/lib/openemail/resources/labels.rb +39 -0
  40. data/lib/openemail/resources/languages.rb +11 -0
  41. data/lib/openemail/resources/me.rb +19 -0
  42. data/lib/openemail/resources/members.rb +74 -0
  43. data/lib/openemail/resources/provider_imports.rb +40 -0
  44. data/lib/openemail/resources/roles.rb +39 -0
  45. data/lib/openemail/resources/rules.rb +57 -0
  46. data/lib/openemail/resources/security.rb +19 -0
  47. data/lib/openemail/resources/senders.rb +15 -0
  48. data/lib/openemail/resources/settings.rb +16 -0
  49. data/lib/openemail/resources/subscriptions.rb +30 -0
  50. data/lib/openemail/resources/support.rb +11 -0
  51. data/lib/openemail/resources/suppressions.rb +31 -0
  52. data/lib/openemail/resources/temp_mail.rb +67 -0
  53. data/lib/openemail/resources/templates.rb +151 -0
  54. data/lib/openemail/resources/threads.rb +104 -0
  55. data/lib/openemail/resources/tools.rb +19 -0
  56. data/lib/openemail/resources/tracking.rb +63 -0
  57. data/lib/openemail/resources/webhooks.rb +142 -0
  58. data/lib/openemail/resources/workspaces.rb +28 -0
  59. data/lib/openemail/results.rb +33 -0
  60. data/lib/openemail/transport.rb +272 -0
  61. data/lib/openemail/update_notice.rb +69 -0
  62. data/lib/openemail/version.rb +5 -0
  63. data/lib/openemail/webhook.rb +82 -0
  64. data/lib/openemail/wire.rb +95 -0
  65. data/lib/openemail.rb +125 -0
  66. metadata +109 -0
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenEmail
4
+ module Pagination
5
+ CURSOR = Protocol::CURSOR_STYLES[:CURSOR]
6
+ PAGE_TOKEN = Protocol::CURSOR_STYLES[:PAGE_TOKEN]
7
+
8
+ module_function
9
+
10
+ def fetch_page(transport, path, query: nil, style: CURSOR, limit: nil, cursor: nil, api_key: nil)
11
+ payload = transport.request(path, query: page_query(query, style, limit, cursor), api_key:)
12
+ envelope = payload.is_a?(Hash) ? payload : {}
13
+ next_cursor = cursor_of(envelope[style[:FIELD].to_sym])
14
+
15
+ Page.new(items: items_of(envelope), has_more: more?(envelope, next_cursor), next_cursor:)
16
+ end
17
+
18
+ def iterate(transport, path, query: nil, style: CURSOR, limit: nil, cursor: nil, api_key: nil)
19
+ walk(cursor) { |current| fetch_page(transport, path, query:, style:, limit:, cursor: current, api_key:) }
20
+ end
21
+
22
+ def collect_all(transport, path, **options)
23
+ iterate(transport, path, **options).to_a
24
+ end
25
+
26
+ def fetch_list(transport, path, query: nil, api_key: nil)
27
+ payload = transport.request(path, query:, api_key:)
28
+ items_of(payload.is_a?(Hash) ? payload : {})
29
+ end
30
+
31
+ def walk(cursor, &fetch)
32
+ Enumerator.new do |yielder|
33
+ current = cursor
34
+
35
+ loop do
36
+ page = fetch.call(current)
37
+ page.items.each { |item| yielder << item }
38
+
39
+ break if !page.has_more || page.next_cursor.nil? || page.next_cursor == current
40
+
41
+ current = page.next_cursor
42
+ end
43
+ end
44
+ end
45
+
46
+ def page_query(query, style, limit, cursor)
47
+ (query || {}).merge(Protocol::QUERY_KEYS[:LIMIT] => limit, style[:PARAM] => cursor)
48
+ end
49
+
50
+ def items_of(envelope)
51
+ envelope[:data].is_a?(Array) ? envelope[:data] : []
52
+ end
53
+
54
+ def more?(envelope, next_cursor)
55
+ flag = envelope[:hasMore]
56
+ (flag == true || flag == false) ? flag : !next_cursor.nil?
57
+ end
58
+
59
+ def cursor_of(value)
60
+ (value.is_a?(String) && !value.empty?) ? value : nil
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,169 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenEmail
4
+ module Protocol
5
+ ACCESS_TOKEN_RULES = {RESERVED_PREFIX: "oe_", MAX_LENGTH: 512}.freeze
6
+
7
+ API_KEY_MODES = {LIVE: "live", TEST: "test"}.freeze
8
+
9
+ API_KEY_PREFIXES = {LIVE: "oe_live_", TEST: "oe_test_"}.freeze
10
+
11
+ CONTENT_TYPES = {
12
+ JSON: "application/json",
13
+ CALENDAR: "text/calendar",
14
+ OCTET_STREAM: "application/octet-stream"
15
+ }.freeze
16
+
17
+ CURSOR_STYLES = {
18
+ CURSOR: {PARAM: "cursor", FIELD: "nextCursor"}.freeze,
19
+ PAGE_TOKEN: {PARAM: "pageToken", FIELD: "nextPageToken"}.freeze
20
+ }.freeze
21
+
22
+ ENV_VARS = {
23
+ API_KEY: "OPENEMAIL_API_KEY",
24
+ ACCESS_TOKEN: "OPENEMAIL_ACCESS_TOKEN",
25
+ BASE_URL: "OPENEMAIL_BASE_URL"
26
+ }.freeze
27
+
28
+ ERROR_CODES = {
29
+ INSUFFICIENT_SCOPE: "insufficient_scope",
30
+ UNRECOGNISED_RESPONSE: "unrecognised_response"
31
+ }.freeze
32
+
33
+ ERROR_TYPE_BY_STATUS = {
34
+ 401 => "authentication_error",
35
+ 403 => "permission_error",
36
+ 404 => "not_found_error",
37
+ 409 => "conflict_error",
38
+ 422 => "validation_error",
39
+ 429 => "rate_limit_error"
40
+ }.freeze
41
+
42
+ HEADER_KEYS = {
43
+ AUTHORIZATION: "Authorization",
44
+ ACCEPT: "Accept",
45
+ CONTENT_TYPE: "Content-Type",
46
+ USER_AGENT: "User-Agent",
47
+ IDEMPOTENCY_KEY: "Idempotency-Key",
48
+ RETRY_AFTER: "retry-after",
49
+ REQUEST_ID: "x-request-id"
50
+ }.freeze
51
+
52
+ HTTP_METHODS = {GET: "GET", POST: "POST", PUT: "PUT", PATCH: "PATCH", DELETE: "DELETE"}.freeze
53
+
54
+ IDEMPOTENCY_KEY_PREFIX = "oe-"
55
+
56
+ LANGUAGE_ALIASES = [
57
+ ["zh", "zh-Hans"].freeze,
58
+ ["zh-cn", "zh-Hans"].freeze,
59
+ ["zh-sg", "zh-Hans"].freeze,
60
+ ["cmn", "zh-Hans"].freeze,
61
+ ["zh-tw", "zh-Hant"].freeze,
62
+ ["zh-mo", "zh-HK"].freeze,
63
+ ["iw", "he"].freeze,
64
+ ["in", "id"].freeze,
65
+ ["ji", "yi"].freeze,
66
+ ["mo", "ro"].freeze,
67
+ ["sh", "sr-Latn"].freeze,
68
+ ["sr-latn-rs", "sr-Latn"].freeze,
69
+ ["prs", "fa-AF"].freeze,
70
+ ["fa-af", "fa-AF"].freeze,
71
+ ["kmr", "ku"].freeze,
72
+ ["pt-pt", "pt"].freeze,
73
+ ["es-mx", "es-419"].freeze,
74
+ ["es-ar", "es-419"].freeze,
75
+ ["es-co", "es-419"].freeze,
76
+ ["es-cl", "es-419"].freeze
77
+ ].freeze
78
+
79
+ LIST_SEPARATOR = ","
80
+
81
+ LOCAL_HOSTS = ["localhost", "::1", "[::1]", "127.0.0.1", "0.0.0.0", "::", "[::]"].freeze
82
+
83
+ LOOPBACK_HOSTNAMES = ["localhost", "::1", "[::1]"].freeze
84
+
85
+ LOOPBACK_IPV4_ADDRESS = "127.0.0.1"
86
+
87
+ LOOPBACK_IPV6_ADDRESS = "[::1]"
88
+
89
+ QUERY_KEYS = {
90
+ LIMIT: "limit",
91
+ CURSOR: "cursor",
92
+ STATUS: "status",
93
+ FROM: "from",
94
+ TO: "to",
95
+ TIMEZONE: "timezone",
96
+ ENABLED: "enabled",
97
+ RULE_ID: "ruleId",
98
+ THREAD_ID: "threadId",
99
+ FOLDER: "folder",
100
+ QUERY: "query",
101
+ LABEL_IDS: "labelIds",
102
+ OPENED: "opened",
103
+ CLICKED: "clicked",
104
+ DAYS: "days",
105
+ MINUTES: "minutes",
106
+ GRAIN: "grain",
107
+ OFFSET_MINUTES: "offsetMinutes",
108
+ INCLUDE_MACHINE: "includeMachine",
109
+ REASSIGN_TO: "reassignTo",
110
+ SOURCE: "source",
111
+ ADDRESS: "address",
112
+ DIRECTION: "direction",
113
+ FILTER: "filter",
114
+ SEARCH: "search",
115
+ SORT: "sort",
116
+ PAGE: "page",
117
+ PAGE_SIZE: "pageSize",
118
+ VERSION: "version",
119
+ TRACKED: "tracked",
120
+ Q: "q",
121
+ AUDIENCE_IDS: "audienceIds",
122
+ AUDIENCE_ID: "audienceId",
123
+ BROADCAST_ID: "broadcastId",
124
+ BROADCAST_IDS: "broadcastIds",
125
+ EMAIL: "email",
126
+ BLOCKED: "blocked",
127
+ DATE_FROM: "dateFrom",
128
+ DATE_TO: "dateTo",
129
+ FROM_CONTACTS: "fromContacts",
130
+ REASON: "reason",
131
+ KIND: "kind",
132
+ ENDPOINT_IDS: "endpointIds",
133
+ KEY_IDS: "keyIds",
134
+ SINCE: "since",
135
+ UNTIL: "until",
136
+ FAILED_ONLY: "failedOnly",
137
+ FILENAME: "filename",
138
+ DOMAIN: "domain",
139
+ SCHEDULED_FROM: "scheduledFrom",
140
+ SCHEDULED_TO: "scheduledTo",
141
+ ADDRESSES: "addresses",
142
+ OFFSET: "offset",
143
+ REFRESH: "refresh",
144
+ CONFIRM: "confirm",
145
+ USERNAME: "username"
146
+ }.freeze
147
+
148
+ RETRY = {
149
+ STATUSES: [408, 429, 500, 502, 503, 504].freeze,
150
+ METHODS: ["GET"].freeze,
151
+ BASE_BACKOFF_MS: 500,
152
+ MAX_BACKOFF_MS: 8000,
153
+ MAX_HONOURED_RETRY_AFTER_MS: 60000,
154
+ RATE_LIMIT_STATUS: 429
155
+ }.freeze
156
+
157
+ SEALED_ENCRYPTION_FORMATS = ["pgp-mime", "pgp-inline", "smime-encrypted"].freeze
158
+
159
+ UNSPECIFIED_HOSTNAMES = ["0.0.0.0", "::", "[::]"].freeze
160
+
161
+ WEBHOOK_SIGNATURE = {
162
+ VERSION: "v1",
163
+ TIMESTAMP: "t",
164
+ PAIR_SEPARATOR: ",",
165
+ VALUE_SEPARATOR: "=",
166
+ TOLERANCE_SECONDS: 300
167
+ }.freeze
168
+ end
169
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenEmail
4
+ module RawBody
5
+ module_function
6
+
7
+ def read(data)
8
+ case data
9
+ when String then data
10
+ when Pathname then File.binread(data)
11
+ else read_stream(data)
12
+ end
13
+ end
14
+
15
+ def size(data)
16
+ case data
17
+ when String then data.bytesize
18
+ when Pathname then File.size(data)
19
+ else stream_size(data)
20
+ end
21
+ end
22
+
23
+ def slice(data, offset, length)
24
+ case data
25
+ when String then data.byteslice(offset, length)
26
+ when Pathname then File.open(data, "rb") { |file| stream_slice(file, offset, length) }
27
+ else stream_slice(data, offset, length)
28
+ end
29
+ end
30
+
31
+ def content_type(data, explicit)
32
+ return explicit unless explicit.nil? || explicit.to_s.empty?
33
+
34
+ declared = data.respond_to?(:content_type) ? data.content_type : nil
35
+ return declared if declared.is_a?(String) && !declared.empty?
36
+
37
+ name = file_name(data)
38
+ Defaults::CONTENT_TYPES_BY_EXTENSION[File.extname(name).downcase] if name
39
+ end
40
+
41
+ def file_name(data)
42
+ return data.original_filename if data.respond_to?(:original_filename) && data.original_filename
43
+ return File.basename(data) if data.is_a?(Pathname)
44
+
45
+ File.basename(data.path) if data.respond_to?(:path) && data.path
46
+ end
47
+
48
+ def read_stream(data)
49
+ raise ArgumentError, Defaults::MESSAGES[:RAW_BODY_SHAPE] unless data.respond_to?(:read)
50
+
51
+ rewind(data)
52
+ data.read.to_s.b
53
+ end
54
+
55
+ def stream_size(data)
56
+ raise ArgumentError, Defaults::MESSAGES[:RAW_BODY_SHAPE] unless data.respond_to?(:read)
57
+ return data.size if data.respond_to?(:size)
58
+
59
+ data.stat.size
60
+ end
61
+
62
+ def stream_slice(data, offset, length)
63
+ raise ArgumentError, Defaults::MESSAGES[:RAW_BODY_SHAPE] unless data.respond_to?(:read)
64
+
65
+ position(data, offset)
66
+ data.read(length).to_s.b
67
+ end
68
+
69
+ def position(data, offset)
70
+ return data.seek(offset) if data.respond_to?(:seek)
71
+
72
+ rewind(data) if offset.zero?
73
+ rescue Errno::ESPIPE
74
+ nil
75
+ end
76
+
77
+ def rewind(data)
78
+ data.rewind if data.respond_to?(:rewind)
79
+ rescue Errno::ESPIPE
80
+ nil
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenEmail
4
+ module RequestPath
5
+ module_function
6
+
7
+ def fill(pattern, params)
8
+ values = params.transform_keys(&:to_s)
9
+
10
+ pattern.scan(Defaults::PATH_PARAM) { |(name)| assert_segment(values[name]) }
11
+
12
+ pattern
13
+ .gsub(Defaults::OPTIONAL_PATH_PARAM) { optional_segment(values[Regexp.last_match(1)]) }
14
+ .gsub(Defaults::PATH_PARAM) { encode(values[Regexp.last_match(1)]) }
15
+ end
16
+
17
+ def assert_segment(value)
18
+ text = segment_text(value)
19
+
20
+ raise ArgumentError, Defaults::MESSAGES[:EMPTY_SEGMENT] if text.nil? || text.empty?
21
+ raise ArgumentError, "#{text.inspect} #{Defaults::MESSAGES[:DOT_SEGMENT]}" if Defaults::DOT_SEGMENT.match?(text)
22
+ end
23
+
24
+ def segment_text(value)
25
+ case value
26
+ when String, Symbol then utf8(value.to_s)
27
+ when Numeric then Wire.number(value).to_s
28
+ end
29
+ end
30
+
31
+ def utf8(text)
32
+ converted = (text.encoding == Encoding::BINARY) ? text.dup.force_encoding(Encoding::UTF_8) : text.encode(Encoding::UTF_8)
33
+ raise ArgumentError, Defaults::MESSAGES[:SEGMENT_ENCODING] unless converted.valid_encoding?
34
+
35
+ converted
36
+ rescue EncodingError
37
+ raise ArgumentError, Defaults::MESSAGES[:SEGMENT_ENCODING]
38
+ end
39
+
40
+ def optional_segment(value)
41
+ (value.nil? || value == "") ? "" : "/#{encode(value)}"
42
+ end
43
+
44
+ def encode(value)
45
+ segment_text(value).to_s.gsub(Defaults::URI_COMPONENT_UNSAFE) do |character|
46
+ character.bytes.map { |byte| format("%%%02X", byte) }.join
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenEmail
4
+ class Resource
5
+ CURSOR = Pagination::CURSOR
6
+ PAGE_TOKEN = Pagination::PAGE_TOKEN
7
+
8
+ def initialize(transport)
9
+ @transport = transport
10
+ end
11
+
12
+ def inspect
13
+ "#<#{self.class.name}>"
14
+ end
15
+
16
+ private
17
+
18
+ def request(path, **options)
19
+ @transport.request(path, **options)
20
+ end
21
+
22
+ def fill(pattern, **params)
23
+ RequestPath.fill(pattern, params)
24
+ end
25
+
26
+ def payload(body, fields)
27
+ Wire.body(body, fields)
28
+ end
29
+
30
+ def query_params(**values)
31
+ values.to_h { |name, value| [Protocol::QUERY_KEYS.fetch(name.to_s.upcase.to_sym), value] }
32
+ end
33
+
34
+ def joined(values)
35
+ Wire.collection?(values) ? Wire.list(values).join(Protocol::LIST_SEPARATOR) : values
36
+ end
37
+
38
+ def flag(value)
39
+ value&.to_s
40
+ end
41
+
42
+ def instant(value)
43
+ Wire.instant(value)
44
+ end
45
+
46
+ def fetch_page(path, query = nil, style: CURSOR, limit: nil, cursor: nil, api_key: nil)
47
+ Pagination.fetch_page(@transport, path, query:, style:, limit:, cursor:, api_key:)
48
+ end
49
+
50
+ def iterate_pages(path, query = nil, style: CURSOR, limit: nil, cursor: nil, api_key: nil, &)
51
+ each_or_enumerator(Pagination.iterate(@transport, path, query:, style:, limit:, cursor:, api_key:), &)
52
+ end
53
+
54
+ def each_or_enumerator(enumerator, &block)
55
+ block ? enumerator.each(&block) : enumerator
56
+ end
57
+
58
+ def collect_all(path, query = nil, style: CURSOR, limit: nil, cursor: nil, api_key: nil)
59
+ iterate_pages(path, query, style:, limit:, cursor:, api_key:).to_a
60
+ end
61
+
62
+ def fetch_list(path, query = nil, api_key: nil)
63
+ Pagination.fetch_list(@transport, path, query:, api_key:)
64
+ end
65
+
66
+ def raw_content_type(data, content_type)
67
+ RawBody.content_type(data, content_type)
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenEmail
4
+ module Resources
5
+ class Account < Resource
6
+ def get_notifications(api_key: nil)
7
+ request(ApiPaths::ACCOUNT_NOTIFICATIONS, api_key:)
8
+ end
9
+
10
+ def set_email_notification(category, body = nil, api_key: nil, **fields)
11
+ request(fill(ApiPaths::ACCOUNT_NOTIFICATION_EMAIL, category:), method: :put,
12
+ body: payload(body, fields).slice("enabled"), repeatable: true, api_key:)
13
+ end
14
+
15
+ def set_push_muted(workspace_id, body = nil, api_key: nil, **fields)
16
+ request(fill(ApiPaths::ACCOUNT_NOTIFICATION_PUSH, workspaceId: workspace_id), method: :put,
17
+ body: payload(body, fields).slice("muted"), repeatable: true, api_key:)
18
+ end
19
+
20
+ def set_photo(data, content_type: nil, api_key: nil)
21
+ request(ApiPaths::ACCOUNT_PHOTO, method: :put, raw: data, content_type: raw_content_type(data, content_type),
22
+ repeatable: true, api_key:)
23
+ end
24
+
25
+ def remove_photo(api_key: nil)
26
+ request(ApiPaths::ACCOUNT_PHOTO, method: :delete, repeatable: true, api_key:)
27
+ end
28
+
29
+ def get_username(api_key: nil)
30
+ request(ApiPaths::ACCOUNT_USERNAME, api_key:)
31
+ end
32
+
33
+ def check_username(username, api_key: nil)
34
+ request(ApiPaths::ACCOUNT_USERNAME_AVAILABILITY, query: query_params(username:), api_key:)
35
+ end
36
+
37
+ def set_username(username, api_key: nil)
38
+ request(ApiPaths::ACCOUNT_USERNAME, method: :put, body: {"username" => username}, api_key:)
39
+ end
40
+
41
+ def list_connected_apps(api_key: nil)
42
+ fetch_list(ApiPaths::ACCOUNT_CONNECTED_APPS, api_key:)
43
+ end
44
+
45
+ def revoke_connected_app(client_id, api_key: nil)
46
+ request(fill(ApiPaths::ACCOUNT_CONNECTED_APP, clientId: client_id), method: :delete, api_key:)
47
+ end
48
+
49
+ def list_invitations(api_key: nil)
50
+ fetch_list(ApiPaths::ACCOUNT_INVITATIONS, api_key:)
51
+ end
52
+
53
+ def accept_invitation(invitation_id, activate: nil, api_key: nil)
54
+ request(fill(ApiPaths::ACCOUNT_INVITATION_ACCEPT, invitationId: invitation_id), method: :post,
55
+ body: activate.nil? ? {} : {"activate" => activate}, repeatable: true, api_key:)
56
+ end
57
+
58
+ def decline_invitation(invitation_id, api_key: nil)
59
+ request(fill(ApiPaths::ACCOUNT_INVITATION_DECLINE, invitationId: invitation_id), method: :post,
60
+ repeatable: true, api_key:)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenEmail
4
+ module Resources
5
+ class Addresses < Resource
6
+ def list(limit: nil, cursor: nil, api_key: nil)
7
+ envelope = request(ApiPaths::ADDRESSES, query: Pagination.page_query(nil, CURSOR, limit, cursor), api_key:)
8
+ envelope = {} unless envelope.is_a?(Hash)
9
+ next_cursor = Pagination.cursor_of(envelope[:nextCursor])
10
+
11
+ AddressBookPage.new(
12
+ unrestricted: envelope[:unrestricted],
13
+ addresses: Pagination.items_of(envelope),
14
+ domains: envelope[:domains].is_a?(Array) ? envelope[:domains] : [],
15
+ has_more: Pagination.more?(envelope, next_cursor),
16
+ next_cursor:
17
+ )
18
+ end
19
+
20
+ def list_all(limit: nil, cursor: nil, api_key: nil)
21
+ current = list(limit:, cursor:, api_key:)
22
+ addresses = current.addresses.dup
23
+
24
+ while current.has_more && current.next_cursor && current.next_cursor != cursor
25
+ cursor = current.next_cursor
26
+ current = list(limit:, cursor:, api_key:)
27
+ addresses.concat(current.addresses)
28
+ end
29
+
30
+ AddressBook.new(unrestricted: current.unrestricted, addresses:, domains: current.domains)
31
+ end
32
+
33
+ def iterate(limit: nil, cursor: nil, api_key: nil, &)
34
+ iterate_pages(ApiPaths::ADDRESSES, limit:, cursor:, api_key:, &)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenEmail
4
+ module Resources
5
+ class Analytics < Resource
6
+ def mailbox(from: nil, to: nil, offset_minutes: nil, grain: nil, address: nil, api_key: nil)
7
+ query = query_params(from: instant(from), to: instant(to), offset_minutes:, grain:, address:)
8
+ request(ApiPaths::ANALYTICS_MAILBOX, query:, api_key:)
9
+ end
10
+
11
+ def sending(days: nil, minutes: nil, offset_minutes: nil, grain: nil, address: nil, api_key: nil)
12
+ query = query_params(days:, minutes:, offset_minutes:, grain:, address:)
13
+ request(ApiPaths::ANALYTICS_SENDING, query:, api_key:)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenEmail
4
+ module Resources
5
+ class AppHost < Resource
6
+ def get(api_key: nil)
7
+ request(ApiPaths::APP_HOST, api_key:)
8
+ end
9
+
10
+ def set(body = nil, api_key: nil, **fields)
11
+ request(ApiPaths::APP_HOST, method: :put, body: payload(body, fields), repeatable: true, api_key:)
12
+ end
13
+
14
+ def verify(api_key: nil)
15
+ request(ApiPaths::APP_HOST_VERIFY, method: :post, repeatable: true, api_key:)
16
+ end
17
+
18
+ def delete(api_key: nil)
19
+ request(ApiPaths::APP_HOST, method: :delete, repeatable: true, api_key:)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenEmail
4
+ module Resources
5
+ class Audiences < Resource
6
+ def list(limit: nil, cursor: nil, api_key: nil)
7
+ fetch_page(ApiPaths::AUDIENCES, limit:, cursor:, api_key:)
8
+ end
9
+
10
+ def list_all(limit: nil, cursor: nil, api_key: nil)
11
+ collect_all(ApiPaths::AUDIENCES, limit:, cursor:, api_key:)
12
+ end
13
+
14
+ def iterate(limit: nil, cursor: nil, api_key: nil, &)
15
+ iterate_pages(ApiPaths::AUDIENCES, limit:, cursor:, api_key:, &)
16
+ end
17
+
18
+ def growth(audience_ids: nil, days: nil, minutes: nil, grain: nil, offset_minutes: nil, api_key: nil)
19
+ query = query_params(audience_ids: joined(audience_ids), days:, minutes:, grain:, offset_minutes:)
20
+ request(ApiPaths::AUDIENCES_GROWTH, query:, api_key:)
21
+ end
22
+
23
+ def get(id, api_key: nil)
24
+ request(fill(ApiPaths::AUDIENCE, id:), api_key:)
25
+ end
26
+
27
+ def create(body = nil, api_key: nil, **fields)
28
+ request(ApiPaths::AUDIENCES, method: :post, body: payload(body, fields), api_key:)
29
+ end
30
+
31
+ def update(id, patch = nil, api_key: nil, **fields)
32
+ request(fill(ApiPaths::AUDIENCE, id:), method: :patch, body: payload(patch, fields), repeatable: true, api_key:)
33
+ end
34
+
35
+ def delete(id, api_key: nil)
36
+ request(fill(ApiPaths::AUDIENCE, id:), method: :delete, api_key:)
37
+ end
38
+
39
+ def empty(id, api_key: nil)
40
+ request(fill(ApiPaths::AUDIENCE_EMPTY, id:), method: :post, api_key:)
41
+ end
42
+
43
+ def list_contacts(id, q: nil, source: nil, sort: nil, statuses: nil, limit: nil, cursor: nil, api_key: nil)
44
+ fetch_page(fill(ApiPaths::AUDIENCE_CONTACTS, id:), member_query(q, source, sort, statuses), limit:, cursor:,
45
+ api_key:)
46
+ end
47
+
48
+ def list_all_contacts(id, q: nil, source: nil, sort: nil, statuses: nil, limit: nil, cursor: nil, api_key: nil)
49
+ collect_all(fill(ApiPaths::AUDIENCE_CONTACTS, id:), member_query(q, source, sort, statuses), limit:, cursor:,
50
+ api_key:)
51
+ end
52
+
53
+ def iterate_contacts(id, q: nil, source: nil, sort: nil, statuses: nil, limit: nil, cursor: nil, api_key: nil, &)
54
+ iterate_pages(fill(ApiPaths::AUDIENCE_CONTACTS, id:), member_query(q, source, sort, statuses), limit:, cursor:,
55
+ api_key:, &)
56
+ end
57
+
58
+ def add_contact(id, body = nil, api_key: nil, **fields)
59
+ request(fill(ApiPaths::AUDIENCE_CONTACTS, id:), method: :post, body: payload(body, fields), repeatable: true,
60
+ api_key:)
61
+ end
62
+
63
+ def remove_contact(id, email, api_key: nil)
64
+ request(fill(ApiPaths::AUDIENCE_CONTACT, id:, email:), method: :delete, api_key:)
65
+ end
66
+
67
+ def add_contacts(id, body = nil, api_key: nil, **fields)
68
+ request(fill(ApiPaths::AUDIENCE_CONTACTS_BATCH, id:), method: :post, body: payload(body, fields),
69
+ repeatable: true, api_key:)
70
+ end
71
+
72
+ def remove_contacts(id, body = nil, api_key: nil, **fields)
73
+ request(fill(ApiPaths::AUDIENCE_CONTACTS_BATCH_REMOVE, id:), method: :post, body: payload(body, fields),
74
+ repeatable: true, api_key:)
75
+ end
76
+
77
+ def import_contacts(id, body = nil, api_key: nil, **fields)
78
+ request(fill(ApiPaths::AUDIENCE_IMPORT, id:), method: :post, body: payload(body, fields), repeatable: true,
79
+ api_key:)
80
+ end
81
+
82
+ private
83
+
84
+ def member_query(q, source, sort, statuses)
85
+ status = (statuses.nil? || statuses.empty?) ? nil : joined(statuses)
86
+ query_params(q:, source:, sort:, status:)
87
+ end
88
+ end
89
+ end
90
+ end