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
30 changes: 30 additions & 0 deletions examples/domain_claims.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true

require_relative "../lib/resend"

raise if ENV["RESEND_API_KEY"].nil?

Resend.api_key = ENV["RESEND_API_KEY"]

def claim_domain
params = {
name: "example.com",
region: "us-east-1"
}
claim = Resend::Domains::Claims.create(params)
puts "Created domain claim: #{claim[:id]}"
puts "Status: #{claim[:status]}"

record = claim[:record]
puts "Add this TXT record to prove ownership: #{record[:name]} = #{record[:value]}" if record

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: This example can print blank/wrong TXT record fields because nested response objects are accessed with symbol keys instead of string keys. Using record['name']/record['value'] keeps the sample aligned with actual response shape.

(Based on your team's feedback about nested response key access conventions.) .

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At examples/domain_claims.rb, line 19:

<comment>This example can print blank/wrong TXT record fields because nested response objects are accessed with symbol keys instead of string keys. Using `record['name']`/`record['value']` keeps the sample aligned with actual response shape.

(Based on your team's feedback about nested response key access conventions.) .</comment>

<file context>
@@ -0,0 +1,30 @@
+  puts "Status: #{claim[:status]}"
+
+  record = claim[:record]
+  puts "Add this TXT record to prove ownership: #{record[:name]} = #{record[:value]}" if record
+
+  # Get: poll the claim until the TXT record has been added and verification can run.
</file context>


# Get: poll the claim until the TXT record has been added and verification can run.
retrieved = Resend::Domains::Claims.get(claim[:domain_id])
puts "Retrieved domain claim status: #{retrieved[:status]}"

# Verify: trigger asynchronous DNS verification and ownership transfer.
verified = Resend::Domains::Claims.verify(claim[:domain_id])
puts "Verification triggered, claim status: #{verified[:status]}"
end

claim_domain
1 change: 1 addition & 0 deletions lib/resend.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
require "resend/contacts/topics"
require "resend/contact_properties"
require "resend/domains"
require "resend/domains/claims"
require "resend/emails"
require "resend/templates"
require "resend/emails/receiving"
Expand Down
56 changes: 56 additions & 0 deletions lib/resend/domains/claims.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# frozen_string_literal: true

module Resend
module Domains
# Domain Claims API wrapper
module Claims
class << self
#
# Start a claim for a domain that another Resend account has already verified.
# The domain is recreated under your account with fresh DKIM keys, so the
# previous account's DNS records cannot be reused. Returns a TXT record to add
# to your DNS to prove ownership. Uses the same request body as creating a domain.
#
# @param params [Hash] the parameters
# @option params [String] :name the name of the domain you want to claim (required)
# @option params [String] :region the region where emails will be sent from.
# Possible values: 'us-east-1' | 'eu-west-1' | 'sa-east-1' | 'ap-northeast-1'
# @option params [String] :custom_return_path subdomain for the Return-Path address (default 'send')
# @option params [String] :tracking_subdomain the custom subdomain used for click and open tracking links
# @option params [Boolean] :click_tracking track clicks within the body of each HTML email
# @option params [Boolean] :open_tracking track the open rate of each email
#
# https://resend.com/docs/api-reference/domains/claim-domain
def create(params)
path = "domains/claim"
Resend::Request.new(path, params, "post").perform
end

#
# Retrieve the latest claim for the placeholder domain created by the claim.
#
# @param domain_id [String] the ID of the placeholder domain created by the claim
#
# https://resend.com/docs/api-reference/domains/get-domain-claim
def get(domain_id = "")
path = "domains/#{domain_id}/claim"
Resend::Request.new(path, {}, "get").perform
end

#
# Trigger asynchronous DNS verification and ownership transfer for a domain claim.
# The claim stays 'pending' while verification runs; poll `get` for status. Once
# 'completed', the transferred domain has new DKIM records that must be added to
# DNS and verified via Resend::Domains.verify.
#
# @param domain_id [String] the ID of the placeholder domain created by the claim
#
# https://resend.com/docs/api-reference/domains/verify-domain-claim
def verify(domain_id = "")
path = "domains/#{domain_id}/claim/verify"
Resend::Request.new(path, {}, "post").perform
end
end
end
end
end
101 changes: 101 additions & 0 deletions spec/domain_claims_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# frozen_string_literal: true

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

describe "create" do
it "should start a domain claim" do
resp = {
object: "domain_claim",
id: "dacf4072-4119-4d88-932f-6c6126d3a9d1",
name: "example.com",
status: "pending",
domain_id: "d91cd9bd-1176-453e-8fc1-35364d380206",
region: "us-east-1",
record: {
type: "TXT",
name: "example.com",
value: "resend-domain-verification=3f8a1c2d4e5b6a7f8091a2b3c4d5e6f7",
ttl: "Auto"
},
blocked_reason: nil,
failure_reason: nil,
created_at: "2026-06-16T17:12:02.059593+00:00",
expires_at: "2026-06-23T17:12:02.059593+00:00"
}

params = { name: "example.com" }
allow_any_instance_of(Resend::Request).to receive(:perform).and_return(resp)
claim = Resend::Domains::Claims.create(params)
expect(claim[:id]).to eql("dacf4072-4119-4d88-932f-6c6126d3a9d1")
expect(claim[:object]).to eql("domain_claim")
expect(claim[:status]).to eql("pending")
expect(claim[:domain_id]).to eql("d91cd9bd-1176-453e-8fc1-35364d380206")
expect(claim[:record][:type]).to eql("TXT")
expect(claim[:record][:value]).to eql("resend-domain-verification=3f8a1c2d4e5b6a7f8091a2b3c4d5e6f7")
end

it "should pass all options in the request body" do
resp = { object: "domain_claim", id: "dacf4072-4119-4d88-932f-6c6126d3a9d1", status: "pending" }

params = {
name: "example.com",
region: "us-east-1",
custom_return_path: "send",
open_tracking: true,
click_tracking: false,
tracking_subdomain: "links"
}

expect(Resend::Request).to receive(:new).with("domains/claim", params, "post").and_call_original
allow_any_instance_of(Resend::Request).to receive(:perform).and_return(resp)
Resend::Domains::Claims.create(params)
end
end

describe "get" do
it "should retrieve a domain claim" do
resp = {
object: "domain_claim",
id: "dacf4072-4119-4d88-932f-6c6126d3a9d1",
name: "example.com",
status: "blocked",
domain_id: "d91cd9bd-1176-453e-8fc1-35364d380206",
region: "us-east-1",
blocked_reason: "grace_period",
failure_reason: nil,
created_at: "2026-06-16T17:12:02.059593+00:00",
expires_at: "2026-06-23T17:12:02.059593+00:00"
}
allow(resp).to receive(:body).and_return(resp)
allow(HTTParty).to receive(:send).and_return(resp)

claim = Resend::Domains::Claims.get(resp[:domain_id])

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: The get and verify tests don't verify the API path or HTTP verb used by the underlying Request, unlike the create tests which have a dedicated assertion for Request.new arguments. Adding path/verb expectations for get and verify would make refactoring safer — for example, catching accidental changes to the URL segment order or the HTTP method.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At spec/domain_claims_spec.rb, line 77:

<comment>The `get` and `verify` tests don't verify the API path or HTTP verb used by the underlying Request, unlike the `create` tests which have a dedicated assertion for `Request.new` arguments. Adding path/verb expectations for `get` and `verify` would make refactoring safer — for example, catching accidental changes to the URL segment order or the HTTP method.</comment>

<file context>
@@ -0,0 +1,101 @@
+      allow(resp).to receive(:body).and_return(resp)
+      allow(HTTParty).to receive(:send).and_return(resp)
+
+      claim = Resend::Domains::Claims.get(resp[:domain_id])
+
+      expect(claim[:id]).to eql("dacf4072-4119-4d88-932f-6c6126d3a9d1")
</file context>


expect(claim[:id]).to eql("dacf4072-4119-4d88-932f-6c6126d3a9d1")
expect(claim[:status]).to eql("blocked")
expect(claim[:blocked_reason]).to eql("grace_period")
end
end

describe "verify" do
it "should trigger verification for a domain claim" do
resp = {
object: "domain_claim",
id: "dacf4072-4119-4d88-932f-6c6126d3a9d1",
name: "example.com",
status: "pending",
domain_id: "d91cd9bd-1176-453e-8fc1-35364d380206",
region: "us-east-1"
}
allow_any_instance_of(Resend::Request).to receive(:perform).and_return(resp)
claim = Resend::Domains::Claims.verify("d91cd9bd-1176-453e-8fc1-35364d380206")
expect(claim[:id]).to eql("dacf4072-4119-4d88-932f-6c6126d3a9d1")
expect(claim[:status]).to eql("pending")
end
end
end