resend 0.27.0 → 1.6.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 +4 -4
- data/CHANGELOG.md +138 -0
- data/lib/resend/automations/runs.rb +22 -0
- data/lib/resend/automations.rb +41 -0
- data/lib/resend/broadcasts.rb +13 -0
- data/lib/resend/contact_properties.rb +106 -0
- data/lib/resend/contacts/imports.rb +64 -0
- data/lib/resend/contacts/segments.rb +66 -0
- data/lib/resend/contacts/topics.rb +80 -0
- data/lib/resend/contacts.rb +83 -21
- data/lib/resend/domains/claims.rb +56 -0
- data/lib/resend/emails/attachments.rb +67 -0
- data/lib/resend/emails/receiving/attachments.rb +69 -0
- data/lib/resend/emails/receiving.rb +51 -0
- data/lib/resend/emails.rb +0 -7
- data/lib/resend/events.rb +46 -0
- data/lib/resend/logs.rb +20 -0
- data/lib/resend/mailer.rb +1 -1
- data/lib/resend/multipart_request.rb +67 -0
- data/lib/resend/oauth_grants.rb +20 -0
- data/lib/resend/request.rb +23 -10
- data/lib/resend/response.rb +141 -0
- data/lib/resend/segments.rb +32 -0
- data/lib/resend/templates.rb +50 -0
- data/lib/resend/topics.rb +38 -0
- data/lib/resend/version.rb +1 -1
- data/lib/resend.rb +21 -1
- metadata +30 -16
- data/lib/resend/audiences.rb +0 -32
data/lib/resend/contacts.rb
CHANGED
|
@@ -6,42 +6,96 @@ module Resend
|
|
|
6
6
|
class << self
|
|
7
7
|
# https://resend.com/docs/api-reference/contacts/create-contact
|
|
8
8
|
def create(params)
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
if params[:audience_id]
|
|
10
|
+
path = "audiences/#{params[:audience_id]}/contacts"
|
|
11
|
+
# Audience-scoped contacts don't support properties
|
|
12
|
+
payload = params.reject { |key, _| key == :properties }
|
|
13
|
+
else
|
|
14
|
+
path = "contacts"
|
|
15
|
+
payload = params
|
|
16
|
+
end
|
|
17
|
+
Resend::Request.new(path, payload, "post").perform
|
|
11
18
|
end
|
|
12
19
|
|
|
13
20
|
#
|
|
14
|
-
# Retrieves a contact
|
|
21
|
+
# Retrieves a contact
|
|
15
22
|
#
|
|
16
|
-
# @param
|
|
17
|
-
# @
|
|
23
|
+
# @param params [Hash] the parameters
|
|
24
|
+
# @option params [String] :id either the contact id or contact's email (required)
|
|
25
|
+
# @option params [String] :audience_id optional audience id to scope the operation
|
|
26
|
+
#
|
|
27
|
+
# @example Get contact by ID
|
|
28
|
+
# Resend::Contacts.get(id: "contact_123")
|
|
29
|
+
#
|
|
30
|
+
# @example Get contact scoped to an audience
|
|
31
|
+
# Resend::Contacts.get(id: "contact_123", audience_id: "aud_456")
|
|
18
32
|
#
|
|
19
33
|
# https://resend.com/docs/api-reference/contacts/get-contact
|
|
20
|
-
def get(
|
|
21
|
-
|
|
34
|
+
def get(params = {})
|
|
35
|
+
raise ArgumentError, "Missing `id` or `email` field" if params[:id].nil? && params[:email].nil?
|
|
36
|
+
|
|
37
|
+
audience_id = params[:audience_id]
|
|
38
|
+
contact_id = params[:id] || params[:email]
|
|
39
|
+
path = if audience_id
|
|
40
|
+
"audiences/#{audience_id}/contacts/#{contact_id}"
|
|
41
|
+
else
|
|
42
|
+
"contacts/#{contact_id}"
|
|
43
|
+
end
|
|
22
44
|
Resend::Request.new(path, {}, "get").perform
|
|
23
45
|
end
|
|
24
46
|
|
|
25
47
|
#
|
|
26
|
-
# List contacts
|
|
48
|
+
# List contacts
|
|
49
|
+
#
|
|
50
|
+
# @param params [Hash] optional parameters including pagination
|
|
51
|
+
# @option params [String] :audience_id optional audience id to scope the operation
|
|
52
|
+
# @option params [Integer] :limit number of records to return
|
|
53
|
+
# @option params [String] :cursor pagination cursor
|
|
54
|
+
#
|
|
55
|
+
# @example List all contacts
|
|
56
|
+
# Resend::Contacts.list
|
|
57
|
+
#
|
|
58
|
+
# @example List contacts with pagination
|
|
59
|
+
# Resend::Contacts.list(limit: 10)
|
|
60
|
+
#
|
|
61
|
+
# @example List contacts scoped to an audience
|
|
62
|
+
# Resend::Contacts.list(audience_id: "aud_456", limit: 10)
|
|
27
63
|
#
|
|
28
|
-
# @param audience_id [String] the audience id
|
|
29
|
-
# @param params [Hash] optional pagination parameters
|
|
30
64
|
# https://resend.com/docs/api-reference/contacts/list-contacts
|
|
31
|
-
def list(
|
|
32
|
-
|
|
65
|
+
def list(params = {})
|
|
66
|
+
audience_id = params[:audience_id]
|
|
67
|
+
path = if audience_id
|
|
68
|
+
Resend::PaginationHelper.build_paginated_path("audiences/#{audience_id}/contacts", params)
|
|
69
|
+
else
|
|
70
|
+
Resend::PaginationHelper.build_paginated_path("contacts", params)
|
|
71
|
+
end
|
|
33
72
|
Resend::Request.new(path, {}, "get").perform
|
|
34
73
|
end
|
|
35
74
|
|
|
36
75
|
#
|
|
37
|
-
# Remove a contact
|
|
76
|
+
# Remove a contact
|
|
77
|
+
#
|
|
78
|
+
# @param params [Hash] the parameters
|
|
79
|
+
# @option params [String] :id either the contact id or contact email (required)
|
|
80
|
+
# @option params [String] :audience_id optional audience id to scope the operation
|
|
38
81
|
#
|
|
39
|
-
# @
|
|
40
|
-
#
|
|
82
|
+
# @example Remove contact by ID
|
|
83
|
+
# Resend::Contacts.remove(id: "contact_123")
|
|
41
84
|
#
|
|
42
|
-
#
|
|
43
|
-
|
|
44
|
-
|
|
85
|
+
# @example Remove contact scoped to an audience
|
|
86
|
+
# Resend::Contacts.remove(id: "contact_123", audience_id: "aud_456")
|
|
87
|
+
#
|
|
88
|
+
# https://resend.com/docs/api-reference/contacts/delete-contact
|
|
89
|
+
def remove(params = {})
|
|
90
|
+
raise ArgumentError, "Missing `id` or `email` field" if params[:id].nil? && params[:email].nil?
|
|
91
|
+
|
|
92
|
+
audience_id = params[:audience_id]
|
|
93
|
+
contact_id = params[:id] || params[:email]
|
|
94
|
+
path = if audience_id
|
|
95
|
+
"audiences/#{audience_id}/contacts/#{contact_id}"
|
|
96
|
+
else
|
|
97
|
+
"contacts/#{contact_id}"
|
|
98
|
+
end
|
|
45
99
|
Resend::Request.new(path, {}, "delete").perform
|
|
46
100
|
end
|
|
47
101
|
|
|
@@ -51,10 +105,18 @@ module Resend
|
|
|
51
105
|
# @param params [Hash] the contact params
|
|
52
106
|
# https://resend.com/docs/api-reference/contacts/update-contact
|
|
53
107
|
def update(params)
|
|
54
|
-
raise ArgumentError, "id or email
|
|
108
|
+
raise ArgumentError, "Missing `id` or `email` field" if params[:id].nil? && params[:email].nil?
|
|
55
109
|
|
|
56
|
-
|
|
57
|
-
|
|
110
|
+
contact_id = params[:id] || params[:email]
|
|
111
|
+
if params[:audience_id]
|
|
112
|
+
path = "audiences/#{params[:audience_id]}/contacts/#{contact_id}"
|
|
113
|
+
# Audience-scoped contacts don't support properties
|
|
114
|
+
payload = params.reject { |key, _| key == :properties }
|
|
115
|
+
else
|
|
116
|
+
path = "contacts/#{contact_id}"
|
|
117
|
+
payload = params
|
|
118
|
+
end
|
|
119
|
+
Resend::Request.new(path, payload, "patch").perform
|
|
58
120
|
end
|
|
59
121
|
end
|
|
60
122
|
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Resend
|
|
4
|
+
module Domains
|
|
5
|
+
# Domain Claims API wrapper
|
|
6
|
+
module Claims
|
|
7
|
+
class << self
|
|
8
|
+
#
|
|
9
|
+
# Start a claim for a domain that another Resend account has already verified.
|
|
10
|
+
# The domain is recreated under your account with fresh DKIM keys, so the
|
|
11
|
+
# previous account's DNS records cannot be reused. Returns a TXT record to add
|
|
12
|
+
# to your DNS to prove ownership. Uses the same request body as creating a domain.
|
|
13
|
+
#
|
|
14
|
+
# @param params [Hash] the parameters
|
|
15
|
+
# @option params [String] :name the name of the domain you want to claim (required)
|
|
16
|
+
# @option params [String] :region the region where emails will be sent from.
|
|
17
|
+
# Possible values: 'us-east-1' | 'eu-west-1' | 'sa-east-1' | 'ap-northeast-1'
|
|
18
|
+
# @option params [String] :custom_return_path subdomain for the Return-Path address (default 'send')
|
|
19
|
+
# @option params [String] :tracking_subdomain the custom subdomain used for click and open tracking links
|
|
20
|
+
# @option params [Boolean] :click_tracking track clicks within the body of each HTML email
|
|
21
|
+
# @option params [Boolean] :open_tracking track the open rate of each email
|
|
22
|
+
#
|
|
23
|
+
# https://resend.com/docs/api-reference/domains/claim-domain
|
|
24
|
+
def create(params)
|
|
25
|
+
path = "domains/claim"
|
|
26
|
+
Resend::Request.new(path, params, "post").perform
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
#
|
|
30
|
+
# Retrieve the latest claim for the placeholder domain created by the claim.
|
|
31
|
+
#
|
|
32
|
+
# @param domain_id [String] the ID of the placeholder domain created by the claim
|
|
33
|
+
#
|
|
34
|
+
# https://resend.com/docs/api-reference/domains/get-domain-claim
|
|
35
|
+
def get(domain_id = "")
|
|
36
|
+
path = "domains/#{domain_id}/claim"
|
|
37
|
+
Resend::Request.new(path, {}, "get").perform
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
#
|
|
41
|
+
# Trigger asynchronous DNS verification and ownership transfer for a domain claim.
|
|
42
|
+
# The claim stays 'pending' while verification runs; poll `get` for status. Once
|
|
43
|
+
# 'completed', the transferred domain has new DKIM records that must be added to
|
|
44
|
+
# DNS and verified via Resend::Domains.verify.
|
|
45
|
+
#
|
|
46
|
+
# @param domain_id [String] the ID of the placeholder domain created by the claim
|
|
47
|
+
#
|
|
48
|
+
# https://resend.com/docs/api-reference/domains/verify-domain-claim
|
|
49
|
+
def verify(domain_id = "")
|
|
50
|
+
path = "domains/#{domain_id}/claim/verify"
|
|
51
|
+
Resend::Request.new(path, {}, "post").perform
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Resend
|
|
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
|
+
# Resend::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
|
+
path = "emails/#{email_id}/attachments/#{attachment_id}"
|
|
25
|
+
Resend::Request.new(path, {}, "get").perform
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# List attachments from a sent 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::Emails::Attachments.list(
|
|
39
|
+
# email_id: "4ef9a417-02e9-4d39-ad75-9611e0fcc33c"
|
|
40
|
+
# )
|
|
41
|
+
#
|
|
42
|
+
# @example List with custom limit
|
|
43
|
+
# Resend::Emails::Attachments.list(
|
|
44
|
+
# email_id: "4ef9a417-02e9-4d39-ad75-9611e0fcc33c",
|
|
45
|
+
# limit: 50
|
|
46
|
+
# )
|
|
47
|
+
#
|
|
48
|
+
# @example List with pagination
|
|
49
|
+
# Resend::Emails::Attachments.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
|
+
base_path = "emails/#{email_id}/attachments"
|
|
57
|
+
|
|
58
|
+
# Extract pagination parameters
|
|
59
|
+
pagination_params = params.slice(:limit, :after, :before)
|
|
60
|
+
|
|
61
|
+
path = Resend::PaginationHelper.build_paginated_path(base_path, pagination_params)
|
|
62
|
+
Resend::Request.new(path, {}, "get").perform
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Resend
|
|
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
|
+
# Resend::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
|
+
path = "emails/receiving/#{email_id}/attachments/#{attachment_id}"
|
|
26
|
+
Resend::Request.new(path, {}, "get").perform
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# List attachments from a received email with optional pagination
|
|
30
|
+
#
|
|
31
|
+
# @param params [Hash] Parameters for listing attachments
|
|
32
|
+
# @option params [String] :email_id The email ID (required)
|
|
33
|
+
# @option params [Integer] :limit Maximum number of attachments to return (1-100)
|
|
34
|
+
# @option params [String] :after Cursor for pagination (newer attachments)
|
|
35
|
+
# @option params [String] :before Cursor for pagination (older attachments)
|
|
36
|
+
# @return [Hash] List of attachments with pagination info
|
|
37
|
+
#
|
|
38
|
+
# @example List all attachments
|
|
39
|
+
# Resend::Emails::Receiving::Attachments.list(
|
|
40
|
+
# email_id: "4ef9a417-02e9-4d39-ad75-9611e0fcc33c"
|
|
41
|
+
# )
|
|
42
|
+
#
|
|
43
|
+
# @example List with custom limit
|
|
44
|
+
# Resend::Emails::Receiving::Attachments.list(
|
|
45
|
+
# email_id: "4ef9a417-02e9-4d39-ad75-9611e0fcc33c",
|
|
46
|
+
# limit: 50
|
|
47
|
+
# )
|
|
48
|
+
#
|
|
49
|
+
# @example List with pagination
|
|
50
|
+
# Resend::Emails::Receiving::Attachments.list(
|
|
51
|
+
# email_id: "4ef9a417-02e9-4d39-ad75-9611e0fcc33c",
|
|
52
|
+
# limit: 20,
|
|
53
|
+
# after: "attachment_id_123"
|
|
54
|
+
# )
|
|
55
|
+
def list(params = {})
|
|
56
|
+
email_id = params[:email_id]
|
|
57
|
+
base_path = "emails/receiving/#{email_id}/attachments"
|
|
58
|
+
|
|
59
|
+
# Extract pagination parameters
|
|
60
|
+
pagination_params = params.slice(:limit, :after, :before)
|
|
61
|
+
|
|
62
|
+
path = Resend::PaginationHelper.build_paginated_path(base_path, pagination_params)
|
|
63
|
+
Resend::Request.new(path, {}, "get").perform
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
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
|
+
# @param params [Hash] Optional query parameters
|
|
12
|
+
# @option params [String] :html_format Format of the HTML content (e.g., "sanitized", "raw")
|
|
13
|
+
# @return [Hash] The received email object
|
|
14
|
+
#
|
|
15
|
+
# @example
|
|
16
|
+
# Resend::Emails::Receiving.get("4ef9a417-02e9-4d39-ad75-9611e0fcc33c")
|
|
17
|
+
#
|
|
18
|
+
# @example With html_format
|
|
19
|
+
# Resend::Emails::Receiving.get("4ef9a417-02e9-4d39-ad75-9611e0fcc33c", html_format: "sanitized")
|
|
20
|
+
def get(email_id = "", params = {})
|
|
21
|
+
path = "emails/receiving/#{email_id}"
|
|
22
|
+
|
|
23
|
+
path += "?html_format=#{CGI.escape(params[:html_format].to_s)}" if params[:html_format]
|
|
24
|
+
|
|
25
|
+
Resend::Request.new(path, {}, "get").perform
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# List received emails with optional pagination
|
|
29
|
+
#
|
|
30
|
+
# @param params [Hash] Optional parameters for pagination
|
|
31
|
+
# @option params [Integer] :limit Maximum number of emails to return (1-100)
|
|
32
|
+
# @option params [String] :after Cursor for pagination (newer emails)
|
|
33
|
+
# @option params [String] :before Cursor for pagination (older emails)
|
|
34
|
+
# @return [Hash] List of received emails with pagination info
|
|
35
|
+
#
|
|
36
|
+
# @example List all received emails
|
|
37
|
+
# Resend::Emails::Receiving.list
|
|
38
|
+
#
|
|
39
|
+
# @example List with custom limit
|
|
40
|
+
# Resend::Emails::Receiving.list(limit: 50)
|
|
41
|
+
#
|
|
42
|
+
# @example List with pagination
|
|
43
|
+
# Resend::Emails::Receiving.list(limit: 20, after: "email_id_123")
|
|
44
|
+
def list(params = {})
|
|
45
|
+
path = Resend::PaginationHelper.build_paginated_path("emails/receiving", params)
|
|
46
|
+
Resend::Request.new(path, {}, "get").perform
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
data/lib/resend/emails.rb
CHANGED
|
@@ -51,12 +51,5 @@ module Resend
|
|
|
51
51
|
Resend::Request.new(path, query_params, "get").perform
|
|
52
52
|
end
|
|
53
53
|
end
|
|
54
|
-
|
|
55
|
-
# This method is kept here for backwards compatibility
|
|
56
|
-
# Use Resend::Emails.send instead.
|
|
57
|
-
def send_email(params)
|
|
58
|
-
warn "[DEPRECATION] `send_email` is deprecated. Please use `Resend::Emails.send` instead."
|
|
59
|
-
Resend::Emails.send(params)
|
|
60
|
-
end
|
|
61
54
|
end
|
|
62
55
|
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Resend
|
|
4
|
+
# events api wrapper
|
|
5
|
+
module Events
|
|
6
|
+
class << self
|
|
7
|
+
# https://resend.com/docs/api-reference/events/create-event
|
|
8
|
+
def create(params = {})
|
|
9
|
+
Resend::Request.new("events", params, "post").perform
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# https://resend.com/docs/api-reference/events/get-event
|
|
13
|
+
# identifier can be a UUID or an event name (e.g. "user.signed_up")
|
|
14
|
+
def get(identifier = "")
|
|
15
|
+
path = "events/#{CGI.escape(identifier.to_s)}"
|
|
16
|
+
Resend::Request.new(path, {}, "get").perform
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# https://resend.com/docs/api-reference/events/update-event
|
|
20
|
+
def update(params = {})
|
|
21
|
+
identifier = params[:identifier]
|
|
22
|
+
path = "events/#{CGI.escape(identifier.to_s)}"
|
|
23
|
+
payload = params.reject { |k, _| k == :identifier }
|
|
24
|
+
Resend::Request.new(path, payload, "patch").perform
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# https://resend.com/docs/api-reference/events/delete-event
|
|
28
|
+
# identifier can be a UUID or an event name (e.g. "user.signed_up")
|
|
29
|
+
def remove(identifier = "")
|
|
30
|
+
path = "events/#{CGI.escape(identifier.to_s)}"
|
|
31
|
+
Resend::Request.new(path, {}, "delete").perform
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# https://resend.com/docs/api-reference/events/send-event
|
|
35
|
+
def send(params = {})
|
|
36
|
+
Resend::Request.new("events/send", params, "post").perform
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# https://resend.com/docs/api-reference/events/list-events
|
|
40
|
+
def list(params = {})
|
|
41
|
+
path = Resend::PaginationHelper.build_paginated_path("events", params)
|
|
42
|
+
Resend::Request.new(path, {}, "get").perform
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
data/lib/resend/logs.rb
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Resend
|
|
4
|
+
# logs api wrapper
|
|
5
|
+
module Logs
|
|
6
|
+
class << self
|
|
7
|
+
# https://resend.com/docs/api-reference/logs/retrieve-log
|
|
8
|
+
def get(log_id = "")
|
|
9
|
+
path = "logs/#{log_id}"
|
|
10
|
+
Resend::Request.new(path, {}, "get").perform
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# https://resend.com/docs/api-reference/logs/list-logs
|
|
14
|
+
def list(params = {})
|
|
15
|
+
path = Resend::PaginationHelper.build_paginated_path("logs", params)
|
|
16
|
+
Resend::Request.new(path, {}, "get").perform
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
data/lib/resend/mailer.rb
CHANGED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
5
|
+
module Resend
|
|
6
|
+
# Handles multipart/form-data requests (file uploads).
|
|
7
|
+
# Builds the multipart body directly instead of relying on HTTParty's
|
|
8
|
+
# duck-typing file detection, which requires a :path method.
|
|
9
|
+
class MultipartRequest < Request
|
|
10
|
+
NEWLINE = "\r\n"
|
|
11
|
+
private_constant :NEWLINE
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def build_request_options
|
|
16
|
+
boundary = SecureRandom.hex(16)
|
|
17
|
+
headers = @headers.merge("Content-Type" => "multipart/form-data; boundary=#{boundary}")
|
|
18
|
+
{ headers: headers, body: build_multipart_body(boundary) }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def build_multipart_body(boundary)
|
|
22
|
+
body = "".b
|
|
23
|
+
|
|
24
|
+
body << part(boundary, "file", read_file(@body[:file]), filename: "import.csv", content_type: "text/csv")
|
|
25
|
+
optional_fields.each { |name, value| body << part(boundary, name, value) }
|
|
26
|
+
body << "--#{boundary}--#{NEWLINE}".b
|
|
27
|
+
|
|
28
|
+
body
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def part(boundary, name, value, filename: nil, content_type: nil)
|
|
32
|
+
disposition = %(Content-Disposition: form-data; name="#{name}")
|
|
33
|
+
disposition += %(; filename="#{filename}") if filename
|
|
34
|
+
|
|
35
|
+
header = "--#{boundary}#{NEWLINE}#{disposition}#{NEWLINE}"
|
|
36
|
+
header += "Content-Type: #{content_type}#{NEWLINE}" if content_type
|
|
37
|
+
header += NEWLINE
|
|
38
|
+
|
|
39
|
+
header.b + value.to_s.b + NEWLINE.b
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def read_file(file_data)
|
|
43
|
+
if file_data.respond_to?(:read)
|
|
44
|
+
content = file_data.read
|
|
45
|
+
file_data.rewind if file_data.respond_to?(:rewind)
|
|
46
|
+
content.b
|
|
47
|
+
else
|
|
48
|
+
file_data.to_s.b
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def optional_fields
|
|
53
|
+
{
|
|
54
|
+
column_map: @body[:column_map] && serialize_json(@body[:column_map]),
|
|
55
|
+
on_conflict: @body[:on_conflict],
|
|
56
|
+
segments: @body[:segments] && serialize_json(@body[:segments]),
|
|
57
|
+
topics: @body[:topics] && serialize_json(@body[:topics])
|
|
58
|
+
}.compact
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def serialize_json(value)
|
|
62
|
+
return value if value.is_a?(String)
|
|
63
|
+
|
|
64
|
+
value.to_json
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Resend
|
|
4
|
+
# oauth grants api wrapper
|
|
5
|
+
module OAuthGrants
|
|
6
|
+
class << self
|
|
7
|
+
# https://resend.com/docs/api-reference/oauth/list-grants
|
|
8
|
+
def list(params = {})
|
|
9
|
+
path = Resend::PaginationHelper.build_paginated_path("oauth/grants", params)
|
|
10
|
+
Resend::Request.new(path, {}, "get").perform
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# https://resend.com/docs/api-reference/oauth/revoke-grant
|
|
14
|
+
def revoke(oauth_grant_id = "")
|
|
15
|
+
path = "oauth/grants/#{oauth_grant_id}"
|
|
16
|
+
Resend::Request.new(path, {}, "delete").perform
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
data/lib/resend/request.rb
CHANGED
|
@@ -34,13 +34,16 @@ module Resend
|
|
|
34
34
|
resp = HTTParty.send(@verb.to_sym, "#{BASE_URL}#{@path}", options)
|
|
35
35
|
|
|
36
36
|
check_json!(resp)
|
|
37
|
-
process_response(resp)
|
|
37
|
+
data = process_response(resp)
|
|
38
|
+
headers = extract_headers(resp)
|
|
39
|
+
|
|
40
|
+
Resend::Response.new(data, headers)
|
|
38
41
|
end
|
|
39
42
|
|
|
40
|
-
def handle_error!(resp)
|
|
41
|
-
code =
|
|
42
|
-
body =
|
|
43
|
-
headers = resp.respond_to?(:headers) ? resp.headers : (
|
|
43
|
+
def handle_error!(data, resp = nil)
|
|
44
|
+
code = data[:statusCode]
|
|
45
|
+
body = data[:message]
|
|
46
|
+
headers = resp.respond_to?(:headers) ? resp.headers : (data[:headers] || {})
|
|
44
47
|
|
|
45
48
|
# get error from the known list of errors
|
|
46
49
|
error_class = Resend::Error::ERRORS[code] || Resend::Error
|
|
@@ -66,20 +69,23 @@ module Resend
|
|
|
66
69
|
end
|
|
67
70
|
|
|
68
71
|
def process_response(resp)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
+
# Extract the parsed data from HTTParty response or use the hash directly (for tests/mocks)
|
|
73
|
+
data = resp.respond_to?(:parsed_response) ? resp.parsed_response : resp
|
|
74
|
+
data ||= {}
|
|
75
|
+
data.transform_keys!(&:to_sym) unless resp.body.empty?
|
|
76
|
+
handle_error!(data, resp) if error_response?(data)
|
|
77
|
+
data
|
|
72
78
|
end
|
|
73
79
|
|
|
74
80
|
def error_response?(resp)
|
|
75
|
-
resp[:statusCode] &&
|
|
81
|
+
resp[:statusCode] && resp[:statusCode] != 200 && resp[:statusCode] != 201
|
|
76
82
|
end
|
|
77
83
|
|
|
78
84
|
def set_idempotency_key
|
|
79
85
|
# Only set idempotency key if the verb is POST for now.
|
|
80
86
|
#
|
|
81
87
|
# Does not set it if the idempotency_key is nil or empty
|
|
82
|
-
if @verb.downcase == "post" &&
|
|
88
|
+
if @verb.downcase == "post" && !@options[:idempotency_key].nil? && !@options[:idempotency_key].empty?
|
|
83
89
|
@headers["Idempotency-Key"] = @options[:idempotency_key]
|
|
84
90
|
end
|
|
85
91
|
end
|
|
@@ -101,5 +107,12 @@ module Resend
|
|
|
101
107
|
rescue JSON::ParserError, TypeError
|
|
102
108
|
raise Resend::Error::InternalServerError.new("Resend API returned an unexpected response", nil)
|
|
103
109
|
end
|
|
110
|
+
|
|
111
|
+
# Extract and normalize headers from the HTTParty response
|
|
112
|
+
def extract_headers(resp)
|
|
113
|
+
return {} unless resp.respond_to?(:headers)
|
|
114
|
+
|
|
115
|
+
resp.headers.to_h.transform_keys { |k| k.to_s.downcase }
|
|
116
|
+
end
|
|
104
117
|
end
|
|
105
118
|
end
|