emailfuse 0.1.4 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1870b9111d8d731962de39300f68e6ac57faa8a67e4233d70eda08e32848d675
4
- data.tar.gz: c0b955ed9bff041c6410d9b5ab93ba1a7292d3377630a1080a416cfb3d4d57cf
3
+ metadata.gz: d1b4f565a1eeaa3276ef82ece624d8fb7f0e2f04f1391b6c6ac3eac119a4e828
4
+ data.tar.gz: f1bf41d6a0c55dfe7c7c0a799effa3c2d0d666dff1a2e766a43209f97890ba65
5
5
  SHA512:
6
- metadata.gz: d60b56c812cb1198f619442d9bc1758205d458734f6f3e163d385558ca12c6b294367a828189d807571e7699a60cd9a458c2afd529c7fd4c0ffec4021108c378
7
- data.tar.gz: 78a748fffc10dcd1bf3959b0da4a6649e241a71b14ec6579f9d7376e261e547f67d9cfdafd9938a6f81927dde7c47e2d470043e36811b2630c6506ba4fbaa171
6
+ metadata.gz: 48d156ee8fc31d249f1f89fa8f06407a2675ba243cb2955c3a82bf91ccd0b9b30ea5efc6294e38f9c32f3bbe8821cc1c16d343c9880960e2e9bfbfafb07bcf4f
7
+ data.tar.gz: 29a56256dbeabe5dcdcc09aa468495dab2bdb9c4fb62cbe1d39106bc82bd54cc4b4a925ff2b8025b21eef56ea5705bae2109cb7a41193ec4e94d68461e493104
data/CHANGELOG.md ADDED
@@ -0,0 +1,131 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.0.0] - 2025-10-31
9
+
10
+ ### Overview
11
+
12
+ This major release introduces breaking changes to the Contacts API and deprecates the Audiences API in favor of Segments. **If you only use the SDK for sending emails or the Audiences API, you can upgrade to v1.0.0 without any code changes.** The breaking changes are limited to the Contacts API only.
13
+
14
+ ### ⚠️ BREAKING CHANGES
15
+
16
+ #### Contacts API Changes
17
+
18
+ - ⚠️ **Change `Contacts.create` to accept hash parameter with optional `audience_id`** - Previously required `audience_id` in params, now it's optional. Supports global contacts.
19
+ - ⚠️ **Change `Contacts.get` from `get(audience_id, id)` to `get(params)`** - Now accepts a hash with optional `audience_id` and required `id` or `email`. Raises `ArgumentError: "Missing \`id\` or \`email\` field"` when neither is provided.
20
+ - ⚠️ **Change `Contacts.list` from `list(audience_id, params = {})` to `list(params = {})`** - Now accepts a hash with optional `audience_id` and pagination params
21
+ - ⚠️ **Change `Contacts.remove` from `remove(audience_id, contact_id)` to `remove(params)`** - Now accepts a hash with optional `audience_id` and required `id` or `email`. Raises `ArgumentError: "Missing \`id\` or \`email\` field"` when neither is provided.
22
+ - ⚠️ **Change `Contacts.update` error message** - Error changed from `"id or email is required"` to `"Missing \`id\` or \`email\` field"` to match Node.js SDK format
23
+ - ⚠️ **Change `Contacts.update` to accept optional `audience_id`** - Previously required `audience_id` in params, now it's optional
24
+
25
+ **Before (v0.x):**
26
+
27
+ ```ruby
28
+ # Methods used positional arguments and required audience_id
29
+ Resend::Contacts.create(audience_id: "aud_123", email: "user@example.com", first_name: "John")
30
+ contact = Resend::Contacts.get("aud_123", "contact_123")
31
+ contacts = Resend::Contacts.list("aud_123")
32
+ contacts = Resend::Contacts.list("aud_123", limit: 10)
33
+ Resend::Contacts.update(audience_id: "aud_123", id: "contact_123", first_name: "Jane")
34
+ Resend::Contacts.remove("aud_123", "contact_123")
35
+ ```
36
+
37
+ **After (v1.0.0):**
38
+
39
+ ```ruby
40
+ # Methods use hash parameters and support optional audience_id
41
+ # Global contacts (no audience_id)
42
+ Resend::Contacts.create(email: "user@example.com", first_name: "John")
43
+ contact = Resend::Contacts.get(id: "contact_123")
44
+ contact = Resend::Contacts.get(email: "user@example.com")
45
+ contacts = Resend::Contacts.list
46
+ contacts = Resend::Contacts.list(limit: 10)
47
+ Resend::Contacts.update(id: "contact_123", first_name: "Jane")
48
+ Resend::Contacts.remove(id: "contact_123")
49
+
50
+ # Audience-scoped contacts (with audience_id)
51
+ Resend::Contacts.create(audience_id: "aud_123", email: "user@example.com", first_name: "John")
52
+ contact = Resend::Contacts.get(audience_id: "aud_123", id: "contact_123")
53
+ contacts = Resend::Contacts.list(audience_id: "aud_123", limit: 10)
54
+ Resend::Contacts.update(audience_id: "aud_123", id: "contact_123", first_name: "Jane")
55
+ Resend::Contacts.remove(audience_id: "aud_123", id: "contact_123")
56
+ ```
57
+
58
+ #### Audiences API Deprecated
59
+
60
+ - ⚠️ **Deprecate `Resend::Audiences` in favor of `Resend::Segments`** - The Audiences module has been replaced with Segments. A backward-compatible alias `Audiences = Segments` has been added, so existing code will continue to work.
61
+
62
+ **Migration (Recommended):**
63
+
64
+ Update your code to use `Segments` instead of `Audiences`:
65
+
66
+ ```ruby
67
+ # Before (still works but deprecated)
68
+ Resend::Audiences.create(name: "My Audience")
69
+ Resend::Audiences.get("audience_123")
70
+ Resend::Audiences.list
71
+ Resend::Audiences.remove("audience_123")
72
+
73
+ # After (recommended)
74
+ Resend::Segments.create(name: "My Segment")
75
+ Resend::Segments.get("segment_123")
76
+ Resend::Segments.list
77
+ Resend::Segments.remove("segment_123")
78
+ ```
79
+
80
+ **Note:** The `Audiences` alias is deprecated and may be removed in a future major version. Please migrate to `Segments`.
81
+
82
+ ### Added
83
+
84
+ #### New API Modules
85
+
86
+ - Add `Resend::Templates` API for managing email templates
87
+ - `Templates.create` - Create a new template
88
+ - `Templates.get` - Retrieve a template by ID
89
+ - `Templates.update` - Update an existing template
90
+ - `Templates.publish` - Publish a template
91
+ - `Templates.duplicate` - Duplicate an existing template
92
+ - `Templates.list` - List all templates with pagination
93
+ - `Templates.remove` - Delete a template
94
+ - Add `Resend::Topics` API for managing topics
95
+ - `Topics.create` - Create a new topic
96
+ - `Topics.get` - Retrieve a topic by ID
97
+ - `Topics.update` - Update a topic
98
+ - `Topics.list` - List all topics with pagination
99
+ - `Topics.remove` - Delete a topic
100
+ - Add `Resend::Segments` API for managing segments (replacement for Audiences)
101
+ - `Segments.create` - Create a new segment
102
+ - `Segments.get` - Retrieve a segment by ID
103
+ - `Segments.list` - List all segments
104
+ - `Segments.remove` - Delete a segment
105
+ - Add `Resend::ContactProperties` API for managing custom contact properties
106
+ - `ContactProperties.update` - Update contact properties (validates and raises `ArgumentError: "Missing \`id\` field"` when id is not provided)
107
+ - `ContactProperties.get` - Retrieve contact properties by ID
108
+ - Add `Resend::Contacts::Segments` API for managing contact-segment relationships
109
+ - `Contacts::Segments.list` - List all segments for a contact
110
+ - `Contacts::Segments.add` - Add a contact to a segment
111
+ - `Contacts::Segments.remove` - Remove a contact from a segment
112
+ - Add `Resend::Contacts::Topics` API for managing contact topic subscriptions
113
+ - `Contacts::Topics.list` - List topic subscriptions for a contact
114
+ - `Contacts::Topics.update` - Update topic subscriptions (opt-in/opt-out) for a contact
115
+
116
+ #### Contacts API Enhancements
117
+
118
+ - Add support for `email` parameter in `Contacts.get` and `Contacts.remove` methods (can now use email instead of ID)
119
+ - Add `audience_id` support in Contacts API methods for scoped operations
120
+ - Add support for global contacts (contacts not scoped to a specific audience/segment)
121
+ - Add validation error messages matching Node.js SDK format with backticks around field names
122
+
123
+ #### Broadcasts API Updates
124
+
125
+ - Add deprecation warnings for `audience_id` in `Broadcasts.create` and `Broadcasts.update` (use `segment_id` instead)
126
+
127
+ ### Removed
128
+
129
+ - Remove deprecated `send_email` method from `Resend::Emails` module (use `Resend::Emails.send` instead)
130
+
131
+ [1.0.0]: https://github.com/resend/resend-ruby/compare/v0.26.0...v1.0.0
data/README.md CHANGED
@@ -1,22 +1,103 @@
1
- # Emailfuse Gem
1
+ # EmailFuse Ruby and Rails SDK
2
2
 
3
- An API wrapper for Emailfuse, including a Rails Action Mailer adapter.
3
+ ---
4
4
 
5
- ## Installing
5
+ ## Installation
6
6
 
7
- In your `Gemfile`
7
+ To install EmailFuse Ruby and Rails SDK, simply execute the following command in a terminal:
8
+
9
+ Via RubyGems:
10
+
11
+ ```
12
+ gem install emailfuse
13
+ ```
14
+
15
+ Via Gemfile:
16
+
17
+ ```
18
+ gem 'emailfuse'
19
+ ```
20
+
21
+ ## Setup
8
22
 
9
23
  ```ruby
10
- gem "emailfuse"
24
+ require "emailfuse"
25
+ EmailFuse.api_key = ENV["EMAILFUSE_API_KEY"]
26
+ ```
27
+
28
+ or
29
+
30
+ ```ruby
31
+ require "emailfuse"
32
+ EmailFuse.configure do |config|
33
+ config.api_key = ENV["EMAILFUSE_API_KEY"]
34
+ end
35
+ ```
36
+
37
+ ## Example
38
+
39
+ ```rb
40
+ require "emailfuse"
41
+
42
+ EmailFuse.api_key = ENV["EMAILFUSE_API_KEY"]
43
+
44
+ params = {
45
+ "from": "onboarding@resend.dev",
46
+ "to": ["delivered@resend.dev", "your@email.com"],
47
+ "html": "<h1>Hello World</h1>",
48
+ "subject": "Hey"
49
+ }
50
+ r = EmailFuse::Emails.send(params)
51
+ puts r
11
52
  ```
12
53
 
13
- ## Usage
54
+ # Rails and ActionMailer support
14
55
 
15
- Create an initializer file in `config/initializers/emailfuse.rb` with the following:
56
+ This gem can be used as an ActionMailer delivery method, add this to your `config/environments/environment.rb` file.
16
57
 
17
58
  ```ruby
18
59
  config.action_mailer.delivery_method = :emailfuse
19
- Emailfuse.configure do |config|
20
- config.token = ENV["EMAILFUSE_TOKEN"]
60
+ ```
61
+
62
+ ### Configuration Options
63
+
64
+ You can configure the API key and host in two ways:
65
+
66
+ #### Option 1: Global Configuration (Initializer)
67
+
68
+ Create or update your mailer initializer file:
69
+
70
+ ```rb
71
+ # /config/initializers/emailfuse.rb
72
+ EmailFuse.configure do |config|
73
+ config.api_key = ENV["EMAILFUSE_API_KEY"]
74
+ config.base_url = "https://api.emailfuse.net" # optional, defaults to https://api.emailfuse.net
21
75
  end
22
76
  ```
77
+
78
+ #### Option 2: Rails Mailer Settings
79
+
80
+ Configure directly in your environment file:
81
+
82
+ ```ruby
83
+ # config/environments/production.rb
84
+ config.action_mailer.delivery_method = :emailfuse
85
+ config.action_mailer.emailfuse_settings = {
86
+ api_key: ENV["EMAILFUSE_API_KEY"],
87
+ host: "https://api.emailfuse.net" # optional
88
+ }
89
+ ```
90
+
91
+ Settings configured via `emailfuse_settings` take precedence over the global configuration, allowing you to use different credentials per environment.
92
+
93
+ ### Sending Emails
94
+
95
+ After configuration, you can use `deliver_now!`:
96
+
97
+ ```ruby
98
+ mailer = EmailMailer.email(self)
99
+ response = mailer.deliver_now!
100
+
101
+ puts response[:id]
102
+ # => "b8f94710-0d84-429c-925a-22d3d8f86916"
103
+ ```
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EmailFuse
4
+ # Module responsible for wrapping Batch email sending API
5
+ module Batch
6
+ class << self
7
+ # Send a batch of emails
8
+ #
9
+ # @param params [Array<Hash>] Array of email parameters (max 100 emails)
10
+ # @param options [Hash] Additional options for the request
11
+ # @option options [String] :idempotency_key Optional idempotency key
12
+ # @option options [String] :batch_validation Batch validation mode: "strict" (default) or "permissive"
13
+ # - "strict": Entire batch fails if any email is invalid
14
+ # - "permissive": Sends valid emails and returns errors for invalid ones
15
+ #
16
+ # @return [Hash] Response with :data array and optional :errors array (in permissive mode)
17
+ #
18
+ # @example Send batch with strict validation (default)
19
+ # EmailFuse::Batch.send([
20
+ # { from: "sender@example.com", to: ["recipient@example.com"], subject: "Hello", html: "<p>Hi</p>" }
21
+ # ])
22
+ #
23
+ # @example Send batch with permissive validation
24
+ # response = EmailFuse::Batch.send(emails, options: { batch_validation: "permissive" })
25
+ # # response[:data] contains successful email IDs
26
+ # # response[:errors] contains validation errors with index and message
27
+ #
28
+ # https://resend.com/docs/api-reference/emails/send-batch-emails
29
+ def send(params = [], options: {})
30
+ raise ArgumentError, "params must be an array of email hashes" unless params.is_a?(Array)
31
+ raise ArgumentError, "params cannot be empty" if params.empty?
32
+
33
+ path = "emails/batch"
34
+
35
+ EmailFuse::Request.new(path, params, "post", options: options).perform
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ # backwards compatibility
4
+ require "email_fuse/emails"
5
+
6
+ module EmailFuse
7
+ # Client class.
8
+ class Client
9
+ include EmailFuse::Emails
10
+
11
+ attr_reader :api_key
12
+
13
+ def initialize(api_key)
14
+ raise ArgumentError, "API Key is not a string" unless api_key.is_a?(String)
15
+
16
+ @api_key = api_key
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EmailFuse
4
+ module Emails
5
+ # Module for sent email attachments API operations
6
+ module Attachments
7
+ class << self
8
+ # Retrieve a single attachment from a sent email
9
+ #
10
+ # @param params [Hash] Parameters for retrieving the attachment
11
+ # @option params [String] :id The attachment ID (required)
12
+ # @option params [String] :email_id The email ID (required)
13
+ # @return [Hash] The attachment object
14
+ #
15
+ # @example
16
+ # EmailFuse::Emails::Attachments.get(
17
+ # id: "2a0c9ce0-3112-4728-976e-47ddcd16a318",
18
+ # email_id: "4ef9a417-02e9-4d39-ad75-9611e0fcc33c"
19
+ # )
20
+ def get(params = {})
21
+ attachment_id = params[:id]
22
+ email_id = params[:email_id]
23
+
24
+ raise ArgumentError, ":email_id is required" if email_id.nil? || email_id.to_s.empty?
25
+ raise ArgumentError, ":id is required" if attachment_id.nil? || attachment_id.to_s.empty?
26
+
27
+ path = "emails/#{email_id}/attachments/#{attachment_id}"
28
+ EmailFuse::Request.new(path, {}, "get").perform
29
+ end
30
+
31
+ # List attachments from a sent email with optional pagination
32
+ #
33
+ # @param params [Hash] Parameters for listing attachments
34
+ # @option params [String] :email_id The email ID (required)
35
+ # @option params [Integer] :limit Maximum number of attachments to return (1-100)
36
+ # @option params [String] :after Cursor for pagination (newer attachments)
37
+ # @option params [String] :before Cursor for pagination (older attachments)
38
+ # @return [Hash] List of attachments with pagination info
39
+ #
40
+ # @example List all attachments
41
+ # EmailFuse::Emails::Attachments.list(
42
+ # email_id: "4ef9a417-02e9-4d39-ad75-9611e0fcc33c"
43
+ # )
44
+ #
45
+ # @example List with custom limit
46
+ # EmailFuse::Emails::Attachments.list(
47
+ # email_id: "4ef9a417-02e9-4d39-ad75-9611e0fcc33c",
48
+ # limit: 50
49
+ # )
50
+ #
51
+ # @example List with pagination
52
+ # EmailFuse::Emails::Attachments.list(
53
+ # email_id: "4ef9a417-02e9-4d39-ad75-9611e0fcc33c",
54
+ # limit: 20,
55
+ # after: "attachment_id_123"
56
+ # )
57
+ def list(params = {})
58
+ email_id = params[:email_id]
59
+ raise ArgumentError, ":email_id is required" if email_id.nil? || email_id.to_s.empty?
60
+
61
+ base_path = "emails/#{email_id}/attachments"
62
+
63
+ # Extract pagination parameters
64
+ pagination_params = params.slice(:limit, :after, :before)
65
+
66
+ path = EmailFuse::PaginationHelper.build_paginated_path(base_path, pagination_params)
67
+ EmailFuse::Request.new(path, {}, "get").perform
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EmailFuse
4
+ module Emails
5
+ module Receiving
6
+ # Module for received email attachments API operations
7
+ module Attachments
8
+ class << self
9
+ # Retrieve a single attachment from a received email
10
+ #
11
+ # @param params [Hash] Parameters for retrieving the attachment
12
+ # @option params [String] :id The attachment ID (required)
13
+ # @option params [String] :email_id The email ID (required)
14
+ # @return [Hash] The attachment object
15
+ #
16
+ # @example
17
+ # EmailFuse::Emails::Receiving::Attachments.get(
18
+ # id: "2a0c9ce0-3112-4728-976e-47ddcd16a318",
19
+ # email_id: "4ef9a417-02e9-4d39-ad75-9611e0fcc33c"
20
+ # )
21
+ def get(params = {})
22
+ attachment_id = params[:id]
23
+ email_id = params[:email_id]
24
+
25
+ raise ArgumentError, ":email_id is required" if email_id.nil? || email_id.to_s.empty?
26
+ raise ArgumentError, ":id is required" if attachment_id.nil? || attachment_id.to_s.empty?
27
+
28
+ path = "emails/receiving/#{email_id}/attachments/#{attachment_id}"
29
+ EmailFuse::Request.new(path, {}, "get").perform
30
+ end
31
+
32
+ # List attachments from a received email with optional pagination
33
+ #
34
+ # @param params [Hash] Parameters for listing attachments
35
+ # @option params [String] :email_id The email ID (required)
36
+ # @option params [Integer] :limit Maximum number of attachments to return (1-100)
37
+ # @option params [String] :after Cursor for pagination (newer attachments)
38
+ # @option params [String] :before Cursor for pagination (older attachments)
39
+ # @return [Hash] List of attachments with pagination info
40
+ #
41
+ # @example List all attachments
42
+ # EmailFuse::Emails::Receiving::Attachments.list(
43
+ # email_id: "4ef9a417-02e9-4d39-ad75-9611e0fcc33c"
44
+ # )
45
+ #
46
+ # @example List with custom limit
47
+ # EmailFuse::Emails::Receiving::Attachments.list(
48
+ # email_id: "4ef9a417-02e9-4d39-ad75-9611e0fcc33c",
49
+ # limit: 50
50
+ # )
51
+ #
52
+ # @example List with pagination
53
+ # EmailFuse::Emails::Receiving::Attachments.list(
54
+ # email_id: "4ef9a417-02e9-4d39-ad75-9611e0fcc33c",
55
+ # limit: 20,
56
+ # after: "attachment_id_123"
57
+ # )
58
+ def list(params = {})
59
+ email_id = params[:email_id]
60
+ raise ArgumentError, ":email_id is required" if email_id.nil? || email_id.to_s.empty?
61
+
62
+ base_path = "emails/receiving/#{email_id}/attachments"
63
+
64
+ # Extract pagination parameters
65
+ pagination_params = params.slice(:limit, :after, :before)
66
+
67
+ path = EmailFuse::PaginationHelper.build_paginated_path(base_path, pagination_params)
68
+ EmailFuse::Request.new(path, {}, "get").perform
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EmailFuse
4
+ module Emails
5
+ # Module for receiving emails API operations
6
+ module Receiving
7
+ class << self
8
+ # Retrieve a single received email
9
+ #
10
+ # @param email_id [String] The ID of the received email
11
+ # @return [Hash] The received email object
12
+ #
13
+ # @example
14
+ # EmailFuse::Emails::Receiving.get("4ef9a417-02e9-4d39-ad75-9611e0fcc33c")
15
+ def get(email_id)
16
+ raise ArgumentError, "email_id is required" if email_id.nil? || email_id.to_s.empty?
17
+
18
+ path = "emails/receiving/#{email_id}"
19
+ EmailFuse::Request.new(path, {}, "get").perform
20
+ end
21
+
22
+ # List received emails with optional pagination
23
+ #
24
+ # @param params [Hash] Optional parameters for pagination
25
+ # @option params [Integer] :limit Maximum number of emails to return (1-100)
26
+ # @option params [String] :after Cursor for pagination (newer emails)
27
+ # @option params [String] :before Cursor for pagination (older emails)
28
+ # @return [Hash] List of received emails with pagination info
29
+ #
30
+ # @example List all received emails
31
+ # EmailFuse::Emails::Receiving.list
32
+ #
33
+ # @example List with custom limit
34
+ # EmailFuse::Emails::Receiving.list(limit: 50)
35
+ #
36
+ # @example List with pagination
37
+ # EmailFuse::Emails::Receiving.list(limit: 20, after: "email_id_123")
38
+ def list(params = {})
39
+ path = EmailFuse::PaginationHelper.build_paginated_path("emails/receiving", params)
40
+ EmailFuse::Request.new(path, {}, "get").perform
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EmailFuse
4
+ # Module responsible for wrapping email sending API
5
+ module Emails
6
+ class << self
7
+ # Sends or schedules an email.
8
+ #
9
+ # @param params [Hash] Email parameters
10
+ # @option params [String] :from The sender email address (required)
11
+ # @option params [Array<String>, String] :to The recipient email address(es) (required)
12
+ # @option params [String] :subject The email subject
13
+ # @option params [String] :html The HTML content of the email
14
+ # @option params [String] :text The plain text content of the email
15
+ #
16
+ # @return [EmailFuse::Response] The response containing the email ID
17
+ def send(params, options: {})
18
+ raise ArgumentError, ":from is required" unless params[:from] || params["from"]
19
+ raise ArgumentError, ":to is required" unless params[:to] || params["to"]
20
+
21
+ path = "emails"
22
+ EmailFuse::Request.new(path, params, "post", options: options).perform
23
+ end
24
+
25
+ # Retrieve a single email.
26
+ #
27
+ # @param email_id [String] The email ID (required)
28
+ #
29
+ # @return [EmailFuse::Response] The email object
30
+ def get(email_id)
31
+ raise ArgumentError, "email_id is required" if email_id.nil? || email_id.to_s.empty?
32
+
33
+ path = "emails/#{email_id}"
34
+ EmailFuse::Request.new(path, {}, "get").perform
35
+ end
36
+
37
+ # Update a scheduled email.
38
+ #
39
+ # @param params [Hash] Update parameters
40
+ # @option params [String] :email_id The email ID (required)
41
+ #
42
+ # @return [EmailFuse::Response] The updated email object
43
+ def update(params)
44
+ email_id = params[:email_id] || params["email_id"]
45
+ raise ArgumentError, ":email_id is required" if email_id.nil? || email_id.to_s.empty?
46
+
47
+ path = "emails/#{email_id}"
48
+ EmailFuse::Request.new(path, params, "patch").perform
49
+ end
50
+
51
+ # Cancel a scheduled email.
52
+ #
53
+ # @param email_id [String] The email ID (required)
54
+ #
55
+ # @return [EmailFuse::Response] Confirmation of cancellation
56
+ def cancel(email_id)
57
+ raise ArgumentError, "email_id is required" if email_id.nil? || email_id.to_s.empty?
58
+
59
+ path = "emails/#{email_id}/cancel"
60
+ EmailFuse::Request.new(path, {}, "post").perform
61
+ end
62
+
63
+ # List emails with optional pagination.
64
+ #
65
+ # @param options [Hash] Optional parameters for pagination
66
+ # @option options [Integer] :limit Maximum number of emails to return (1-100, default 20)
67
+ # @option options [String] :after Cursor for pagination (newer emails)
68
+ # @option options [String] :before Cursor for pagination (older emails)
69
+ #
70
+ # @return [EmailFuse::Response] Paginated list of emails
71
+ def list(options = {})
72
+ path = "emails"
73
+
74
+ # Build query parameters, filtering out nil values
75
+ query_params = {}
76
+ query_params[:limit] = options[:limit] if options[:limit]
77
+ query_params[:after] = options[:after] if options[:after]
78
+ query_params[:before] = options[:before] if options[:before]
79
+
80
+ EmailFuse::Request.new(path, query_params, "get").perform
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EmailFuse
4
+ # Errors wrapper class
5
+ class Error < StandardError
6
+ # 4xx HTTP status code
7
+ ClientError = Class.new(self)
8
+
9
+ # 5xx HTTP status code
10
+ ServerError = Class.new(self)
11
+
12
+ # code 500
13
+ InternalServerError = Class.new(ServerError)
14
+
15
+ # code 400, 401, 404, 422 - these are client errors (4xx)
16
+ InvalidRequestError = Class.new(ClientError)
17
+
18
+ # code 404
19
+ NotFoundError = Class.new(ClientError)
20
+
21
+ # code 429
22
+ class RateLimitExceededError < ClientError
23
+ attr_reader :rate_limit_limit, :rate_limit_remaining, :rate_limit_reset, :retry_after
24
+
25
+ def initialize(msg, code = nil, headers = {})
26
+ super(msg, code, headers)
27
+ @rate_limit_limit = headers["ratelimit-limit"]&.to_i
28
+ @rate_limit_remaining = headers["ratelimit-remaining"]&.to_i
29
+ @rate_limit_reset = headers["ratelimit-reset"]&.to_i
30
+ @retry_after = headers["retry-after"]&.to_i
31
+ end
32
+ end
33
+
34
+ ERRORS = {
35
+ 400 => EmailFuse::Error::InvalidRequestError,
36
+ 401 => EmailFuse::Error::InvalidRequestError,
37
+ 404 => EmailFuse::Error::NotFoundError,
38
+ 422 => EmailFuse::Error::InvalidRequestError,
39
+ 429 => EmailFuse::Error::RateLimitExceededError,
40
+ 500 => EmailFuse::Error::InternalServerError
41
+ }.freeze
42
+
43
+ attr_reader :code, :headers
44
+
45
+ def initialize(msg, code = nil, headers = {})
46
+ super(msg)
47
+ @code = code
48
+ @headers = headers
49
+ end
50
+ end
51
+ end