resend 0.27.0 → 1.0.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 +4 -4
- data/CHANGELOG.md +131 -0
- data/lib/resend/broadcasts.rb +13 -0
- data/lib/resend/contact_properties.rb +106 -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/emails/attachments.rb +67 -0
- data/lib/resend/emails/receiving/attachments.rb +69 -0
- data/lib/resend/emails/receiving.rb +43 -0
- data/lib/resend/emails.rb +0 -7
- data/lib/resend/mailer.rb +1 -1
- 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 +13 -1
- metadata +18 -8
- data/lib/resend/audiences.rb +0 -32
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 67dadb5f7acb77801501030612149bf52b6c46626fd5630a233a77ab6fc552d7
|
|
4
|
+
data.tar.gz: 8842020484f59a566282b8ff1258bfeea02594274b2f117c548ae2ad49284ad2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d889989325170107795bed29b590b45382909a8b4812381c4fa330e8c94c6cf7d8c3be1474793c0b6a93c85fc3f0647fd868f5b540902cd9e6ca260821f3b5ec
|
|
7
|
+
data.tar.gz: 48ec677afd58504f02b4099fb1f4d926defa2aa64f05cb2d8e8e1c724b08f1d1b3950f79535ac89481a8f58f1b1884962c4e46247bbdb6b03e0364325822fab9
|
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/lib/resend/broadcasts.rb
CHANGED
|
@@ -5,13 +5,26 @@ module Resend
|
|
|
5
5
|
module Broadcasts
|
|
6
6
|
class << self
|
|
7
7
|
# https://resend.com/docs/api-reference/broadcasts/create-broadcast
|
|
8
|
+
# @note Supports both segment_id and audience_id. At least one is required.
|
|
9
|
+
# audience_id is deprecated - use segment_id instead.
|
|
10
|
+
# @note When send: true is passed, the broadcast is sent immediately instead of
|
|
11
|
+
# creating a draft. When using send: true, you can also include scheduled_at
|
|
12
|
+
# to schedule the broadcast. Passing scheduled_at without send: true is an error.
|
|
8
13
|
def create(params = {})
|
|
14
|
+
if params[:audience_id] && !params[:segment_id]
|
|
15
|
+
warn "[DEPRECATION] Using audience_id in broadcasts is deprecated. Use segment_id instead."
|
|
16
|
+
end
|
|
9
17
|
path = "broadcasts"
|
|
10
18
|
Resend::Request.new(path, params, "post").perform
|
|
11
19
|
end
|
|
12
20
|
|
|
13
21
|
# https://resend.com/docs/api-reference/broadcasts/update-broadcast
|
|
22
|
+
# @note Supports both segment_id and audience_id. At least one may be required.
|
|
23
|
+
# audience_id is deprecated - use segment_id instead.
|
|
14
24
|
def update(params = {})
|
|
25
|
+
if params[:audience_id] && !params[:segment_id]
|
|
26
|
+
warn "[DEPRECATION] Using audience_id in broadcasts is deprecated. Use segment_id instead."
|
|
27
|
+
end
|
|
15
28
|
path = "broadcasts/#{params[:broadcast_id]}"
|
|
16
29
|
Resend::Request.new(path, params, "patch").perform
|
|
17
30
|
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Resend
|
|
4
|
+
# Module for managing contact properties
|
|
5
|
+
#
|
|
6
|
+
# Contact properties allow you to store custom data about your contacts
|
|
7
|
+
module ContactProperties
|
|
8
|
+
class << self
|
|
9
|
+
# Create a custom property for your contacts
|
|
10
|
+
#
|
|
11
|
+
# @param params [Hash] Parameters for creating a contact property
|
|
12
|
+
# @option params [String] :key The property key (max 50 characters, alphanumeric and underscores only) (required)
|
|
13
|
+
# @option params [String] :type The property type ('string' or 'number') (required)
|
|
14
|
+
# @option params [String, Integer] :fallback_value The default value when property is not set (must match type)
|
|
15
|
+
#
|
|
16
|
+
# @return [Hash] Response containing the created contact property
|
|
17
|
+
#
|
|
18
|
+
# @example Create a string property
|
|
19
|
+
# Resend::ContactProperties.create({
|
|
20
|
+
# key: 'company_name',
|
|
21
|
+
# type: 'string',
|
|
22
|
+
# fallback_value: 'Acme Corp'
|
|
23
|
+
# })
|
|
24
|
+
#
|
|
25
|
+
# @example Create a number property
|
|
26
|
+
# Resend::ContactProperties.create({
|
|
27
|
+
# key: 'age',
|
|
28
|
+
# type: 'number',
|
|
29
|
+
# fallback_value: 0
|
|
30
|
+
# })
|
|
31
|
+
def create(params)
|
|
32
|
+
path = "contact-properties"
|
|
33
|
+
Resend::Request.new(path, params, "post").perform
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Retrieve a contact property by its ID
|
|
37
|
+
#
|
|
38
|
+
# @param contact_property_id [String] The Contact Property ID
|
|
39
|
+
#
|
|
40
|
+
# @return [Hash] Response containing the contact property details
|
|
41
|
+
#
|
|
42
|
+
# @example Get a contact property
|
|
43
|
+
# Resend::ContactProperties.get('b6d24b8e-af0b-4c3c-be0c-359bbd97381e')
|
|
44
|
+
def get(contact_property_id = "")
|
|
45
|
+
path = "contact-properties/#{contact_property_id}"
|
|
46
|
+
Resend::Request.new(path, {}, "get").perform
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Retrieve a list of contact properties
|
|
50
|
+
#
|
|
51
|
+
# @param params [Hash] Optional query parameters
|
|
52
|
+
# @option params [Integer] :limit Number of contact properties to retrieve (1-100, default: 20)
|
|
53
|
+
# @option params [String] :after The ID after which to retrieve more contact properties
|
|
54
|
+
# @option params [String] :before The ID before which to retrieve more contact properties
|
|
55
|
+
#
|
|
56
|
+
# @return [Hash] Response containing list of contact properties
|
|
57
|
+
#
|
|
58
|
+
# @example List all contact properties
|
|
59
|
+
# Resend::ContactProperties.list
|
|
60
|
+
#
|
|
61
|
+
# @example List with pagination
|
|
62
|
+
# Resend::ContactProperties.list({ limit: 10, after: 'cursor_123' })
|
|
63
|
+
def list(params = {})
|
|
64
|
+
path = Resend::PaginationHelper.build_paginated_path("contact-properties", params)
|
|
65
|
+
Resend::Request.new(path, {}, "get").perform
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Update an existing contact property
|
|
69
|
+
#
|
|
70
|
+
# Note: The 'key' and 'type' fields cannot be changed after creation
|
|
71
|
+
#
|
|
72
|
+
# @param params [Hash] Parameters for updating a contact property
|
|
73
|
+
# @option params [String] :id The Contact Property ID (required)
|
|
74
|
+
# @option params [String, Integer] :fallback_value The default value when property is not set
|
|
75
|
+
# (must match property type)
|
|
76
|
+
#
|
|
77
|
+
# @return [Hash] Response containing the updated contact property
|
|
78
|
+
#
|
|
79
|
+
# @example Update fallback value
|
|
80
|
+
# Resend::ContactProperties.update({
|
|
81
|
+
# id: 'b6d24b8e-af0b-4c3c-be0c-359bbd97381e',
|
|
82
|
+
# fallback_value: 'Example Company'
|
|
83
|
+
# })
|
|
84
|
+
def update(params)
|
|
85
|
+
raise ArgumentError, "Missing `id` field" if params[:id].nil?
|
|
86
|
+
|
|
87
|
+
contact_property_id = params[:id]
|
|
88
|
+
path = "contact-properties/#{contact_property_id}"
|
|
89
|
+
Resend::Request.new(path, params, "patch").perform
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Remove an existing contact property
|
|
93
|
+
#
|
|
94
|
+
# @param contact_property_id [String] The Contact Property ID
|
|
95
|
+
#
|
|
96
|
+
# @return [Hash] Response containing the deleted property ID and confirmation
|
|
97
|
+
#
|
|
98
|
+
# @example Delete a contact property
|
|
99
|
+
# Resend::ContactProperties.remove('b6d24b8e-af0b-4c3c-be0c-359bbd97381e')
|
|
100
|
+
def remove(contact_property_id = "")
|
|
101
|
+
path = "contact-properties/#{contact_property_id}"
|
|
102
|
+
Resend::Request.new(path, {}, "delete").perform
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Resend
|
|
4
|
+
module Contacts
|
|
5
|
+
# Contact Segments api wrapper
|
|
6
|
+
module Segments
|
|
7
|
+
class << self
|
|
8
|
+
#
|
|
9
|
+
# List all segments for a contact
|
|
10
|
+
#
|
|
11
|
+
# @param params [Hash] the parameters
|
|
12
|
+
# @option params [String] :contact_id the contact id (either contact_id or email is required)
|
|
13
|
+
# @option params [String] :email the contact email (either contact_id or email is required)
|
|
14
|
+
# @option params [Integer] :limit the maximum number of results to return (optional)
|
|
15
|
+
# @option params [String] :after the cursor for pagination (optional)
|
|
16
|
+
# @option params [String] :before the cursor for pagination (optional)
|
|
17
|
+
#
|
|
18
|
+
# https://resend.com/docs/api-reference/contacts/list-contact-segments
|
|
19
|
+
def list(params)
|
|
20
|
+
raise ArgumentError, "contact_id or email is required" if params[:contact_id].nil? && params[:email].nil?
|
|
21
|
+
|
|
22
|
+
identifier = params[:contact_id] || params[:email]
|
|
23
|
+
base_path = "contacts/#{identifier}/segments"
|
|
24
|
+
path = Resend::PaginationHelper.build_paginated_path(base_path, params)
|
|
25
|
+
Resend::Request.new(path, {}, "get").perform
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
#
|
|
29
|
+
# Add a contact to a segment
|
|
30
|
+
#
|
|
31
|
+
# @param params [Hash] the parameters
|
|
32
|
+
# @option params [String] :contact_id the contact id (either contact_id or email is required)
|
|
33
|
+
# @option params [String] :email the contact email (either contact_id or email is required)
|
|
34
|
+
# @option params [String] :segment_id the segment id (required)
|
|
35
|
+
#
|
|
36
|
+
# https://resend.com/docs/api-reference/contacts/add-contact-to-segment
|
|
37
|
+
def add(params)
|
|
38
|
+
raise ArgumentError, "contact_id or email is required" if params[:contact_id].nil? && params[:email].nil?
|
|
39
|
+
raise ArgumentError, "segment_id is required" if params[:segment_id].nil?
|
|
40
|
+
|
|
41
|
+
identifier = params[:contact_id] || params[:email]
|
|
42
|
+
path = "contacts/#{identifier}/segments/#{params[:segment_id]}"
|
|
43
|
+
Resend::Request.new(path, {}, "post").perform
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
#
|
|
47
|
+
# Remove a contact from a segment
|
|
48
|
+
#
|
|
49
|
+
# @param params [Hash] the parameters
|
|
50
|
+
# @option params [String] :contact_id the contact id (either contact_id or email is required)
|
|
51
|
+
# @option params [String] :email the contact email (either contact_id or email is required)
|
|
52
|
+
# @option params [String] :segment_id the segment id (required)
|
|
53
|
+
#
|
|
54
|
+
# https://resend.com/docs/api-reference/contacts/remove-contact-from-segment
|
|
55
|
+
def remove(params)
|
|
56
|
+
raise ArgumentError, "contact_id or email is required" if params[:contact_id].nil? && params[:email].nil?
|
|
57
|
+
raise ArgumentError, "segment_id is required" if params[:segment_id].nil?
|
|
58
|
+
|
|
59
|
+
identifier = params[:contact_id] || params[:email]
|
|
60
|
+
path = "contacts/#{identifier}/segments/#{params[:segment_id]}"
|
|
61
|
+
Resend::Request.new(path, {}, "delete").perform
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Resend
|
|
4
|
+
module Contacts
|
|
5
|
+
# Module for managing contact topic subscriptions
|
|
6
|
+
#
|
|
7
|
+
# Allows you to manage which topics contacts are subscribed to
|
|
8
|
+
module Topics
|
|
9
|
+
class << self
|
|
10
|
+
# Retrieve a list of topics subscriptions for a contact
|
|
11
|
+
#
|
|
12
|
+
# @param params [Hash] Parameters for listing topics
|
|
13
|
+
# @option params [String] :id The Contact ID (either :id or :email must be provided)
|
|
14
|
+
# @option params [String] :email The Contact Email (either :id or :email must be provided)
|
|
15
|
+
# @option params [Integer] :limit Number of topics to retrieve (1-100)
|
|
16
|
+
# @option params [String] :after The ID after which to retrieve more topics
|
|
17
|
+
# @option params [String] :before The ID before which to retrieve more topics
|
|
18
|
+
#
|
|
19
|
+
# @return [Hash] Response containing list of topics with subscription status
|
|
20
|
+
#
|
|
21
|
+
# @example List topics by contact ID
|
|
22
|
+
# Resend::Contacts::Topics.list(id: 'e169aa45-1ecf-4183-9955-b1499d5701d3')
|
|
23
|
+
#
|
|
24
|
+
# @example List topics by contact email
|
|
25
|
+
# Resend::Contacts::Topics.list(email: 'steve.wozniak@gmail.com')
|
|
26
|
+
#
|
|
27
|
+
# @example List topics with pagination
|
|
28
|
+
# Resend::Contacts::Topics.list(id: 'contact-id', limit: 10, after: 'cursor_123')
|
|
29
|
+
def list(params = {})
|
|
30
|
+
contact_identifier = params[:id] || params[:email]
|
|
31
|
+
raise ArgumentError, "Either :id or :email must be provided" if contact_identifier.nil?
|
|
32
|
+
|
|
33
|
+
pagination_params = params.slice(:limit, :after, :before)
|
|
34
|
+
base_path = "contacts/#{contact_identifier}/topics"
|
|
35
|
+
path = Resend::PaginationHelper.build_paginated_path(base_path, pagination_params)
|
|
36
|
+
|
|
37
|
+
Resend::Request.new(path, {}, "get").perform
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Update topic subscriptions for a contact
|
|
41
|
+
#
|
|
42
|
+
# @param params [Hash] Parameters for updating topics
|
|
43
|
+
# @option params [String] :id The Contact ID (either :id or :email must be provided)
|
|
44
|
+
# @option params [String] :email The Contact Email (either :id or :email must be provided)
|
|
45
|
+
# @option params [Array<Hash>] :topics Array of topic subscription updates
|
|
46
|
+
# Each topic hash should contain:
|
|
47
|
+
# - :id [String] The Topic ID (required)
|
|
48
|
+
# - :subscription [String] The subscription action: 'opt_in' or 'opt_out' (required)
|
|
49
|
+
#
|
|
50
|
+
# @return [Hash] Response containing the contact ID
|
|
51
|
+
#
|
|
52
|
+
# @example Update by contact ID
|
|
53
|
+
# Resend::Contacts::Topics.update({
|
|
54
|
+
# id: 'e169aa45-1ecf-4183-9955-b1499d5701d3',
|
|
55
|
+
# topics: [
|
|
56
|
+
# { id: 'b6d24b8e-af0b-4c3c-be0c-359bbd97381e', subscription: 'opt_out' },
|
|
57
|
+
# { id: '07d84122-7224-4881-9c31-1c048e204602', subscription: 'opt_in' }
|
|
58
|
+
# ]
|
|
59
|
+
# })
|
|
60
|
+
#
|
|
61
|
+
# @example Update by contact email
|
|
62
|
+
# Resend::Contacts::Topics.update({
|
|
63
|
+
# email: 'steve.wozniak@gmail.com',
|
|
64
|
+
# topics: [
|
|
65
|
+
# { id: '07d84122-7224-4881-9c31-1c048e204602', subscription: 'opt_out' }
|
|
66
|
+
# ]
|
|
67
|
+
# })
|
|
68
|
+
def update(params)
|
|
69
|
+
contact_identifier = params[:id] || params[:email]
|
|
70
|
+
raise ArgumentError, "Either :id or :email must be provided" if contact_identifier.nil?
|
|
71
|
+
|
|
72
|
+
path = "contacts/#{contact_identifier}/topics"
|
|
73
|
+
body = params[:topics]
|
|
74
|
+
|
|
75
|
+
Resend::Request.new(path, body, "patch").perform
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
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,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,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
|
@@ -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
|
data/lib/resend/mailer.rb
CHANGED
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
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Resend
|
|
4
|
+
# Response wrapper that maintains backwards compatibility while exposing headers
|
|
5
|
+
#
|
|
6
|
+
# This class wraps API responses and behaves like a Hash for backwards compatibility,
|
|
7
|
+
# while also providing access to response headers via the #headers method.
|
|
8
|
+
#
|
|
9
|
+
# @example Backwards compatible hash access
|
|
10
|
+
# response = Resend::Emails.send(params)
|
|
11
|
+
# response[:id] # => "49a3999c-0ce1-4ea6-ab68-afcd6dc2e794"
|
|
12
|
+
#
|
|
13
|
+
# @example Accessing response headers
|
|
14
|
+
# response = Resend::Emails.send(params)
|
|
15
|
+
# response.headers # => {"content-type" => "application/json", ...}
|
|
16
|
+
# response.headers['x-ratelimit-remaining'] # => "50"
|
|
17
|
+
class Response
|
|
18
|
+
include Enumerable
|
|
19
|
+
|
|
20
|
+
# @param data [Hash] The response data
|
|
21
|
+
# @param headers [Hash, HTTParty::Response] The response headers
|
|
22
|
+
def initialize(data, headers)
|
|
23
|
+
@data = data.is_a?(Hash) ? data : {}
|
|
24
|
+
@headers = normalize_headers(headers)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Access response headers
|
|
28
|
+
# @return [Hash] Response headers as a hash with lowercase string keys
|
|
29
|
+
attr_reader :headers
|
|
30
|
+
|
|
31
|
+
# Hash-like access via []
|
|
32
|
+
# @param key [Symbol, String] The key to access
|
|
33
|
+
# @return [Object] The value at the key
|
|
34
|
+
def [](key)
|
|
35
|
+
@data[key]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Hash-like assignment via []=
|
|
39
|
+
# @param key [Symbol, String] The key to set
|
|
40
|
+
# @param value [Object] The value to set
|
|
41
|
+
def []=(key, value)
|
|
42
|
+
@data[key] = value
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Dig into nested hash structure
|
|
46
|
+
# @param keys [Array<Symbol, String>] Keys to dig through
|
|
47
|
+
# @return [Object] The value at the nested key path
|
|
48
|
+
def dig(*keys)
|
|
49
|
+
@data.dig(*keys)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Convert to plain hash
|
|
53
|
+
# @return [Hash] The underlying data hash
|
|
54
|
+
def to_h
|
|
55
|
+
@data
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
alias to_hash to_h
|
|
59
|
+
|
|
60
|
+
# Get all keys from the data
|
|
61
|
+
# @return [Array] Array of keys
|
|
62
|
+
def keys
|
|
63
|
+
@data.keys
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Get all values from the data
|
|
67
|
+
# @return [Array] Array of values
|
|
68
|
+
def values
|
|
69
|
+
@data.values
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Check if key exists
|
|
73
|
+
# @param key [Symbol, String] The key to check
|
|
74
|
+
# @return [Boolean] True if key exists
|
|
75
|
+
def key?(key)
|
|
76
|
+
@data.key?(key)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
alias has_key? key?
|
|
80
|
+
|
|
81
|
+
# Enable enumeration over the data
|
|
82
|
+
def each(&block)
|
|
83
|
+
@data.each(&block)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Transform keys in the underlying data
|
|
87
|
+
# @return [Resend::Response] Self for chaining
|
|
88
|
+
def transform_keys!(&block)
|
|
89
|
+
@data.transform_keys!(&block)
|
|
90
|
+
self
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Check if response is empty
|
|
94
|
+
# @return [Boolean] True if data is empty
|
|
95
|
+
def empty?
|
|
96
|
+
@data.empty?
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Respond to hash-like methods
|
|
100
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
101
|
+
@data.respond_to?(method_name) || super
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Delegate unknown methods to the underlying data hash
|
|
105
|
+
def method_missing(method_name, *args, &block)
|
|
106
|
+
if @data.respond_to?(method_name)
|
|
107
|
+
result = @data.send(method_name, *args, &block)
|
|
108
|
+
# If the method returns the hash itself, return self to maintain wrapper
|
|
109
|
+
result.equal?(@data) ? self : result
|
|
110
|
+
else
|
|
111
|
+
super
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# String representation for debugging
|
|
116
|
+
# @return [String] String representation of the response
|
|
117
|
+
def inspect
|
|
118
|
+
"#<Resend::Response data=#{@data.inspect} headers=#{@headers.keys.inspect}>"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
private
|
|
122
|
+
|
|
123
|
+
# Normalize headers to a simple hash with lowercase string keys
|
|
124
|
+
# @param headers [Hash, HTTParty::Response, nil] The headers to normalize
|
|
125
|
+
# @return [Hash] Normalized headers hash
|
|
126
|
+
def normalize_headers(headers)
|
|
127
|
+
return {} if headers.nil?
|
|
128
|
+
|
|
129
|
+
# Handle HTTParty::Response object
|
|
130
|
+
headers = headers.headers if headers.respond_to?(:headers)
|
|
131
|
+
|
|
132
|
+
# Convert to hash and normalize keys to lowercase strings
|
|
133
|
+
case headers
|
|
134
|
+
when Hash
|
|
135
|
+
headers.to_h.transform_keys { |k| k.to_s.downcase }
|
|
136
|
+
else
|
|
137
|
+
{}
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Resend
|
|
4
|
+
# Segments api wrapper
|
|
5
|
+
module Segments
|
|
6
|
+
class << self
|
|
7
|
+
# https://resend.com/docs/api-reference/segments/create-segment
|
|
8
|
+
def create(params)
|
|
9
|
+
path = "segments"
|
|
10
|
+
Resend::Request.new(path, params, "post").perform
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# https://resend.com/docs/api-reference/segments/get-segment
|
|
14
|
+
def get(segment_id = "")
|
|
15
|
+
path = "segments/#{segment_id}"
|
|
16
|
+
Resend::Request.new(path, {}, "get").perform
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# https://resend.com/docs/api-reference/segments/list-segments
|
|
20
|
+
def list(params = {})
|
|
21
|
+
path = Resend::PaginationHelper.build_paginated_path("segments", params)
|
|
22
|
+
Resend::Request.new(path, {}, "get").perform
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# https://resend.com/docs/api-reference/segments/delete-segment
|
|
26
|
+
def remove(segment_id = "")
|
|
27
|
+
path = "segments/#{segment_id}"
|
|
28
|
+
Resend::Request.new(path, {}, "delete").perform
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Resend
|
|
4
|
+
# Templates api wrapper
|
|
5
|
+
module Templates
|
|
6
|
+
class << self
|
|
7
|
+
# https://resend.com/docs/api-reference/templates/create-template
|
|
8
|
+
def create(params = {})
|
|
9
|
+
path = "templates"
|
|
10
|
+
Resend::Request.new(path, params, "post").perform
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# https://resend.com/docs/api-reference/templates/get-template
|
|
14
|
+
def get(template_id = "")
|
|
15
|
+
path = "templates/#{template_id}"
|
|
16
|
+
Resend::Request.new(path, {}, "get").perform
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# https://resend.com/docs/api-reference/templates/update-template
|
|
20
|
+
def update(template_id, params = {})
|
|
21
|
+
path = "templates/#{template_id}"
|
|
22
|
+
Resend::Request.new(path, params, "patch").perform
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# https://resend.com/docs/api-reference/templates/publish-template
|
|
26
|
+
def publish(template_id = "")
|
|
27
|
+
path = "templates/#{template_id}/publish"
|
|
28
|
+
Resend::Request.new(path, {}, "post").perform
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# https://resend.com/docs/api-reference/templates/duplicate-template
|
|
32
|
+
def duplicate(template_id = "")
|
|
33
|
+
path = "templates/#{template_id}/duplicate"
|
|
34
|
+
Resend::Request.new(path, {}, "post").perform
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# https://resend.com/docs/api-reference/templates/list-templates
|
|
38
|
+
def list(params = {})
|
|
39
|
+
path = Resend::PaginationHelper.build_paginated_path("templates", params)
|
|
40
|
+
Resend::Request.new(path, {}, "get").perform
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# https://resend.com/docs/api-reference/templates/delete-template
|
|
44
|
+
def remove(template_id = "")
|
|
45
|
+
path = "templates/#{template_id}"
|
|
46
|
+
Resend::Request.new(path, {}, "delete").perform
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -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
|
data/lib/resend/version.rb
CHANGED
data/lib/resend.rb
CHANGED
|
@@ -8,18 +8,27 @@ require "httparty"
|
|
|
8
8
|
require "json"
|
|
9
9
|
require "cgi"
|
|
10
10
|
require "resend/errors"
|
|
11
|
+
require "resend/response"
|
|
11
12
|
require "resend/client"
|
|
12
13
|
require "resend/request"
|
|
13
14
|
require "resend/pagination_helper"
|
|
14
15
|
|
|
15
16
|
# API Operations
|
|
16
|
-
require "resend/
|
|
17
|
+
require "resend/segments"
|
|
17
18
|
require "resend/api_keys"
|
|
18
19
|
require "resend/broadcasts"
|
|
19
20
|
require "resend/batch"
|
|
20
21
|
require "resend/contacts"
|
|
22
|
+
require "resend/contacts/segments"
|
|
23
|
+
require "resend/contacts/topics"
|
|
24
|
+
require "resend/contact_properties"
|
|
21
25
|
require "resend/domains"
|
|
22
26
|
require "resend/emails"
|
|
27
|
+
require "resend/templates"
|
|
28
|
+
require "resend/emails/receiving"
|
|
29
|
+
require "resend/emails/attachments"
|
|
30
|
+
require "resend/emails/receiving/attachments"
|
|
31
|
+
require "resend/topics"
|
|
23
32
|
require "resend/webhooks"
|
|
24
33
|
|
|
25
34
|
# Rails
|
|
@@ -36,4 +45,7 @@ module Resend
|
|
|
36
45
|
end
|
|
37
46
|
alias config configure
|
|
38
47
|
end
|
|
48
|
+
|
|
49
|
+
# @deprecated Use Segments instead
|
|
50
|
+
Audiences = Segments
|
|
39
51
|
end
|
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.
|
|
4
|
+
version: 1.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Derich Pacheco
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-02-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: httparty
|
|
@@ -38,34 +38,44 @@ dependencies:
|
|
|
38
38
|
- - ">="
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
40
|
version: '0'
|
|
41
|
-
description:
|
|
41
|
+
description:
|
|
42
42
|
email: carlosderich@gmail.com
|
|
43
43
|
executables: []
|
|
44
44
|
extensions: []
|
|
45
45
|
extra_rdoc_files: []
|
|
46
46
|
files:
|
|
47
|
+
- CHANGELOG.md
|
|
47
48
|
- README.md
|
|
48
49
|
- lib/resend.rb
|
|
49
50
|
- lib/resend/api_keys.rb
|
|
50
|
-
- lib/resend/audiences.rb
|
|
51
51
|
- lib/resend/batch.rb
|
|
52
52
|
- lib/resend/broadcasts.rb
|
|
53
53
|
- lib/resend/client.rb
|
|
54
|
+
- lib/resend/contact_properties.rb
|
|
54
55
|
- lib/resend/contacts.rb
|
|
56
|
+
- lib/resend/contacts/segments.rb
|
|
57
|
+
- lib/resend/contacts/topics.rb
|
|
55
58
|
- lib/resend/domains.rb
|
|
56
59
|
- lib/resend/emails.rb
|
|
60
|
+
- lib/resend/emails/attachments.rb
|
|
61
|
+
- lib/resend/emails/receiving.rb
|
|
62
|
+
- lib/resend/emails/receiving/attachments.rb
|
|
57
63
|
- lib/resend/errors.rb
|
|
58
64
|
- lib/resend/mailer.rb
|
|
59
65
|
- lib/resend/pagination_helper.rb
|
|
60
66
|
- lib/resend/railtie.rb
|
|
61
67
|
- lib/resend/request.rb
|
|
68
|
+
- lib/resend/response.rb
|
|
69
|
+
- lib/resend/segments.rb
|
|
70
|
+
- lib/resend/templates.rb
|
|
71
|
+
- lib/resend/topics.rb
|
|
62
72
|
- lib/resend/version.rb
|
|
63
73
|
- lib/resend/webhooks.rb
|
|
64
74
|
homepage: https://github.com/resend/resend-ruby
|
|
65
75
|
licenses:
|
|
66
76
|
- MIT
|
|
67
77
|
metadata: {}
|
|
68
|
-
post_install_message:
|
|
78
|
+
post_install_message:
|
|
69
79
|
rdoc_options: []
|
|
70
80
|
require_paths:
|
|
71
81
|
- lib
|
|
@@ -80,8 +90,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
80
90
|
- !ruby/object:Gem::Version
|
|
81
91
|
version: '0'
|
|
82
92
|
requirements: []
|
|
83
|
-
rubygems_version: 3.
|
|
84
|
-
signing_key:
|
|
93
|
+
rubygems_version: 3.0.3.1
|
|
94
|
+
signing_key:
|
|
85
95
|
specification_version: 4
|
|
86
96
|
summary: The Ruby and Rails SDK for resend.com
|
|
87
97
|
test_files: []
|
data/lib/resend/audiences.rb
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Resend
|
|
4
|
-
# Audiences api wrapper
|
|
5
|
-
module Audiences
|
|
6
|
-
class << self
|
|
7
|
-
# https://resend.com/docs/api-reference/audiences/create-audience
|
|
8
|
-
def create(params)
|
|
9
|
-
path = "audiences"
|
|
10
|
-
Resend::Request.new(path, params, "post").perform
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
# https://resend.com/docs/api-reference/audiences/get-audience
|
|
14
|
-
def get(audience_id = "")
|
|
15
|
-
path = "audiences/#{audience_id}"
|
|
16
|
-
Resend::Request.new(path, {}, "get").perform
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
# https://resend.com/docs/api-reference/audiences/list-audiences
|
|
20
|
-
def list(params = {})
|
|
21
|
-
path = Resend::PaginationHelper.build_paginated_path("audiences", params)
|
|
22
|
-
Resend::Request.new(path, {}, "get").perform
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
# https://resend.com/docs/api-reference/audiences/delete-audience
|
|
26
|
-
def remove(audience_id = "")
|
|
27
|
-
path = "audiences/#{audience_id}"
|
|
28
|
-
Resend::Request.new(path, {}, "delete").perform
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|