Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
resend (1.19.0)
resend (1.20.0)
base64
httparty (>= 0.22.0)

Expand Down
1 change: 1 addition & 0 deletions lib/resend.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
require "resend/contact_properties"
require "resend/domains"
require "resend/domains/claims"
require "resend/domains/forge"
require "resend/emails"
require "resend/templates"
require "resend/emails/receiving"
Expand Down
10 changes: 8 additions & 2 deletions lib/resend/domains.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ def create(params)
Resend::Request.new(path, params, "post").perform
end

# Updates a domain. :forge_return_path moves the Forge return path
# without starting verification, and composes with the other fields.
#
# https://resend.com/docs/api-reference/domains/update-domain
def update(params)
path = "domains/#{params[:id]}"
Expand All @@ -34,10 +37,13 @@ def remove(domain_id = "")
Resend::Request.new(path, {}, "delete").perform
end

# Triggers domain verification. Pass `scope: ["forge"]` to re-check only
# the Forge record; without a scope the whole domain is verified.
#
# https://resend.com/docs/api-reference/domains/verify-domain
def verify(domain_id = "")
def verify(domain_id = "", params = {})
path = "domains/#{domain_id}/verify"
Resend::Request.new(path, {}, "post").perform
Resend::Request.new(path, params.slice(:scope), "post").perform
end
end
end
Expand Down
26 changes: 26 additions & 0 deletions lib/resend/domains/forge.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# frozen_string_literal: true

module Resend
module Domains
# Domain Forge API wrapper
module Forge
class << self
#
# Activate Forge on a domain. Returns the full domain, in the same shape
# as Resend::Domains.get. Enabling a domain that is already migrated is
# idempotent and returns its current state.
#
# @param params [Hash] the parameters
# @option params [String] :id the ID of the domain (required)
# @option params [String] :return_path subdomain for the Return-Path address.
# When omitted, the server picks a free DNS label.
#
# https://resend.com/docs/api-reference/domains/enable-forge
def enable(params)
path = "domains/#{params[:id]}/forge"
Resend::Request.new(path, params.slice(:return_path), "post").perform
end
end
end
end
end
2 changes: 1 addition & 1 deletion lib/resend/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module Resend
VERSION = "1.19.0"
VERSION = "1.20.0"
end
76 changes: 76 additions & 0 deletions spec/domain_forge_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# frozen_string_literal: true

RSpec.describe "Domains::Forge" do
before do
Resend.configure do |config|
config.api_key = "re_123"
end
end

let(:domain_id) { "d91cd9bd-1176-453e-8fc1-35364d380206" }

let(:forge_domain) do
{
object: "domain",
id: "d91cd9bd-1176-453e-8fc1-35364d380206",
name: "example.com",
status: "pending",
created_at: "2026-11-11 10:00:00.000000+00",
region: "us-east-1",
capabilities: {
"sending" => "enabled",
"receiving" => "disabled"
},
records: [
{
"record" => "SPF",
"name" => "send",
"value" => "v=spf1 include:spf.resend.com ~all",
"type" => "TXT",
"ttl" => "Auto",
"status" => "pending"
},
{
"record" => "DKIM",
"name" => "resend._domainkey",
"value" => "resend._domainkey.example.com.dkim.resend.com.",
"type" => "CNAME",
"ttl" => "Auto",
"status" => "pending"
}
],
open_tracking: false,
click_tracking: false
}
end

describe "enable" do
it "activates Forge and returns the full domain" do
params = { id: domain_id, return_path: "send" }

expect(Resend::Request).to receive(:new)
.with("domains/#{domain_id}/forge", { return_path: "send" }, "post")
.and_call_original
allow_any_instance_of(Resend::Request).to receive(:perform).and_return(forge_domain)

domain = Resend::Domains::Forge.enable(params)

expect(domain[:object]).to eql("domain")
expect(domain[:id]).to eql(domain_id)
expect(domain[:status]).to eql("pending")
expect(domain[:records].first["record"]).to eql("SPF")
end

it "activates Forge without a return path" do
expect(Resend::Request).to receive(:new)
.with("domains/#{domain_id}/forge", {}, "post")
.and_call_original
allow_any_instance_of(Resend::Request).to receive(:perform).and_return(forge_domain)

domain = Resend::Domains::Forge.enable({ id: domain_id })

expect(domain[:object]).to eql("domain")
expect(domain[:id]).to eql(domain_id)
end
end
end
37 changes: 37 additions & 0 deletions spec/domains_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,27 @@
domain = Resend::Domains.update(update_params)
expect(domain[:id]).to eql("479e3145-dd38-476b-932c-529ceb705947")
end

it "updates the forge return path" do
resp = {
"object": "domain",
"id": "479e3145-dd38-476b-932c-529ceb705947"
}

update_params = {
id: "479e3145-dd38-476b-932c-529ceb705947",
forge_return_path: "outbound"
}

expect(Resend::Request).to receive(:new)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: This example depends on API-key state left by earlier examples, so it fails when run by itself (for example, while debugging this line). Add a local Resend.configure setup for the verification examples, or move the configuration to the outer Domains group.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At spec/domains_spec.rb, line 214:

<comment>This example depends on API-key state left by earlier examples, so it fails when run by itself (for example, while debugging this line). Add a local `Resend.configure` setup for the verification examples, or move the configuration to the outer `Domains` group.</comment>

<file context>
@@ -199,6 +199,27 @@
+        forge_return_path: "outbound"
+      }
+
+      expect(Resend::Request).to receive(:new)
+        .with("domains/479e3145-dd38-476b-932c-529ceb705947", update_params, "patch")
+        .and_call_original
</file context>

.with("domains/479e3145-dd38-476b-932c-529ceb705947", update_params, "patch")
.and_call_original
allow_any_instance_of(Resend::Request).to receive(:perform).and_return(resp)

domain = Resend::Domains.update(update_params)
expect(domain[:id]).to eql("479e3145-dd38-476b-932c-529ceb705947")
expect(domain[:object]).to eql("domain")
end
end

describe "get domain" do
Expand Down Expand Up @@ -278,5 +299,21 @@
allow_any_instance_of(Resend::Request).to receive(:perform).and_return("")
expect { Resend::Domains.verify }.not_to raise_error
end

it "verifies only the Forge record when scoped" do

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Custom agent: API Key Permission Check SDK Methods

These tests exercise new Forge provider operations — PATCH domains/:id with forge_return_path and POST domains/:id/verify with scope: ["forge"] — plus the new /domains/:id/forge endpoints in lib/resend/domains/forge.rb. Per the API-key permission checklist, confirm the API keys used in production have the required permissions for these new operations before deploying, to avoid permission-related failures.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At spec/domains_spec.rb, line 303:

<comment>These tests exercise new Forge provider operations — PATCH `domains/:id` with `forge_return_path` and POST `domains/:id/verify` with `scope: ["forge"]` — plus the new `/domains/:id/forge` endpoints in `lib/resend/domains/forge.rb`. Per the API-key permission checklist, confirm the API keys used in production have the required permissions for these new operations before deploying, to avoid permission-related failures.</comment>

<file context>
@@ -278,5 +299,21 @@
       expect { Resend::Domains.verify }.not_to raise_error
     end
+
+    it "verifies only the Forge record when scoped" do
+      resp = {
+        "object": "domain",
</file context>

resp = {
"object": "domain",
"id": "d91cd9bd-1176-453e-8fc1-35364d380206"
}

expect(Resend::Request).to receive(:new)
.with("domains/d91cd9bd-1176-453e-8fc1-35364d380206/verify", { scope: ["forge"] }, "post")
.and_call_original
allow_any_instance_of(Resend::Request).to receive(:perform).and_return(resp)

verified = Resend::Domains.verify("d91cd9bd-1176-453e-8fc1-35364d380206", scope: ["forge"])
expect(verified[:object]).to eql("domain")
expect(verified[:id]).to eql("d91cd9bd-1176-453e-8fc1-35364d380206")
end
end
end
Loading