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
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lettermint
|
|
4
|
+
module Resources
|
|
5
|
+
# Webhooks resource for managing webhook endpoints and viewing deliveries.
|
|
6
|
+
class Webhooks < Base
|
|
7
|
+
# List all webhooks.
|
|
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, url, created_at (prefix - for desc)
|
|
11
|
+
# @param enabled [Boolean, nil] Filter by enabled status
|
|
12
|
+
# @param event [String, nil] Filter by event type
|
|
13
|
+
# @param route_id [String, nil] Filter by route ID
|
|
14
|
+
# @param search [String, nil] Search filter
|
|
15
|
+
# @return [Hash] Paginated list of webhooks
|
|
16
|
+
# rubocop:disable-next Metrics/ParameterLists
|
|
17
|
+
def list(page_size: nil, page_cursor: nil, sort: nil, enabled: nil,
|
|
18
|
+
event: nil, route_id: nil, search: nil)
|
|
19
|
+
params = build_params(
|
|
20
|
+
page_size: page_size,
|
|
21
|
+
page_cursor: page_cursor,
|
|
22
|
+
sort: sort,
|
|
23
|
+
enabled: enabled,
|
|
24
|
+
event: event,
|
|
25
|
+
route_id: route_id,
|
|
26
|
+
search: search
|
|
27
|
+
)
|
|
28
|
+
@http_client.get(path: '/webhooks', params: params)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Create a new webhook.
|
|
32
|
+
# @param route_id [String] Route ID to attach webhook to
|
|
33
|
+
# @param name [String] Webhook name (max 255 chars)
|
|
34
|
+
# @param url [String] Webhook URL (max 500 chars)
|
|
35
|
+
# @param events [Array<String>] Event types to subscribe (min 1)
|
|
36
|
+
# @param enabled [Boolean, nil] Enable webhook (default: true)
|
|
37
|
+
# @return [Hash] Created webhook data including secret (shown only once)
|
|
38
|
+
def create(route_id:, name:, url:, events:, enabled: nil)
|
|
39
|
+
data = { route_id: route_id, name: name, url: url, events: events }
|
|
40
|
+
data[:enabled] = enabled unless enabled.nil?
|
|
41
|
+
@http_client.post(path: '/webhooks', data: data)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Get webhook details.
|
|
45
|
+
# @param id [String] Webhook ID
|
|
46
|
+
# @return [Hash] Webhook data including secret
|
|
47
|
+
def find(id)
|
|
48
|
+
@http_client.get(path: "/webhooks/#{id}")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Update a webhook.
|
|
52
|
+
# @param id [String] Webhook ID
|
|
53
|
+
# @param name [String, nil] New webhook name
|
|
54
|
+
# @param url [String, nil] New webhook URL
|
|
55
|
+
# @param enabled [Boolean, nil] Enable/disable webhook
|
|
56
|
+
# @param events [Array<String>, nil] Event types (min 1)
|
|
57
|
+
# @return [Hash] Updated webhook data
|
|
58
|
+
def update(id, name: nil, url: nil, enabled: nil, events: nil)
|
|
59
|
+
data = {}
|
|
60
|
+
data[:name] = name if name
|
|
61
|
+
data[:url] = url if url
|
|
62
|
+
data[:enabled] = enabled unless enabled.nil?
|
|
63
|
+
data[:events] = events if events
|
|
64
|
+
@http_client.put(path: "/webhooks/#{id}", data: data)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Delete a webhook.
|
|
68
|
+
# @param id [String] Webhook ID
|
|
69
|
+
# @return [Hash] Confirmation message
|
|
70
|
+
def delete(id)
|
|
71
|
+
@http_client.delete(path: "/webhooks/#{id}")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Test a webhook by sending a test delivery.
|
|
75
|
+
# @param id [String] Webhook ID
|
|
76
|
+
# @return [Hash] Contains delivery_id for tracking
|
|
77
|
+
def test(id)
|
|
78
|
+
@http_client.post(path: "/webhooks/#{id}/test")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Regenerate webhook secret.
|
|
82
|
+
# @param id [String] Webhook ID
|
|
83
|
+
# @return [Hash] Updated webhook data with new secret
|
|
84
|
+
def regenerate_secret(id)
|
|
85
|
+
@http_client.post(path: "/webhooks/#{id}/regenerate-secret")
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# List webhook deliveries.
|
|
89
|
+
# @param webhook_id [String] Webhook ID
|
|
90
|
+
# @param page_size [Integer, nil] Number of items per page
|
|
91
|
+
# @param page_cursor [String, nil] Cursor for pagination
|
|
92
|
+
# @param sort [String, nil] Sort field: created_at, attempt_number
|
|
93
|
+
# @param status [String, nil] Filter: pending, success, failed, client_error, server_error, timeout
|
|
94
|
+
# @param event_type [String, nil] Filter by event type
|
|
95
|
+
# @param from_date [String, nil] Filter from date (Y-m-d)
|
|
96
|
+
# @param to_date [String, nil] Filter to date (Y-m-d)
|
|
97
|
+
# @return [Hash] Paginated list of deliveries
|
|
98
|
+
# rubocop:disable-next Metrics/ParameterLists
|
|
99
|
+
def deliveries(webhook_id, page_size: nil, page_cursor: nil, sort: nil,
|
|
100
|
+
status: nil, event_type: nil, from_date: nil, to_date: nil)
|
|
101
|
+
params = build_params(
|
|
102
|
+
page_size: page_size,
|
|
103
|
+
page_cursor: page_cursor,
|
|
104
|
+
sort: sort,
|
|
105
|
+
status: status,
|
|
106
|
+
event_type: event_type,
|
|
107
|
+
from_date: from_date,
|
|
108
|
+
to_date: to_date
|
|
109
|
+
)
|
|
110
|
+
@http_client.get(path: "/webhooks/#{webhook_id}/deliveries", params: params)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Get a specific delivery.
|
|
114
|
+
# @param webhook_id [String] Webhook ID
|
|
115
|
+
# @param delivery_id [String] Delivery ID
|
|
116
|
+
# @return [Hash] Delivery data including payload and response
|
|
117
|
+
def delivery(webhook_id, delivery_id)
|
|
118
|
+
@http_client.get(path: "/webhooks/#{webhook_id}/deliveries/#{delivery_id}")
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lettermint
|
|
4
|
+
# Client for the Lettermint Sending API (project-level email sending).
|
|
5
|
+
# Authenticates with project tokens via x-lettermint-token header.
|
|
6
|
+
class SendingAPI
|
|
7
|
+
attr_reader :configuration
|
|
8
|
+
|
|
9
|
+
def initialize(api_token:, base_url: nil, timeout: nil)
|
|
10
|
+
validate_api_token!(api_token)
|
|
11
|
+
|
|
12
|
+
@configuration = Configuration.new
|
|
13
|
+
@configuration.base_url = base_url || Lettermint.configuration.base_url
|
|
14
|
+
@configuration.timeout = timeout || Lettermint.configuration.timeout
|
|
15
|
+
|
|
16
|
+
yield @configuration if block_given?
|
|
17
|
+
|
|
18
|
+
@http_client = HttpClient.new(
|
|
19
|
+
api_token: api_token,
|
|
20
|
+
base_url: @configuration.base_url,
|
|
21
|
+
timeout: @configuration.timeout,
|
|
22
|
+
auth_scheme: :project
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def email
|
|
27
|
+
EmailMessage.new(http_client: @http_client)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Makes a GET request to an arbitrary API endpoint.
|
|
31
|
+
#
|
|
32
|
+
# @param path [String] The API endpoint path (e.g., '/domains')
|
|
33
|
+
# @param params [Hash, nil] Query parameters to include in the request
|
|
34
|
+
# @param headers [Hash, nil] Additional HTTP headers
|
|
35
|
+
# @return [Hash] The parsed JSON response body
|
|
36
|
+
def get(path, params: nil, headers: nil)
|
|
37
|
+
@http_client.get(path: path, params: params, headers: headers)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Makes a POST request to an arbitrary API endpoint.
|
|
41
|
+
#
|
|
42
|
+
# @param path [String] The API endpoint path
|
|
43
|
+
# @param data [Hash, nil] The request body (will be JSON-encoded)
|
|
44
|
+
# @param headers [Hash, nil] Additional HTTP headers
|
|
45
|
+
# @return [Hash] The parsed JSON response body
|
|
46
|
+
def post(path, data: nil, headers: nil)
|
|
47
|
+
@http_client.post(path: path, data: data, headers: headers)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Makes a PUT request to an arbitrary API endpoint.
|
|
51
|
+
#
|
|
52
|
+
# @param path [String] The API endpoint path
|
|
53
|
+
# @param data [Hash, nil] The request body (will be JSON-encoded)
|
|
54
|
+
# @param headers [Hash, nil] Additional HTTP headers
|
|
55
|
+
# @return [Hash] The parsed JSON response body
|
|
56
|
+
def put(path, data: nil, headers: nil)
|
|
57
|
+
@http_client.put(path: path, data: data, headers: headers)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Makes a DELETE request to an arbitrary API endpoint.
|
|
61
|
+
#
|
|
62
|
+
# @param path [String] The API endpoint path
|
|
63
|
+
# @param headers [Hash, nil] Additional HTTP headers
|
|
64
|
+
# @return [Hash] The parsed JSON response body
|
|
65
|
+
def delete(path, headers: nil)
|
|
66
|
+
@http_client.delete(path: path, headers: headers)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def validate_api_token!(token)
|
|
72
|
+
raise ArgumentError, 'API token cannot be empty' if token.nil? || token.to_s.strip.empty?
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lettermint
|
|
4
|
+
# Client for the Lettermint Team API (team-level management operations).
|
|
5
|
+
# Authenticates with team tokens (lm_team_*) via Authorization: Bearer header.
|
|
6
|
+
class TeamAPI
|
|
7
|
+
attr_reader :configuration
|
|
8
|
+
|
|
9
|
+
def initialize(team_token:, base_url: nil, timeout: nil)
|
|
10
|
+
validate_team_token!(team_token)
|
|
11
|
+
|
|
12
|
+
@configuration = Configuration.new
|
|
13
|
+
@configuration.base_url = base_url || Lettermint.configuration.base_url
|
|
14
|
+
@configuration.timeout = timeout || Lettermint.configuration.timeout
|
|
15
|
+
|
|
16
|
+
yield @configuration if block_given?
|
|
17
|
+
|
|
18
|
+
@http_client = HttpClient.new(
|
|
19
|
+
api_token: team_token,
|
|
20
|
+
base_url: @configuration.base_url,
|
|
21
|
+
timeout: @configuration.timeout,
|
|
22
|
+
auth_scheme: :team
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Health check endpoint (accepts both token types)
|
|
27
|
+
# @return [Hash] Parsed response body, e.g. { 'ok' => true } on success
|
|
28
|
+
def ping
|
|
29
|
+
@http_client.get(path: '/ping')
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Team resource accessor
|
|
33
|
+
# @return [Resources::Team]
|
|
34
|
+
def team
|
|
35
|
+
Resources::Team.new(http_client: @http_client)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Domains resource accessor
|
|
39
|
+
# @return [Resources::Domains]
|
|
40
|
+
def domains
|
|
41
|
+
Resources::Domains.new(http_client: @http_client)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Projects resource accessor
|
|
45
|
+
# @return [Resources::Projects]
|
|
46
|
+
def projects
|
|
47
|
+
Resources::Projects.new(http_client: @http_client)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Webhooks resource accessor
|
|
51
|
+
# @return [Resources::Webhooks]
|
|
52
|
+
def webhooks
|
|
53
|
+
Resources::Webhooks.new(http_client: @http_client)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Messages resource accessor
|
|
57
|
+
# @return [Resources::Messages]
|
|
58
|
+
def messages
|
|
59
|
+
Resources::Messages.new(http_client: @http_client)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Suppressions resource accessor
|
|
63
|
+
# @return [Resources::Suppressions]
|
|
64
|
+
def suppressions
|
|
65
|
+
Resources::Suppressions.new(http_client: @http_client)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Stats resource accessor
|
|
69
|
+
# @return [Resources::Stats]
|
|
70
|
+
def stats
|
|
71
|
+
Resources::Stats.new(http_client: @http_client)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Routes resource accessor (top-level for direct route access)
|
|
75
|
+
# For project-scoped routes, use projects.routes(project_id)
|
|
76
|
+
# @return [Resources::Routes]
|
|
77
|
+
def routes
|
|
78
|
+
Resources::Routes.new(http_client: @http_client)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def validate_team_token!(token)
|
|
84
|
+
raise ArgumentError, 'Team token cannot be empty' if token.nil? || token.to_s.strip.empty?
|
|
85
|
+
return if token.to_s.start_with?('lm_team_')
|
|
86
|
+
|
|
87
|
+
raise ArgumentError, "Invalid team token format (expected 'lm_team_*')"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
data/lib/lettermint/version.rb
CHANGED
data/lib/lettermint.rb
CHANGED
|
@@ -7,6 +7,21 @@ require_relative 'lettermint/configuration'
|
|
|
7
7
|
require_relative 'lettermint/http_client'
|
|
8
8
|
require_relative 'lettermint/email_message'
|
|
9
9
|
require_relative 'lettermint/webhook'
|
|
10
|
+
|
|
11
|
+
# Resources (Team API)
|
|
12
|
+
require_relative 'lettermint/resources/base'
|
|
13
|
+
require_relative 'lettermint/resources/team'
|
|
14
|
+
require_relative 'lettermint/resources/domains'
|
|
15
|
+
require_relative 'lettermint/resources/projects'
|
|
16
|
+
require_relative 'lettermint/resources/routes'
|
|
17
|
+
require_relative 'lettermint/resources/webhooks'
|
|
18
|
+
require_relative 'lettermint/resources/messages'
|
|
19
|
+
require_relative 'lettermint/resources/suppressions'
|
|
20
|
+
require_relative 'lettermint/resources/stats'
|
|
21
|
+
|
|
22
|
+
# API Clients
|
|
23
|
+
require_relative 'lettermint/sending_api'
|
|
24
|
+
require_relative 'lettermint/team_api'
|
|
10
25
|
require_relative 'lettermint/client'
|
|
11
26
|
|
|
12
27
|
module Lettermint
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lettermint
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Delano
|
|
@@ -34,13 +34,25 @@ files:
|
|
|
34
34
|
- CHANGELOG.md
|
|
35
35
|
- LICENSE
|
|
36
36
|
- README.md
|
|
37
|
-
-
|
|
37
|
+
- docs/api.md
|
|
38
|
+
- docs/sdk.md
|
|
38
39
|
- lib/lettermint.rb
|
|
39
40
|
- lib/lettermint/client.rb
|
|
40
41
|
- lib/lettermint/configuration.rb
|
|
41
42
|
- lib/lettermint/email_message.rb
|
|
42
43
|
- lib/lettermint/errors.rb
|
|
43
44
|
- lib/lettermint/http_client.rb
|
|
45
|
+
- lib/lettermint/resources/base.rb
|
|
46
|
+
- lib/lettermint/resources/domains.rb
|
|
47
|
+
- lib/lettermint/resources/messages.rb
|
|
48
|
+
- lib/lettermint/resources/projects.rb
|
|
49
|
+
- lib/lettermint/resources/routes.rb
|
|
50
|
+
- lib/lettermint/resources/stats.rb
|
|
51
|
+
- lib/lettermint/resources/suppressions.rb
|
|
52
|
+
- lib/lettermint/resources/team.rb
|
|
53
|
+
- lib/lettermint/resources/webhooks.rb
|
|
54
|
+
- lib/lettermint/sending_api.rb
|
|
55
|
+
- lib/lettermint/team_api.rb
|
|
44
56
|
- lib/lettermint/types.rb
|
|
45
57
|
- lib/lettermint/version.rb
|
|
46
58
|
- lib/lettermint/webhook.rb
|
data/lettermint.gemspec
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'lib/lettermint/version'
|
|
4
|
-
|
|
5
|
-
Gem::Specification.new do |spec|
|
|
6
|
-
spec.name = 'lettermint'
|
|
7
|
-
spec.version = Lettermint::VERSION
|
|
8
|
-
spec.authors = ['Delano']
|
|
9
|
-
spec.email = ['gems@onetimesecret.com']
|
|
10
|
-
|
|
11
|
-
spec.summary = 'Ruby SDK for the Lettermint transactional email API'
|
|
12
|
-
spec.description = 'Send transactional emails and verify webhooks with the Lettermint API. ' \
|
|
13
|
-
'Provides a fluent builder interface, typed responses, and HMAC-SHA256 webhook verification.'
|
|
14
|
-
spec.homepage = 'https://github.com/onetimesecret/lettermint-ruby'
|
|
15
|
-
spec.license = 'MIT'
|
|
16
|
-
spec.required_ruby_version = '>= 3.2.0'
|
|
17
|
-
|
|
18
|
-
spec.metadata['homepage_uri'] = spec.homepage
|
|
19
|
-
spec.metadata['source_code_uri'] = spec.homepage
|
|
20
|
-
spec.metadata['changelog_uri'] = "#{spec.homepage}/blob/main/CHANGELOG.md"
|
|
21
|
-
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
22
|
-
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
|
23
|
-
|
|
24
|
-
spec.files = Dir.chdir(__dir__) do
|
|
25
|
-
`git ls-files -z`.split("\x0").reject do |f|
|
|
26
|
-
(File.expand_path(f) == __FILE__) ||
|
|
27
|
-
f.start_with?('spec/', 'examples/', '.git', '.rubocop', 'Rakefile', 'Gemfile',
|
|
28
|
-
'CLAUDE', '.pre-commit', '.rspec')
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
spec.require_paths = ['lib']
|
|
33
|
-
|
|
34
|
-
spec.add_dependency 'faraday', '~> 2.0'
|
|
35
|
-
end
|