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.
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lettermint
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.1'
5
5
  end
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.0
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
- - lettermint.gemspec
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