resend 0.25.0 → 0.27.0.alpha.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 58501f77e4e315fec8185ee1a1b74942d0daa553ba4d13275e8efb0729780225
4
- data.tar.gz: ee0215aa0aa03241b817625736e26e99ff9c6194e19ff304b1cd0f12824bcd50
3
+ metadata.gz: c2285e905778c2a5332f4adc94ac29f229ed02b62645c4e074febd95202d56cc
4
+ data.tar.gz: 978ae7cf38ac0906c40d8af4b55aa5f0602a929bed2bc543b29500b157a94eaf
5
5
  SHA512:
6
- metadata.gz: f37730c78b2579ac2bf6b739504a20d6e6472430c4614a9ec22cedb134d5f19f440106f2f2c9d615eff4198e8be19292d10953a8af1e463a1465dc2051f68d76
7
- data.tar.gz: 68e4d7b0dc34700c1ddfee321f466f5a5f49319049cc2d54cb53a69063478941afab35907f9c66085caf56b96247bdf8da461117d677bdcae2898c8335cd019e
6
+ metadata.gz: 6c338e6d9b06847278ba7cd9bbd146f3d0d8012d5c614418a6ef9066c931cb73b4a270a407e74dfba7e6a5a27944c1996537c720d7108d6a838f5003a2aa2b7f
7
+ data.tar.gz: 396fdd53a1e21840dcbd996735a8570ec7e5353a87b4ce86ccb311f9dfcdd2cfa0928a0883b0727423ed39d8f5bf30a6b7afa45e60743191fd29ca593307f3e9
@@ -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
 
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Resend
4
+ module Attachments
5
+ # Module for receiving email attachments API operations
6
+ module Receiving
7
+ class << self
8
+ # Retrieve a single attachment from a received 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
+ # Resend::Attachments::Receiving.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
+ path = "emails/receiving/#{email_id}/attachments/#{attachment_id}"
25
+ Resend::Request.new(path, {}, "get").perform
26
+ end
27
+
28
+ # List attachments from a received email with optional pagination
29
+ #
30
+ # @param params [Hash] Parameters for listing attachments
31
+ # @option params [String] :email_id The email ID (required)
32
+ # @option params [Integer] :limit Maximum number of attachments to return (1-100)
33
+ # @option params [String] :after Cursor for pagination (newer attachments)
34
+ # @option params [String] :before Cursor for pagination (older attachments)
35
+ # @return [Hash] List of attachments with pagination info
36
+ #
37
+ # @example List all attachments
38
+ # Resend::Attachments::Receiving.list(
39
+ # email_id: "4ef9a417-02e9-4d39-ad75-9611e0fcc33c"
40
+ # )
41
+ #
42
+ # @example List with custom limit
43
+ # Resend::Attachments::Receiving.list(
44
+ # email_id: "4ef9a417-02e9-4d39-ad75-9611e0fcc33c",
45
+ # limit: 50
46
+ # )
47
+ #
48
+ # @example List with pagination
49
+ # Resend::Attachments::Receiving.list(
50
+ # email_id: "4ef9a417-02e9-4d39-ad75-9611e0fcc33c",
51
+ # limit: 20,
52
+ # after: "attachment_id_123"
53
+ # )
54
+ def list(params = {})
55
+ email_id = params[:email_id]
56
+ path = "emails/receiving/#{email_id}/attachments"
57
+
58
+ # Build query parameters, filtering out nil values
59
+ query_params = {}
60
+ query_params[:limit] = params[:limit] if params[:limit]
61
+ query_params[:after] = params[:after] if params[:after]
62
+ query_params[:before] = params[:before] if params[:before]
63
+
64
+ Resend::Request.new(path, query_params, "get").perform
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -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
 
@@ -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
 
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Resend
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
+ # Resend::Emails::Receiving.get("4ef9a417-02e9-4d39-ad75-9611e0fcc33c")
15
+ def get(email_id = "")
16
+ path = "emails/receiving/#{email_id}"
17
+ Resend::Request.new(path, {}, "get").perform
18
+ end
19
+
20
+ # List received emails with optional pagination
21
+ #
22
+ # @param params [Hash] Optional parameters for pagination
23
+ # @option params [Integer] :limit Maximum number of emails to return (1-100)
24
+ # @option params [String] :after Cursor for pagination (newer emails)
25
+ # @option params [String] :before Cursor for pagination (older emails)
26
+ # @return [Hash] List of received emails with pagination info
27
+ #
28
+ # @example List all received emails
29
+ # Resend::Emails::Receiving.list
30
+ #
31
+ # @example List with custom limit
32
+ # Resend::Emails::Receiving.list(limit: 50)
33
+ #
34
+ # @example List with pagination
35
+ # Resend::Emails::Receiving.list(limit: 20, after: "email_id_123")
36
+ def list(params = {})
37
+ path = Resend::PaginationHelper.build_paginated_path("emails/receiving", params)
38
+ Resend::Request.new(path, {}, "get").perform
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
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
@@ -30,34 +30,51 @@ module Resend
30
30
 
31
31
  # Performs the HTTP call
32
32
  def perform
33
- options = {
34
- headers: @headers
35
- }
36
- options[:body] = @body.to_json unless @body.empty?
37
-
33
+ options = build_request_options
38
34
  resp = HTTParty.send(@verb.to_sym, "#{BASE_URL}#{@path}", options)
39
35
 
40
36
  check_json!(resp)
41
-
42
- resp.transform_keys!(&:to_sym) unless resp.body.empty?
43
- handle_error!(resp) if resp[:statusCode] && (resp[:statusCode] != 200 || resp[:statusCode] != 201)
44
- resp
37
+ process_response(resp)
45
38
  end
46
39
 
47
40
  def handle_error!(resp)
48
41
  code = resp[:statusCode]
49
42
  body = resp[:message]
43
+ headers = resp.respond_to?(:headers) ? resp.headers : (resp[:headers] || {})
50
44
 
51
45
  # get error from the known list of errors
52
- error = Resend::Error::ERRORS[code]
53
- raise(error.new(body, code)) if error
54
-
55
- # Raise generic Resend error when the error code is not part of the known errors
56
- raise Resend::Error.new(body, code)
46
+ error_class = Resend::Error::ERRORS[code] || Resend::Error
47
+ raise error_class.new(body, code, headers)
57
48
  end
58
49
 
59
50
  private
60
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
+
61
78
  def set_idempotency_key
62
79
  # Only set idempotency key if the verb is POST for now.
63
80
  #
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Resend
4
+ # Topics api wrapper
5
+ module Topics
6
+ class << self
7
+ # https://resend.com/docs/api-reference/topics/create-topic
8
+ def create(params = {})
9
+ path = "topics"
10
+ Resend::Request.new(path, params, "post").perform
11
+ end
12
+
13
+ # https://resend.com/docs/api-reference/topics/get-topic
14
+ def get(topic_id = "")
15
+ path = "topics/#{topic_id}"
16
+ Resend::Request.new(path, {}, "get").perform
17
+ end
18
+
19
+ # https://resend.com/docs/api-reference/topics/update-topic
20
+ def update(params = {})
21
+ path = "topics/#{params[:topic_id]}"
22
+ Resend::Request.new(path, params, "patch").perform
23
+ end
24
+
25
+ # https://resend.com/docs/api-reference/topics/list-topics
26
+ def list(params = {})
27
+ path = Resend::PaginationHelper.build_paginated_path("topics", params)
28
+ Resend::Request.new(path, {}, "get").perform
29
+ end
30
+
31
+ # https://resend.com/docs/api-reference/topics/delete-topic
32
+ def remove(topic_id = "")
33
+ path = "topics/#{topic_id}"
34
+ Resend::Request.new(path, {}, "delete").perform
35
+ end
36
+ end
37
+ end
38
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Resend
4
- VERSION = "0.25.0"
4
+ VERSION = "0.27.0.alpha.1"
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"
@@ -18,6 +20,9 @@ require "resend/batch"
18
20
  require "resend/contacts"
19
21
  require "resend/domains"
20
22
  require "resend/emails"
23
+ require "resend/emails/receiving"
24
+ require "resend/attachments/receiving"
25
+ require "resend/topics"
21
26
 
22
27
  # Rails
23
28
  require "resend/railtie" if defined?(Rails) && defined?(ActionMailer)
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.25.0
4
+ version: 0.27.0.alpha.1
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-09-20 00:00:00.000000000 Z
11
+ date: 2025-10-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httparty
@@ -47,6 +47,7 @@ files:
47
47
  - README.md
48
48
  - lib/resend.rb
49
49
  - lib/resend/api_keys.rb
50
+ - lib/resend/attachments/receiving.rb
50
51
  - lib/resend/audiences.rb
51
52
  - lib/resend/batch.rb
52
53
  - lib/resend/broadcasts.rb
@@ -54,10 +55,13 @@ files:
54
55
  - lib/resend/contacts.rb
55
56
  - lib/resend/domains.rb
56
57
  - lib/resend/emails.rb
58
+ - lib/resend/emails/receiving.rb
57
59
  - lib/resend/errors.rb
58
60
  - lib/resend/mailer.rb
61
+ - lib/resend/pagination_helper.rb
59
62
  - lib/resend/railtie.rb
60
63
  - lib/resend/request.rb
64
+ - lib/resend/topics.rb
61
65
  - lib/resend/version.rb
62
66
  homepage: https://github.com/resend/resend-ruby
63
67
  licenses:
@@ -74,9 +78,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
74
78
  version: '2.6'
75
79
  required_rubygems_version: !ruby/object:Gem::Requirement
76
80
  requirements:
77
- - - ">="
81
+ - - ">"
78
82
  - !ruby/object:Gem::Version
79
- version: '0'
83
+ version: 1.3.1
80
84
  requirements: []
81
85
  rubygems_version: 3.4.10
82
86
  signing_key: