lettermint 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +42 -0
- data/README.md +141 -28
- data/docs/api.md +210 -0
- data/docs/sdk.md +510 -0
- data/lib/lettermint/client.rb +3 -29
- data/lib/lettermint/email_message.rb +11 -2
- data/lib/lettermint/errors.rb +75 -9
- data/lib/lettermint/http_client.rb +45 -24
- data/lib/lettermint/resources/base.rb +33 -0
- data/lib/lettermint/resources/domains.rb +66 -0
- data/lib/lettermint/resources/messages.rb +80 -0
- data/lib/lettermint/resources/projects.rb +103 -0
- data/lib/lettermint/resources/routes.rb +92 -0
- data/lib/lettermint/resources/stats.rb +23 -0
- data/lib/lettermint/resources/suppressions.rb +58 -0
- data/lib/lettermint/resources/team.rb +38 -0
- data/lib/lettermint/resources/webhooks.rb +122 -0
- data/lib/lettermint/sending_api.rb +75 -0
- data/lib/lettermint/team_api.rb +90 -0
- data/lib/lettermint/version.rb +1 -1
- data/lib/lettermint.rb +15 -0
- metadata +14 -2
- data/lettermint.gemspec +0 -35
|
@@ -4,7 +4,7 @@ require 'faraday'
|
|
|
4
4
|
|
|
5
5
|
module Lettermint
|
|
6
6
|
class HttpClient
|
|
7
|
-
def initialize(api_token:, base_url:, timeout:)
|
|
7
|
+
def initialize(api_token:, base_url:, timeout:, auth_scheme: :project)
|
|
8
8
|
normalized_url = "#{base_url.chomp('/')}/"
|
|
9
9
|
@connection = Faraday.new(url: normalized_url) do |f|
|
|
10
10
|
f.request :json
|
|
@@ -14,7 +14,7 @@ module Lettermint
|
|
|
14
14
|
f.headers = {
|
|
15
15
|
'Content-Type' => 'application/json',
|
|
16
16
|
'Accept' => 'application/json',
|
|
17
|
-
|
|
17
|
+
**auth_headers(api_token, auth_scheme),
|
|
18
18
|
'User-Agent' => "Lettermint/#{Lettermint::VERSION} (Ruby; ruby #{RUBY_VERSION})"
|
|
19
19
|
}
|
|
20
20
|
end
|
|
@@ -57,46 +57,67 @@ module Lettermint
|
|
|
57
57
|
|
|
58
58
|
private
|
|
59
59
|
|
|
60
|
+
def auth_headers(token, scheme)
|
|
61
|
+
case scheme.to_sym
|
|
62
|
+
when :project then { 'x-lettermint-token' => token }
|
|
63
|
+
when :team then { 'Authorization' => "Bearer #{token}" }
|
|
64
|
+
else raise ArgumentError, "Unknown auth_scheme: #{scheme}"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
60
68
|
def with_error_handling
|
|
61
69
|
response = yield
|
|
62
70
|
handle_response(response)
|
|
63
71
|
rescue Faraday::TimeoutError, Timeout::Error
|
|
64
72
|
raise Lettermint::TimeoutError, "Request timeout after #{@connection.options.timeout}s"
|
|
73
|
+
rescue Faraday::SSLError => e
|
|
74
|
+
raise Lettermint::ConnectionError.new(message: "SSL error: #{e.message}", original_exception: e)
|
|
65
75
|
rescue Faraday::ConnectionFailed => e
|
|
66
|
-
raise Lettermint::
|
|
76
|
+
raise Lettermint::ConnectionError.new(message: e.message, original_exception: e)
|
|
77
|
+
rescue Faraday::ParsingError => e
|
|
78
|
+
raise Lettermint::ResponseParsingError.new(message: "API response parsing failed: #{e.message}",
|
|
79
|
+
original_exception: e)
|
|
67
80
|
end
|
|
68
81
|
|
|
69
82
|
def handle_response(response)
|
|
70
83
|
return response.body if response.success?
|
|
71
84
|
|
|
72
|
-
|
|
73
|
-
raise_api_error(response.status, body, response.headers)
|
|
85
|
+
raise build_error(response.status, normalize_body(response.body), response.headers)
|
|
74
86
|
end
|
|
75
87
|
|
|
76
|
-
|
|
77
|
-
|
|
88
|
+
# Hash stays Hash; String is stripped (nil when empty); anything else passes through as parsed.
|
|
89
|
+
def normalize_body(body)
|
|
90
|
+
return body unless body.is_a?(String)
|
|
91
|
+
|
|
92
|
+
stripped = body.strip
|
|
93
|
+
stripped.empty? ? nil : stripped
|
|
78
94
|
end
|
|
79
95
|
|
|
80
|
-
def build_error(status, body, headers)
|
|
81
|
-
|
|
96
|
+
def build_error(status, body, headers)
|
|
97
|
+
message, code, details = ErrorBody.parse(status, body)
|
|
98
|
+
common = { message: message, response_body: body, code: code, details: details }
|
|
82
99
|
|
|
83
100
|
case status
|
|
84
|
-
when 401, 403
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
when
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
ValidationError.new(message: msg['message', 'Validation error'],
|
|
91
|
-
error_type: msg['error', 'ValidationError'], response_body: body)
|
|
92
|
-
when 429
|
|
93
|
-
retry_after = headers && headers['Retry-After'] && Integer(headers['Retry-After'], exception: false)
|
|
94
|
-
RateLimitError.new(message: msg['message', 'Rate limit exceeded'],
|
|
95
|
-
retry_after: retry_after, response_body: body)
|
|
96
|
-
else
|
|
97
|
-
HttpRequestError.new(message: msg['message', "HTTP #{status}"],
|
|
98
|
-
status_code: status, response_body: body)
|
|
101
|
+
when 401, 403 then AuthenticationError.new(status_code: status, **common)
|
|
102
|
+
when 400 then ClientError.new(**common)
|
|
103
|
+
when 404 then NotFoundError.new(**common)
|
|
104
|
+
when 422 then ValidationError.new(error_type: validation_error_type(body, code), **common)
|
|
105
|
+
when 429 then RateLimitError.new(retry_after: retry_after_from(headers), **common)
|
|
106
|
+
else HttpRequestError.new(status_code: status, **common)
|
|
99
107
|
end
|
|
100
108
|
end
|
|
109
|
+
|
|
110
|
+
# A String `error` is kept verbatim; a Hash `error` contributes its code.
|
|
111
|
+
def validation_error_type(body, code)
|
|
112
|
+
error = body.is_a?(Hash) ? body['error'] : nil
|
|
113
|
+
return error if error.is_a?(String)
|
|
114
|
+
|
|
115
|
+
code || 'ValidationError'
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def retry_after_from(headers)
|
|
119
|
+
value = headers && headers['Retry-After']
|
|
120
|
+
value && Integer(value, exception: false)
|
|
121
|
+
end
|
|
101
122
|
end
|
|
102
123
|
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lettermint
|
|
4
|
+
module Resources
|
|
5
|
+
# Base class for Team API resources providing shared functionality.
|
|
6
|
+
class Base
|
|
7
|
+
def initialize(http_client:)
|
|
8
|
+
@http_client = http_client
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
# Builds query parameters for list endpoints.
|
|
14
|
+
# @param page_size [Integer, nil] Number of items per page
|
|
15
|
+
# @param page_cursor [String, nil] Cursor for pagination
|
|
16
|
+
# @param sort [String, nil] Sort field (prefix with - for descending)
|
|
17
|
+
# @param include [String, nil] Related resources to include
|
|
18
|
+
# @param filters [Hash] Filter parameters (converted to filter[key]=value)
|
|
19
|
+
# @return [Hash, nil] Query parameters hash or nil if empty
|
|
20
|
+
def build_params(page_size: nil, page_cursor: nil, sort: nil, include: nil, **filters)
|
|
21
|
+
params = {
|
|
22
|
+
'page[size]' => page_size,
|
|
23
|
+
'page[cursor]' => page_cursor,
|
|
24
|
+
'sort' => sort,
|
|
25
|
+
'include' => include
|
|
26
|
+
}.compact
|
|
27
|
+
|
|
28
|
+
filters.each { |k, v| params["filter[#{k}]"] = v unless v.nil? }
|
|
29
|
+
params.empty? ? nil : params
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lettermint
|
|
4
|
+
module Resources
|
|
5
|
+
# Domains resource for managing sending domains and DNS verification.
|
|
6
|
+
class Domains < Base
|
|
7
|
+
# List all domains.
|
|
8
|
+
# @param page_size [Integer, nil] Number of items per page (default: 30)
|
|
9
|
+
# @param page_cursor [String, nil] Cursor for pagination
|
|
10
|
+
# @param sort [String, nil] Sort field: domain, created_at, status_changed_at (prefix - for desc)
|
|
11
|
+
# @param status [String, nil] Filter by status (verified, partially_verified, etc.)
|
|
12
|
+
# @param domain [String, nil] Filter by domain (partial match)
|
|
13
|
+
# @return [Hash] Paginated list of domains
|
|
14
|
+
def list(page_size: nil, page_cursor: nil, sort: nil, status: nil, domain: nil)
|
|
15
|
+
params = build_params(page_size:, page_cursor:, sort:, status:, domain:)
|
|
16
|
+
@http_client.get(path: '/domains', params: params)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Create a new domain.
|
|
20
|
+
# @param domain [String] Domain name (max 255 chars)
|
|
21
|
+
# @return [Hash] Created domain data
|
|
22
|
+
def create(domain:)
|
|
23
|
+
@http_client.post(path: '/domains', data: { domain: domain })
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Get domain details.
|
|
27
|
+
# @param id [String] Domain ID
|
|
28
|
+
# @param include [String, nil] Related data to include (dnsRecords, dnsRecordsCount, dnsRecordsExists)
|
|
29
|
+
# @return [Hash] Domain data with optional includes
|
|
30
|
+
def find(id, include: nil)
|
|
31
|
+
params = build_params(include: include)
|
|
32
|
+
@http_client.get(path: "/domains/#{id}", params: params)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Delete a domain.
|
|
36
|
+
# @param id [String] Domain ID
|
|
37
|
+
# @return [Hash] Confirmation message
|
|
38
|
+
def delete(id)
|
|
39
|
+
@http_client.delete(path: "/domains/#{id}")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Verify all DNS records for a domain.
|
|
43
|
+
# @param id [String] Domain ID
|
|
44
|
+
# @return [Hash] Verification result
|
|
45
|
+
def verify_dns(id)
|
|
46
|
+
@http_client.post(path: "/domains/#{id}/dns-records/verify")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Verify a specific DNS record.
|
|
50
|
+
# @param domain_id [String] Domain ID
|
|
51
|
+
# @param record_id [String] DNS record ID
|
|
52
|
+
# @return [Hash] Verification result
|
|
53
|
+
def verify_dns_record(domain_id, record_id)
|
|
54
|
+
@http_client.post(path: "/domains/#{domain_id}/dns-records/#{record_id}/verify")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Update projects associated with a domain.
|
|
58
|
+
# @param id [String] Domain ID
|
|
59
|
+
# @param project_ids [Array<String>] Array of project UUIDs
|
|
60
|
+
# @return [Hash] Updated domain data
|
|
61
|
+
def update_projects(id, project_ids:)
|
|
62
|
+
@http_client.put(path: "/domains/#{id}/projects", data: { project_ids: project_ids })
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lettermint
|
|
4
|
+
module Resources
|
|
5
|
+
# Messages resource for viewing sent and received messages.
|
|
6
|
+
class Messages < Base
|
|
7
|
+
# List messages.
|
|
8
|
+
# @param page_size [Integer, nil] Number of items per page (default: 30)
|
|
9
|
+
# @param page_cursor [String, nil] Cursor for pagination
|
|
10
|
+
# @param sort [String, nil] Sort field: type, status, from_email, subject, created_at, status_changed_at
|
|
11
|
+
# @param type [String, nil] Filter: inbound, outbound
|
|
12
|
+
# @param status [String, nil] Filter by status
|
|
13
|
+
# @param route_id [String, nil] Filter by route ID
|
|
14
|
+
# @param domain_id [String, nil] Filter by domain ID
|
|
15
|
+
# @param tag [String, nil] Filter by tag
|
|
16
|
+
# @param from_email [String, nil] Filter by sender email
|
|
17
|
+
# @param subject [String, nil] Filter by subject
|
|
18
|
+
# @param from_date [String, nil] Filter from date (Y-m-d)
|
|
19
|
+
# @param to_date [String, nil] Filter to date (Y-m-d)
|
|
20
|
+
# @return [Hash] Paginated list of messages
|
|
21
|
+
# rubocop:disable-next Metrics/ParameterLists
|
|
22
|
+
def list(page_size: nil, page_cursor: nil, sort: nil, type: nil, status: nil,
|
|
23
|
+
route_id: nil, domain_id: nil, tag: nil, from_email: nil, subject: nil,
|
|
24
|
+
from_date: nil, to_date: nil)
|
|
25
|
+
params = build_params(
|
|
26
|
+
page_size: page_size,
|
|
27
|
+
page_cursor: page_cursor,
|
|
28
|
+
sort: sort,
|
|
29
|
+
type: type,
|
|
30
|
+
status: status,
|
|
31
|
+
route_id: route_id,
|
|
32
|
+
domain_id: domain_id,
|
|
33
|
+
tag: tag,
|
|
34
|
+
from_email: from_email,
|
|
35
|
+
subject: subject,
|
|
36
|
+
from_date: from_date,
|
|
37
|
+
to_date: to_date
|
|
38
|
+
)
|
|
39
|
+
@http_client.get(path: '/messages', params: params)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Get message details.
|
|
43
|
+
# @param id [String] Message ID
|
|
44
|
+
# @return [Hash] Message data
|
|
45
|
+
def find(id)
|
|
46
|
+
@http_client.get(path: "/messages/#{id}")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Get message events (delivery history).
|
|
50
|
+
# @param id [String] Message ID
|
|
51
|
+
# @param sort [String, nil] Sort field: timestamp, event
|
|
52
|
+
# @return [Hash] List of message events
|
|
53
|
+
def events(id, sort: nil)
|
|
54
|
+
params = sort ? { 'sort' => sort } : nil
|
|
55
|
+
@http_client.get(path: "/messages/#{id}/events", params: params)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Get raw message source (RFC822 format).
|
|
59
|
+
# @param id [String] Message ID
|
|
60
|
+
# @return [String] Raw message source (message/rfc822)
|
|
61
|
+
def source(id)
|
|
62
|
+
@http_client.get(path: "/messages/#{id}/source", headers: { 'Accept' => 'message/rfc822' })
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Get message HTML body.
|
|
66
|
+
# @param id [String] Message ID
|
|
67
|
+
# @return [String] HTML content (text/html)
|
|
68
|
+
def html(id)
|
|
69
|
+
@http_client.get(path: "/messages/#{id}/html", headers: { 'Accept' => 'text/html' })
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Get message plain text body.
|
|
73
|
+
# @param id [String] Message ID
|
|
74
|
+
# @return [String] Plain text content (text/plain)
|
|
75
|
+
def text(id)
|
|
76
|
+
@http_client.get(path: "/messages/#{id}/text", headers: { 'Accept' => 'text/plain' })
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lettermint
|
|
4
|
+
module Resources
|
|
5
|
+
# Projects resource for managing projects, members, and accessing routes.
|
|
6
|
+
class Projects < Base
|
|
7
|
+
# List all projects.
|
|
8
|
+
# @param page_size [Integer, nil] Number of items per page (default: 30)
|
|
9
|
+
# @param page_cursor [String, nil] Cursor for pagination
|
|
10
|
+
# @param sort [String, nil] Sort field: name, created_at (prefix - for desc)
|
|
11
|
+
# @param search [String, nil] Search filter
|
|
12
|
+
# @return [Hash] Paginated list of projects
|
|
13
|
+
def list(page_size: nil, page_cursor: nil, sort: nil, search: nil)
|
|
14
|
+
params = build_params(page_size: page_size, page_cursor: page_cursor, sort: sort, search: search)
|
|
15
|
+
@http_client.get(path: '/projects', params: params)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Create a new project.
|
|
19
|
+
# @param name [String] Project name (max 255 chars)
|
|
20
|
+
# @param smtp_enabled [Boolean, nil] Enable SMTP (default: false)
|
|
21
|
+
# @param initial_routes [String, nil] Initial routes: both, transactional, broadcast (default: both)
|
|
22
|
+
# @return [Hash] Created project data including api_token
|
|
23
|
+
def create(name:, smtp_enabled: nil, initial_routes: nil)
|
|
24
|
+
data = { name: name }
|
|
25
|
+
data[:smtp_enabled] = smtp_enabled unless smtp_enabled.nil?
|
|
26
|
+
data[:initial_routes] = initial_routes if initial_routes
|
|
27
|
+
@http_client.post(path: '/projects', data: data)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Get project details.
|
|
31
|
+
#
|
|
32
|
+
# Note: Returns a Hash, not a resource object. To access routes for a project,
|
|
33
|
+
# use `projects.routes(project_id)` rather than chaining on the find result.
|
|
34
|
+
#
|
|
35
|
+
# @param id [String] Project ID
|
|
36
|
+
# @param include [String, nil] Related data: routes, domains, teamMembers, messageStats (+ Count/Exists variants)
|
|
37
|
+
# @return [Hash] Project data with optional includes
|
|
38
|
+
def find(id, include: nil)
|
|
39
|
+
params = include ? { 'include' => include } : nil
|
|
40
|
+
@http_client.get(path: "/projects/#{id}", params: params)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Update a project.
|
|
44
|
+
# @param id [String] Project ID
|
|
45
|
+
# @param name [String, nil] New project name
|
|
46
|
+
# @param smtp_enabled [Boolean, nil] Enable/disable SMTP
|
|
47
|
+
# @param default_route_id [String, nil] Default route UUID
|
|
48
|
+
# @return [Hash] Updated project data
|
|
49
|
+
def update(id, name: nil, smtp_enabled: nil, default_route_id: nil)
|
|
50
|
+
data = {}
|
|
51
|
+
data[:name] = name if name
|
|
52
|
+
data[:smtp_enabled] = smtp_enabled unless smtp_enabled.nil?
|
|
53
|
+
data[:default_route_id] = default_route_id if default_route_id
|
|
54
|
+
@http_client.put(path: "/projects/#{id}", data: data)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Delete a project.
|
|
58
|
+
# @param id [String] Project ID
|
|
59
|
+
# @return [Hash] Confirmation message
|
|
60
|
+
def delete(id)
|
|
61
|
+
@http_client.delete(path: "/projects/#{id}")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Rotate the project API token.
|
|
65
|
+
# @param id [String] Project ID
|
|
66
|
+
# @return [Hash] Contains new_token
|
|
67
|
+
def rotate_token(id)
|
|
68
|
+
@http_client.post(path: "/projects/#{id}/rotate-token")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Update project members (replace all).
|
|
72
|
+
# @param id [String] Project ID
|
|
73
|
+
# @param team_member_ids [Array<String>] Array of team member IDs
|
|
74
|
+
# @return [Hash] Confirmation
|
|
75
|
+
def update_members(id, team_member_ids:)
|
|
76
|
+
@http_client.put(path: "/projects/#{id}/members", data: { team_member_ids: team_member_ids })
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Add a member to the project.
|
|
80
|
+
# @param project_id [String] Project ID
|
|
81
|
+
# @param member_id [String] Team member ID
|
|
82
|
+
# @return [Hash] Confirmation
|
|
83
|
+
def add_member(project_id, member_id)
|
|
84
|
+
@http_client.post(path: "/projects/#{project_id}/members/#{member_id}")
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Remove a member from the project.
|
|
88
|
+
# @param project_id [String] Project ID
|
|
89
|
+
# @param member_id [String] Team member ID
|
|
90
|
+
# @return [Hash] Confirmation
|
|
91
|
+
def remove_member(project_id, member_id)
|
|
92
|
+
@http_client.delete(path: "/projects/#{project_id}/members/#{member_id}")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Get a routes accessor scoped to this project.
|
|
96
|
+
# @param project_id [String] Project ID
|
|
97
|
+
# @return [Routes] Routes resource scoped to the project
|
|
98
|
+
def routes(project_id)
|
|
99
|
+
Routes.new(http_client: @http_client, project_id: project_id)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lettermint
|
|
4
|
+
module Resources
|
|
5
|
+
# Routes resource for managing project routes (transactional, broadcast, inbound).
|
|
6
|
+
# Can be instantiated with a project_id for scoped operations or without for direct route access.
|
|
7
|
+
class Routes < Base
|
|
8
|
+
# @param http_client [HttpClient] HTTP client instance
|
|
9
|
+
# @param project_id [String, nil] Optional project ID for scoped operations
|
|
10
|
+
def initialize(http_client:, project_id: nil)
|
|
11
|
+
super(http_client: http_client)
|
|
12
|
+
@project_id = project_id
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# List routes for a project.
|
|
16
|
+
# Requires project_id to be set (via constructor or projects.routes(id)).
|
|
17
|
+
# @param page_size [Integer, nil] Number of items per page (default: 30)
|
|
18
|
+
# @param page_cursor [String, nil] Cursor for pagination
|
|
19
|
+
# @param sort [String, nil] Sort field: name, slug, created_at (prefix - for desc)
|
|
20
|
+
# @param route_type [String, nil] Filter: transactional, broadcast, inbound
|
|
21
|
+
# @param is_default [Boolean, nil] Filter by default route status
|
|
22
|
+
# @param search [String, nil] Search filter
|
|
23
|
+
# @return [Hash] Paginated list of routes
|
|
24
|
+
# rubocop:disable-next Metrics/ParameterLists
|
|
25
|
+
def list(page_size: nil, page_cursor: nil, sort: nil, route_type: nil,
|
|
26
|
+
is_default: nil, search: nil)
|
|
27
|
+
raise ArgumentError, 'project_id required for listing routes' unless @project_id
|
|
28
|
+
|
|
29
|
+
params = build_params(
|
|
30
|
+
page_size: page_size,
|
|
31
|
+
page_cursor: page_cursor,
|
|
32
|
+
sort: sort,
|
|
33
|
+
route_type: route_type,
|
|
34
|
+
is_default: is_default,
|
|
35
|
+
search: search
|
|
36
|
+
)
|
|
37
|
+
@http_client.get(path: "/projects/#{@project_id}/routes", params: params)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Create a new route in a project.
|
|
41
|
+
# Requires project_id to be set.
|
|
42
|
+
# @param name [String] Route name (max 255 chars)
|
|
43
|
+
# @param route_type [String] Type: transactional, broadcast, inbound
|
|
44
|
+
# @param slug [String, nil] Optional slug (max 255 chars)
|
|
45
|
+
# @return [Hash] Created route data
|
|
46
|
+
def create(name:, route_type:, slug: nil)
|
|
47
|
+
raise ArgumentError, 'project_id required for creating routes' unless @project_id
|
|
48
|
+
|
|
49
|
+
data = { name: name, route_type: route_type }
|
|
50
|
+
data[:slug] = slug if slug
|
|
51
|
+
@http_client.post(path: "/projects/#{@project_id}/routes", data: data)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Get route details.
|
|
55
|
+
# @param id [String] Route ID
|
|
56
|
+
# @param include [String, nil] Related data: project, statistics
|
|
57
|
+
# @return [Hash] Route data
|
|
58
|
+
def find(id, include: nil)
|
|
59
|
+
params = include ? { 'include' => include } : nil
|
|
60
|
+
@http_client.get(path: "/routes/#{id}", params: params)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Update a route.
|
|
64
|
+
# @param id [String] Route ID
|
|
65
|
+
# @param name [String, nil] New route name
|
|
66
|
+
# @param settings [Hash, nil] Route settings (track_opens, track_clicks, disable_hosted_unsubscribe)
|
|
67
|
+
# @param inbound_settings [Hash, nil] Inbound settings (inbound_domain, spam_threshold, etc.)
|
|
68
|
+
# @return [Hash] Updated route data
|
|
69
|
+
def update(id, name: nil, settings: nil, inbound_settings: nil)
|
|
70
|
+
data = {}
|
|
71
|
+
data[:name] = name if name
|
|
72
|
+
data[:settings] = settings if settings
|
|
73
|
+
data[:inbound_settings] = inbound_settings if inbound_settings
|
|
74
|
+
@http_client.put(path: "/routes/#{id}", data: data)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Delete a route.
|
|
78
|
+
# @param id [String] Route ID
|
|
79
|
+
# @return [Hash] Confirmation message
|
|
80
|
+
def delete(id)
|
|
81
|
+
@http_client.delete(path: "/routes/#{id}")
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Verify inbound domain for a route.
|
|
85
|
+
# @param id [String] Route ID
|
|
86
|
+
# @return [Hash] Verification result
|
|
87
|
+
def verify_inbound_domain(id)
|
|
88
|
+
@http_client.post(path: "/routes/#{id}/verify-inbound-domain")
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lettermint
|
|
4
|
+
module Resources
|
|
5
|
+
# Stats resource for retrieving email statistics.
|
|
6
|
+
class Stats < Base
|
|
7
|
+
# Get statistics for a date range.
|
|
8
|
+
# @param from [String] Start date (Y-m-d format, required)
|
|
9
|
+
# @param to [String] End date (Y-m-d format, required, max 90 days from start)
|
|
10
|
+
# @param project_id [String, nil] Filter by project ID
|
|
11
|
+
# @param route_id [String, nil] Filter by a single route ID
|
|
12
|
+
# @param route_ids [Array<String>, String, nil] Filter by multiple route IDs
|
|
13
|
+
# @return [Hash] Stats data with totals and daily breakdown
|
|
14
|
+
def get(from:, to:, project_id: nil, route_id: nil, route_ids: nil)
|
|
15
|
+
params = { 'from' => from, 'to' => to }
|
|
16
|
+
params['project_id'] = project_id if project_id
|
|
17
|
+
params['route_id'] = route_id if route_id
|
|
18
|
+
params['route_ids'] = route_ids if route_ids
|
|
19
|
+
@http_client.get(path: '/stats', params: params)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lettermint
|
|
4
|
+
module Resources
|
|
5
|
+
# Suppressions resource for managing email suppression lists.
|
|
6
|
+
class Suppressions < Base
|
|
7
|
+
# List suppressions.
|
|
8
|
+
# @param page_size [Integer, nil] Number of items per page (default: 30)
|
|
9
|
+
# @param page_cursor [String, nil] Cursor for pagination
|
|
10
|
+
# @param sort [String, nil] Sort field: value, created_at, reason
|
|
11
|
+
# @param scope [String, nil] Filter: team, project, route
|
|
12
|
+
# @param route_id [String, nil] Filter by route ID
|
|
13
|
+
# @param project_id [String, nil] Filter by project ID
|
|
14
|
+
# @param value [String, nil] Filter by suppression value (email/domain/extension)
|
|
15
|
+
# @param reason [String, nil] Filter: spam_complaint, hard_bounce, unsubscribe, manual
|
|
16
|
+
# @return [Hash] Paginated list of suppressions
|
|
17
|
+
# rubocop:disable-next Metrics/ParameterLists
|
|
18
|
+
def list(page_size: nil, page_cursor: nil, sort: nil, scope: nil,
|
|
19
|
+
route_id: nil, project_id: nil, value: nil, reason: nil)
|
|
20
|
+
params = build_params(
|
|
21
|
+
page_size: page_size,
|
|
22
|
+
page_cursor: page_cursor,
|
|
23
|
+
sort: sort,
|
|
24
|
+
scope: scope,
|
|
25
|
+
route_id: route_id,
|
|
26
|
+
project_id: project_id,
|
|
27
|
+
value: value,
|
|
28
|
+
reason: reason
|
|
29
|
+
)
|
|
30
|
+
@http_client.get(path: '/suppressions', params: params)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Create a suppression entry.
|
|
34
|
+
# @param reason [String] Reason: spam_complaint, hard_bounce, unsubscribe, manual
|
|
35
|
+
# @param scope [String] Scope: team, project, route
|
|
36
|
+
# @param email [String, nil] Single email to suppress (max 255 chars)
|
|
37
|
+
# @param emails [Array<String>, nil] Multiple emails to suppress (max 1000)
|
|
38
|
+
# @param route_id [String, nil] Route ID (required if scope is route)
|
|
39
|
+
# @param project_id [String, nil] Project ID (required if scope is project)
|
|
40
|
+
# @return [Hash] Created suppression data
|
|
41
|
+
def create(reason:, scope:, email: nil, emails: nil, route_id: nil, project_id: nil) # rubocop:disable Metrics/ParameterLists
|
|
42
|
+
data = { reason: reason, scope: scope }
|
|
43
|
+
data[:email] = email if email
|
|
44
|
+
data[:emails] = emails if emails
|
|
45
|
+
data[:route_id] = route_id if route_id
|
|
46
|
+
data[:project_id] = project_id if project_id
|
|
47
|
+
@http_client.post(path: '/suppressions', data: data)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Delete a suppression entry.
|
|
51
|
+
# @param id [String] Suppression ID
|
|
52
|
+
# @return [Hash] Confirmation message
|
|
53
|
+
def delete(id)
|
|
54
|
+
@http_client.delete(path: "/suppressions/#{id}")
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lettermint
|
|
4
|
+
module Resources
|
|
5
|
+
# Team resource for managing team settings, usage, and members.
|
|
6
|
+
class Team < Base
|
|
7
|
+
# Get team details.
|
|
8
|
+
# @param include [String, nil] Related data to include (features, featuresCount, featuresExists)
|
|
9
|
+
# @return [Hash] Team data
|
|
10
|
+
def get(include: nil)
|
|
11
|
+
params = include ? { 'include' => include } : nil
|
|
12
|
+
@http_client.get(path: '/team', params: params)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Update team settings.
|
|
16
|
+
# @param name [String] New team name (max 255 chars)
|
|
17
|
+
# @return [Hash] Updated team data
|
|
18
|
+
def update(name:)
|
|
19
|
+
@http_client.put(path: '/team', data: { name: name })
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Get team usage statistics.
|
|
23
|
+
# @return [Hash] Current period and up to 12 historical periods
|
|
24
|
+
def usage
|
|
25
|
+
@http_client.get(path: '/team/usage')
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# List team members.
|
|
29
|
+
# @param page_size [Integer, nil] Number of items per page (default: 30)
|
|
30
|
+
# @param page_cursor [String, nil] Cursor for pagination
|
|
31
|
+
# @return [Hash] Paginated list of team members
|
|
32
|
+
def members(page_size: nil, page_cursor: nil)
|
|
33
|
+
params = build_params(page_size: page_size, page_cursor: page_cursor)
|
|
34
|
+
@http_client.get(path: '/team/members', params: params)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|