diff --git a/Gemfile.lock b/Gemfile.lock index 1582b43..2641498 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - resend (1.19.0) + resend (1.20.0) base64 httparty (>= 0.22.0) diff --git a/lib/resend.rb b/lib/resend.rb index 952739d..6b7805f 100644 --- a/lib/resend.rb +++ b/lib/resend.rb @@ -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" diff --git a/lib/resend/domains.rb b/lib/resend/domains.rb index f90516c..7b4dea1 100644 --- a/lib/resend/domains.rb +++ b/lib/resend/domains.rb @@ -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]}" @@ -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 diff --git a/lib/resend/domains/forge.rb b/lib/resend/domains/forge.rb new file mode 100644 index 0000000..fb550bf --- /dev/null +++ b/lib/resend/domains/forge.rb @@ -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 diff --git a/lib/resend/version.rb b/lib/resend/version.rb index e3e06b5..7dc8c61 100644 --- a/lib/resend/version.rb +++ b/lib/resend/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Resend - VERSION = "1.19.0" + VERSION = "1.20.0" end diff --git a/spec/domain_forge_spec.rb b/spec/domain_forge_spec.rb new file mode 100644 index 0000000..97cee58 --- /dev/null +++ b/spec/domain_forge_spec.rb @@ -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 diff --git a/spec/domains_spec.rb b/spec/domains_spec.rb index bbcf7b8..991a25c 100644 --- a/spec/domains_spec.rb +++ b/spec/domains_spec.rb @@ -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) + .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 @@ -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 + 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