resend 0.24.0 → 0.26.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: 17fbcff646b3034847bea36e37e12f63a8654fd38cdc66da8fd935704d613250
4
- data.tar.gz: 4a5105290aa022dd5e72ef6e546294dd23c50d7dc7fb1458c40c51ac679517d8
3
+ metadata.gz: 380fc77dd4f78646cea876d8a41c040c2456f5d83de51794adb366ae0cdf4c68
4
+ data.tar.gz: 6e868e3144a11781a20dcdd7fc2613e01885820d621595421845bceefee4c334
5
5
  SHA512:
6
- metadata.gz: c469989bed973379461c1b93a837f7acd8f1a6cfceac4007f49cf9215189307faed7018cf7fd6adba389af9cfffacda93bc434aba1c739452f58a75f042b19b4
7
- data.tar.gz: 590552f7916c9b870fbaed5f54ebbb35652aa3bf8a9b332c020d1e77345c2020ba61fa8f95fc6943f0d2e3ded9907f99a9c9507a3eb76b6a5721dd37fc97507a
6
+ metadata.gz: 6aecfeb42d92ef7a7c90620a0a529f522b26c00b9341cdcf2ca0ae71c100710703b4162b96d4889f29a09825285a923ce1feaef465765ce2d060dffcf17405d4
7
+ data.tar.gz: c14fb10e9a778579ad5c1615899db85b90e2feba28a7b6441f0b3dcde4679a1d4bafe26ef93e9631ca2087540ade271687b174d797acad10afda53eacef4f10b
data/README.md CHANGED
@@ -72,7 +72,7 @@ puts r
72
72
 
73
73
  You can view all the examples in the [examples folder](https://github.com/drish/resend-ruby/tree/main/examples)
74
74
 
75
- # Rails and ActiveMailer support
75
+ # Rails and ActionMailer support
76
76
 
77
77
  This gem can be used as an ActionMailer delivery method, add this to your `config/environments/environment.rb` file.
78
78
 
@@ -11,8 +11,8 @@ module Resend
11
11
  end
12
12
 
13
13
  # https://resend.com/docs/api-reference/api-keys/list-api-keys
14
- def list
15
- path = "api-keys"
14
+ def list(params = {})
15
+ path = Resend::PaginationHelper.build_paginated_path("api-keys", params)
16
16
  Resend::Request.new(path, {}, "get").perform
17
17
  end
18
18
 
@@ -17,8 +17,8 @@ module Resend
17
17
  end
18
18
 
19
19
  # https://resend.com/docs/api-reference/audiences/list-audiences
20
- def list
21
- path = "audiences"
20
+ def list(params = {})
21
+ path = Resend::PaginationHelper.build_paginated_path("audiences", params)
22
22
  Resend::Request.new(path, {}, "get").perform
23
23
  end
24
24
 
data/lib/resend/batch.rb CHANGED
@@ -4,6 +4,27 @@ module Resend
4
4
  # Module responsible for wrapping Batch email sending API
5
5
  module Batch
6
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
+ # Resend::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 = Resend::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
+ #
7
28
  # https://resend.com/docs/api-reference/emails/send-batch-emails
8
29
  def send(params = [], options: {})
9
30
  path = "emails/batch"
@@ -23,8 +23,8 @@ module Resend
23
23
  end
24
24
 
25
25
  # https://resend.com/docs/api-reference/broadcasts/list-broadcasts
26
- def list
27
- path = "broadcasts"
26
+ def list(params = {})
27
+ path = Resend::PaginationHelper.build_paginated_path("broadcasts", params)
28
28
  Resend::Request.new(path, {}, "get").perform
29
29
  end
30
30
 
@@ -26,9 +26,10 @@ module Resend
26
26
  # List contacts in an audience
27
27
  #
28
28
  # @param audience_id [String] the audience id
29
+ # @param params [Hash] optional pagination parameters
29
30
  # https://resend.com/docs/api-reference/contacts/list-contacts
30
- def list(audience_id)
31
- path = "audiences/#{audience_id}/contacts"
31
+ def list(audience_id, params = {})
32
+ path = Resend::PaginationHelper.build_paginated_path("audiences/#{audience_id}/contacts", params)
32
33
  Resend::Request.new(path, {}, "get").perform
33
34
  end
34
35
 
@@ -22,9 +22,9 @@ module Resend
22
22
  Resend::Request.new(path, {}, "get").perform
23
23
  end
24
24
 
25
- # https://resend.com/docs/api-reference/api-keys/list-api-keys
26
- def list
27
- path = "domains"
25
+ # https://resend.com/docs/api-reference/domains/list-domains
26
+ def list(params = {})
27
+ path = Resend::PaginationHelper.build_paginated_path("domains", params)
28
28
  Resend::Request.new(path, {}, "get").perform
29
29
  end
30
30
 
data/lib/resend/emails.rb CHANGED
@@ -31,6 +31,25 @@ module Resend
31
31
  path = "emails/#{email_id}/cancel"
32
32
  Resend::Request.new(path, {}, "post").perform
33
33
  end
34
+
35
+ # List emails with optional pagination.
36
+ # see more: https://resend.com/docs/api-reference/emails/list-emails
37
+ #
38
+ # @param options [Hash] Optional parameters for pagination
39
+ # @option options [Integer] :limit Maximum number of emails to return (1-100, default 20)
40
+ # @option options [String] :after Cursor for pagination (newer emails)
41
+ # @option options [String] :before Cursor for pagination (older emails)
42
+ def list(options = {})
43
+ path = "emails"
44
+
45
+ # Build query parameters, filtering out nil values
46
+ query_params = {}
47
+ query_params[:limit] = options[:limit] if options[:limit]
48
+ query_params[:after] = options[:after] if options[:after]
49
+ query_params[:before] = options[:before] if options[:before]
50
+
51
+ Resend::Request.new(path, query_params, "get").perform
52
+ end
34
53
  end
35
54
 
36
55
  # This method is kept here for backwards compatibility
data/lib/resend/errors.rb CHANGED
@@ -17,7 +17,17 @@ module Resend
17
17
  InvalidRequestError = Class.new(ServerError)
18
18
 
19
19
  # code 429
20
- RateLimitExceededError = Class.new(ServerError)
20
+ class RateLimitExceededError < ServerError
21
+ attr_reader :rate_limit_limit, :rate_limit_remaining, :rate_limit_reset, :retry_after
22
+
23
+ def initialize(msg, code = nil, headers = {})
24
+ super(msg, code, headers)
25
+ @rate_limit_limit = headers["ratelimit-limit"]&.to_i
26
+ @rate_limit_remaining = headers["ratelimit-remaining"]&.to_i
27
+ @rate_limit_reset = headers["ratelimit-reset"]&.to_i
28
+ @retry_after = headers["retry-after"]&.to_i
29
+ end
30
+ end
21
31
 
22
32
  # code 404
23
33
  NotFoundError = Class.new(ServerError)
@@ -31,9 +41,12 @@ module Resend
31
41
  500 => Resend::Error::InternalServerError
32
42
  }.freeze
33
43
 
34
- def initialize(msg, code = nil)
44
+ attr_reader :headers
45
+
46
+ def initialize(msg, code = nil, headers = {})
35
47
  super(msg)
36
48
  @code = code
49
+ @headers = headers
37
50
  end
38
51
  end
39
52
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Resend
4
+ # Helper class for building paginated query strings
5
+ class PaginationHelper
6
+ class << self
7
+ # Builds a paginated path with query parameters
8
+ #
9
+ # @param base_path [String] the base API path
10
+ # @param query_params [Hash] optional pagination parameters
11
+ # @option query_params [Integer] :limit Number of items to retrieve (max 100)
12
+ # @option query_params [String] :after ID after which to retrieve more items
13
+ # @option query_params [String] :before ID before which to retrieve more items
14
+ # @return [String] the path with query parameters
15
+ def build_paginated_path(base_path, query_params = nil)
16
+ return base_path if query_params.nil? || query_params.empty?
17
+
18
+ # Filter out nil values and convert to string keys
19
+ filtered_params = query_params.compact.transform_keys(&:to_s)
20
+
21
+ # Build query string
22
+ query_string = filtered_params.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join("&")
23
+
24
+ return base_path if query_string.empty?
25
+
26
+ "#{base_path}?#{query_string}"
27
+ end
28
+ end
29
+ end
30
+ end
@@ -25,38 +25,56 @@ module Resend
25
25
  }
26
26
 
27
27
  set_idempotency_key
28
+ set_batch_validation
28
29
  end
29
30
 
30
31
  # Performs the HTTP call
31
32
  def perform
32
- options = {
33
- headers: @headers
34
- }
35
- options[:body] = @body.to_json unless @body.empty?
36
-
33
+ options = build_request_options
37
34
  resp = HTTParty.send(@verb.to_sym, "#{BASE_URL}#{@path}", options)
38
35
 
39
36
  check_json!(resp)
40
-
41
- resp.transform_keys!(&:to_sym) unless resp.body.empty?
42
- handle_error!(resp) if resp[:statusCode] && (resp[:statusCode] != 200 || resp[:statusCode] != 201)
43
- resp
37
+ process_response(resp)
44
38
  end
45
39
 
46
40
  def handle_error!(resp)
47
41
  code = resp[:statusCode]
48
42
  body = resp[:message]
43
+ headers = resp.respond_to?(:headers) ? resp.headers : (resp[:headers] || {})
49
44
 
50
45
  # get error from the known list of errors
51
- error = Resend::Error::ERRORS[code]
52
- raise(error.new(body, code)) if error
53
-
54
- # Raise generic Resend error when the error code is not part of the known errors
55
- raise Resend::Error.new(body, code)
46
+ error_class = Resend::Error::ERRORS[code] || Resend::Error
47
+ raise error_class.new(body, code, headers)
56
48
  end
57
49
 
58
50
  private
59
51
 
52
+ def build_request_options
53
+ options = { headers: @headers }
54
+
55
+ if get_request_with_query?
56
+ options[:query] = @body
57
+ elsif !@body.empty?
58
+ options[:body] = @body.to_json
59
+ end
60
+
61
+ options
62
+ end
63
+
64
+ def get_request_with_query?
65
+ @verb.downcase == "get" && !@body.empty?
66
+ end
67
+
68
+ def process_response(resp)
69
+ resp.transform_keys!(&:to_sym) unless resp.body.empty?
70
+ handle_error!(resp) if error_response?(resp)
71
+ resp
72
+ end
73
+
74
+ def error_response?(resp)
75
+ resp[:statusCode] && (resp[:statusCode] != 200 && resp[:statusCode] != 201)
76
+ end
77
+
60
78
  def set_idempotency_key
61
79
  # Only set idempotency key if the verb is POST for now.
62
80
  #
@@ -66,6 +84,14 @@ module Resend
66
84
  end
67
85
  end
68
86
 
87
+ def set_batch_validation
88
+ # Set x-batch-validation header for batch emails
89
+ # Supported values: 'strict' (default) or 'permissive'
90
+ if @path == "emails/batch" && @options[:batch_validation]
91
+ @headers["x-batch-validation"] = @options[:batch_validation]
92
+ end
93
+ end
94
+
69
95
  def check_json!(resp)
70
96
  if resp.body.is_a?(Hash)
71
97
  JSON.parse(resp.body.to_json)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Resend
4
- VERSION = "0.24.0"
4
+ VERSION = "0.26.0"
5
5
  end
data/lib/resend.rb CHANGED
@@ -6,9 +6,11 @@ require "resend/version"
6
6
  # Utils
7
7
  require "httparty"
8
8
  require "json"
9
+ require "cgi"
9
10
  require "resend/errors"
10
11
  require "resend/client"
11
12
  require "resend/request"
13
+ require "resend/pagination_helper"
12
14
 
13
15
  # API Operations
14
16
  require "resend/audiences"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: resend
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.24.0
4
+ version: 0.26.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Derich Pacheco
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-08-13 00:00:00.000000000 Z
11
+ date: 2025-10-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httparty
@@ -56,6 +56,7 @@ files:
56
56
  - lib/resend/emails.rb
57
57
  - lib/resend/errors.rb
58
58
  - lib/resend/mailer.rb
59
+ - lib/resend/pagination_helper.rb
59
60
  - lib/resend/railtie.rb
60
61
  - lib/resend/request.rb
61
62
  - lib/resend/version.rb