From 6396ec4fd7b73fcca75d4c116241d345e95f100b Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 8 Jul 2026 19:01:06 +0000 Subject: [PATCH 1/2] feat: add message_id typing for emails and webhooks Add RBS type signatures matching the Node.js SDK for message_id on GET /emails/:id, receiving email responses, and all email webhook events. Include sig files in the gem package, stop gitignoring sig/, and update specs to assert message_id. Co-authored-by: cpenned --- .gitignore | 1 - lib/resend/emails.rb | 2 + lib/resend/emails/receiving.rb | 2 +- lib/resend/webhooks.rb | 3 + resend.gemspec | 2 +- sig/resend.rbs | 11 +- sig/resend/emails.rbs | 62 +++++++++ sig/resend/emails/receiving.rbs | 59 ++++++++ sig/resend/response.rbs | 19 +++ sig/resend/webhooks.rbs | 236 ++++++++++++++++++++++++++++++++ spec/emails/receiving_spec.rb | 1 + spec/emails_spec.rb | 7 +- spec/webhooks_spec.rb | 15 +- 13 files changed, 413 insertions(+), 7 deletions(-) create mode 100644 sig/resend/emails.rbs create mode 100644 sig/resend/emails/receiving.rbs create mode 100644 sig/resend/response.rbs create mode 100644 sig/resend/webhooks.rbs diff --git a/.gitignore b/.gitignore index 2bddeb8..7dec49e 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,6 @@ /pkg/ /spec/reports/ /tmp/ -/sig/ # rspec failure tracking .rspec_status diff --git a/lib/resend/emails.rb b/lib/resend/emails.rb index f233338..9a45484 100644 --- a/lib/resend/emails.rb +++ b/lib/resend/emails.rb @@ -13,6 +13,8 @@ def send(params, options: {}) # Retrieve a single email. # see more: https://resend.com/docs/api-reference/emails/retrieve-email + # + # @return [Resend::Response] The email object, including +message_id+ (RFC Message-ID header value) def get(email_id = "") path = "emails/#{email_id}" Resend::Request.new(path, {}, "get").perform diff --git a/lib/resend/emails/receiving.rb b/lib/resend/emails/receiving.rb index e78076c..8952565 100644 --- a/lib/resend/emails/receiving.rb +++ b/lib/resend/emails/receiving.rb @@ -10,7 +10,7 @@ class << self # @param email_id [String] The ID of the received email # @param params [Hash] Optional query parameters # @option params [String] :html_format Format of the HTML content (e.g., "sanitized", "raw") - # @return [Hash] The received email object + # @return [Resend::Response] The received email object, including +message_id+ (RFC Message-ID header value) # # @example # Resend::Emails::Receiving.get("4ef9a417-02e9-4d39-ad75-9611e0fcc33c") diff --git a/lib/resend/webhooks.rb b/lib/resend/webhooks.rb index 82b3906..3ed0e5c 100644 --- a/lib/resend/webhooks.rb +++ b/lib/resend/webhooks.rb @@ -9,6 +9,9 @@ module Resend # # Default tolerance for timestamp validation (5 minutes) WEBHOOK_TOLERANCE_SECONDS = 300 + + # Webhook event payloads include +message_id+ on all email events. + # See +Resend::Webhooks+ RBS types in +sig/resend/webhooks.rbs+ for the full payload shapes. # # @example Create a webhook # Resend::Webhooks.create( diff --git a/resend.gemspec b/resend.gemspec index f09cf9b..9f931d4 100644 --- a/resend.gemspec +++ b/resend.gemspec @@ -12,7 +12,7 @@ Gem::Specification.new do |spec| spec.author = "Derich Pacheco" spec.email = "carlosderich@gmail.com" - spec.files = Dir["*.{md,txt}", "{lib}/**/*"] + spec.files = Dir["*.{md,txt}", "{lib}/**/*", "sig/**/*.rbs"] spec.require_path = "lib" spec.required_ruby_version = ">= 3.2" spec.add_dependency "base64" diff --git a/sig/resend.rbs b/sig/resend.rbs index 35c5a9f..9fe3047 100644 --- a/sig/resend.rbs +++ b/sig/resend.rbs @@ -1,4 +1,13 @@ module Resend VERSION: String - # See the writing guide of rbs: https://github.com/ruby/rbs#guides + + def self.api_key: () -> String? + def self.api_key=: (String api_key) -> String + def self.configure: () { (Resend) -> void } -> bool + alias config configure end + +require "resend/response" +require "resend/emails" +require "resend/emails/receiving" +require "resend/webhooks" diff --git a/sig/resend/emails.rbs b/sig/resend/emails.rbs new file mode 100644 index 0000000..abb7059 --- /dev/null +++ b/sig/resend/emails.rbs @@ -0,0 +1,62 @@ +module Resend + module Emails + type email_tag = { name: String, value: String } + + type email_last_event = + | "bounced" + | "canceled" + | "clicked" + | "complained" + | "delivered" + | "delivery_delayed" + | "failed" + | "opened" + | "queued" + | "scheduled" + | "sent" + | "suppressed" + + type get_email_response = { + object: "email", + id: String, + message_id: String, + to: Array[String], + from: String, + created_at: String, + subject: String, + html: String?, + text: String?, + bcc: Array[String]?, + cc: Array[String]?, + reply_to: Array[String]?, + last_event: email_last_event, + scheduled_at: String?, + tags?: Array[email_tag], + topic_id?: String?, + } + + type list_email = { + id: String, + message_id: String, + to: Array[String], + from: String, + created_at: String, + subject: String, + last_event: email_last_event, + scheduled_at: String?, + topic_id?: String?, + } + + type list_emails_response = { + object: "list", + has_more: bool, + data: Array[list_email], + } + + def self.send: (Hash[Symbol, untyped] params, ?options: Hash[Symbol, untyped]) -> Resend::Response + def self.get: (String email_id) -> (Resend::Response & get_email_response) + def self.update: (Hash[Symbol, untyped] params) -> Resend::Response + def self.cancel: (String email_id) -> Resend::Response + def self.list: (?limit: Integer?, ?after: String?, ?before: String?) -> (Resend::Response & list_emails_response) + end +end diff --git a/sig/resend/emails/receiving.rbs b/sig/resend/emails/receiving.rbs new file mode 100644 index 0000000..db25e1e --- /dev/null +++ b/sig/resend/emails/receiving.rbs @@ -0,0 +1,59 @@ +module Resend + module Emails + module Receiving + type inbound_attachment = { + id: String, + filename: String?, + size: Integer, + content_type: String, + content_id: String?, + content_disposition: String?, + } + + type get_receiving_email_response = { + object: "email", + id: String, + to: Array[String], + from: String, + created_at: String, + subject: String, + bcc: Array[String]?, + cc: Array[String]?, + reply_to: Array[String]?, + received_for: Array[String], + html: String?, + text: String?, + headers: Hash[String, String]?, + message_id: String, + raw?: { + download_url: String, + expires_at: String, + }?, + attachments: Array[inbound_attachment], + } + + type list_receiving_email = { + id: String, + to: Array[String], + from: String, + created_at: String, + subject: String, + bcc: Array[String], + cc: Array[String], + reply_to: Array[String], + received_for: Array[String], + message_id: String, + attachments: Array[inbound_attachment], + } + + type list_receiving_emails_response = { + object: "list", + has_more: bool, + data: Array[list_receiving_email], + } + + def self.get: (String email_id, ?Hash[Symbol, untyped] params) -> (Resend::Response & get_receiving_email_response) + def self.list: (?limit: Integer?, ?after: String?, ?before: String?) -> (Resend::Response & list_receiving_emails_response) + end + end +end diff --git a/sig/resend/response.rbs b/sig/resend/response.rbs new file mode 100644 index 0000000..44bd806 --- /dev/null +++ b/sig/resend/response.rbs @@ -0,0 +1,19 @@ +module Resend + class Response + include Enumerable[untyped] + + attr_reader headers: Hash[String, String] + + def initialize: (Hash[Symbol | String, untyped] data, Hash[Symbol | String, untyped] | HTTParty::Response? headers) -> void + def []: (Symbol | String key) -> untyped + def []=: (Symbol | String key, untyped value) -> untyped + def dig: (*Symbol | String keys) -> untyped + def to_h: () -> Hash[Symbol | String, untyped] + alias to_hash to_h + def keys: () -> Array[Symbol | String] + def values: () -> Array[untyped] + def key?: (Symbol | String key) -> bool + def each: () { (?(Symbol | String), untyped) -> void } -> self + def empty?: () -> bool + end +end diff --git a/sig/resend/webhooks.rbs b/sig/resend/webhooks.rbs new file mode 100644 index 0000000..84242d0 --- /dev/null +++ b/sig/resend/webhooks.rbs @@ -0,0 +1,236 @@ +module Resend + module Webhooks + type webhook_event = + | "email.sent" + | "email.scheduled" + | "email.delivered" + | "email.delivery_delayed" + | "email.complained" + | "email.bounced" + | "email.opened" + | "email.clicked" + | "email.received" + | "email.failed" + | "email.suppressed" + | "contact.created" + | "contact.updated" + | "contact.deleted" + | "domain.created" + | "domain.updated" + | "domain.deleted" + + type base_email_event_data = { + broadcast_id?: String, + created_at: String, + email_id: String, + message_id: String, + from: String, + to: Array[String], + subject: String, + template_id?: String, + tags?: Hash[String, String], + } + + type email_bounce = { + message: String, + subType: String, + type: String, + } + + type email_click = { + ipAddress: String, + link: String, + timestamp: String, + userAgent: String, + } + + type email_failed = { + reason: String, + } + + type email_suppressed = { + message: String, + type: String, + } + + type received_email_attachment = { + id: String, + filename: String?, + content_type: String, + content_disposition: String?, + content_id: String?, + } + + type received_email_event_data = { + email_id: String, + created_at: String, + from: String, + to: Array[String], + bcc: Array[String], + cc: Array[String], + received_for: Array[String], + message_id: String, + subject: String, + attachments: Array[received_email_attachment], + } + + type contact_event_data = { + id: String, + audience_id: String, + segment_ids: Array[String], + created_at: String, + updated_at: String, + email: String, + first_name?: String, + last_name?: String, + unsubscribed: bool, + } + + type domain_record = { + record: String, + name: String, + type: String, + ttl: String, + status: String, + value: String, + priority?: Integer, + } + + type domain_event_data = { + id: String, + name: String, + status: String, + created_at: String, + region: String, + records: Array[domain_record], + } + + type email_sent_event = { + type: "email.sent", + created_at: String, + data: base_email_event_data, + } + + type email_scheduled_event = { + type: "email.scheduled", + created_at: String, + data: base_email_event_data, + } + + type email_delivered_event = { + type: "email.delivered", + created_at: String, + data: base_email_event_data, + } + + type email_delivery_delayed_event = { + type: "email.delivery_delayed", + created_at: String, + data: base_email_event_data, + } + + type email_complained_event = { + type: "email.complained", + created_at: String, + data: base_email_event_data, + } + + type email_bounced_event = { + type: "email.bounced", + created_at: String, + data: base_email_event_data & { bounce: email_bounce }, + } + + type email_opened_event = { + type: "email.opened", + created_at: String, + data: base_email_event_data, + } + + type email_clicked_event = { + type: "email.clicked", + created_at: String, + data: base_email_event_data & { click: email_click }, + } + + type email_received_event = { + type: "email.received", + created_at: String, + data: received_email_event_data, + } + + type email_failed_event = { + type: "email.failed", + created_at: String, + data: base_email_event_data & { failed: email_failed }, + } + + type email_suppressed_event = { + type: "email.suppressed", + created_at: String, + data: base_email_event_data & { suppressed: email_suppressed }, + } + + type contact_created_event = { + type: "contact.created", + created_at: String, + data: contact_event_data, + } + + type contact_updated_event = { + type: "contact.updated", + created_at: String, + data: contact_event_data, + } + + type contact_deleted_event = { + type: "contact.deleted", + created_at: String, + data: contact_event_data, + } + + type domain_created_event = { + type: "domain.created", + created_at: String, + data: domain_event_data, + } + + type domain_updated_event = { + type: "domain.updated", + created_at: String, + data: domain_event_data, + } + + type domain_deleted_event = { + type: "domain.deleted", + created_at: String, + data: domain_event_data, + } + + type webhook_event_payload = + | email_sent_event + | email_scheduled_event + | email_delivered_event + | email_delivery_delayed_event + | email_complained_event + | email_bounced_event + | email_opened_event + | email_clicked_event + | email_received_event + | email_failed_event + | email_suppressed_event + | contact_created_event + | contact_updated_event + | contact_deleted_event + | domain_created_event + | domain_updated_event + | domain_deleted_event + + def self.create: (Hash[Symbol, untyped] params) -> Resend::Response + def self.list: (?limit: Integer?, ?after: String?, ?before: String?) -> Resend::Response + def self.get: (String webhook_id) -> Resend::Response + def self.update: (Hash[Symbol, untyped] params) -> Resend::Response + def self.remove: (String webhook_id) -> Resend::Response + def self.verify: (payload: String, headers: Hash[Symbol, String], webhook_secret: String) -> bool + end +end diff --git a/spec/emails/receiving_spec.rb b/spec/emails/receiving_spec.rb index 1a5d297..1510da9 100644 --- a/spec/emails/receiving_spec.rb +++ b/spec/emails/receiving_spec.rb @@ -39,6 +39,7 @@ expect(email[:from]).to eql("Acme ") expect(email[:to]).to eql(["delivered@resend.dev"]) expect(email[:attachments].length).to eql(1) + expect(email[:message_id]).to eql("") end it "should call the correct API endpoint" do diff --git a/spec/emails_spec.rb b/spec/emails_spec.rb index 0aff5e1..bdf5c02 100644 --- a/spec/emails_spec.rb +++ b/spec/emails_spec.rb @@ -46,13 +46,15 @@ "bcc": [nil], "cc": [nil], "reply_to": [nil], - "last_event": "delivered" + "last_event": "delivered", + "message_id": "<111-222-333@email.example.com>" } allow(resp).to receive(:body).and_return(resp) allow(HTTParty).to receive(:send).and_return(resp) email = Resend::Emails.get(resp[:id]) expect(email[:subject]).to eql "Hello World" expect(email[:id]).to eql "4ef9a417-02e9-4d39-ad75-9611e0fcc33c" + expect(email[:message_id]).to eql "<111-222-333@email.example.com>" end it "cancels email" do @@ -217,7 +219,8 @@ "from" => "onboarding@resend.dev", "created_at" => "2023-04-03T22:13:42.674981+00:00", "subject" => "Hello World", - "last_event" => "delivered" + "last_event" => "delivered", + "message_id" => "<111-222-333@email.example.com>" } ] } diff --git a/spec/webhooks_spec.rb b/spec/webhooks_spec.rb index 35409a8..eb46a53 100644 --- a/spec/webhooks_spec.rb +++ b/spec/webhooks_spec.rb @@ -229,7 +229,20 @@ describe "verify" do let(:webhook_secret) { "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw" } - let(:payload) { '{"type":"email.sent","created_at":"2024-01-01T00:00:00.000Z"}' } + let(:payload) do + { + type: "email.delivered", + created_at: "2024-01-01T00:00:00.000Z", + data: { + email_id: "56761188-7520-42d8-8898-ff6fc54ce618", + message_id: "<111-222-333@email.example.com>", + created_at: "2024-01-01T00:00:00.000Z", + from: "onboarding@resend.dev", + to: ["delivered@resend.dev"], + subject: "Sending this example" + } + }.to_json + end let(:msg_id) { "msg_2Lh9KX9FZ5Z5Z5Z5Z5Z5Z5Z5Z" } let(:timestamp) { Time.now.to_i.to_s } From e93f1d8ee9b47e9e837818c0a97042fe095f1816 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 8 Jul 2026 19:11:08 +0000 Subject: [PATCH 2/2] refactor: scope PR to message_id test coverage only Revert the broad RBS webhook/email type definitions, gemspec sig packaging, gitignore change, and doc comments. The API already returns message_id; keep only the spec updates that assert it. Co-authored-by: cpenned --- .gitignore | 1 + lib/resend/emails.rb | 2 - lib/resend/emails/receiving.rb | 2 +- lib/resend/webhooks.rb | 3 - resend.gemspec | 2 +- sig/resend.rbs | 11 +- sig/resend/emails.rbs | 62 --------- sig/resend/emails/receiving.rbs | 59 -------- sig/resend/response.rbs | 19 --- sig/resend/webhooks.rbs | 236 -------------------------------- spec/webhooks_spec.rb | 15 +- 11 files changed, 5 insertions(+), 407 deletions(-) delete mode 100644 sig/resend/emails.rbs delete mode 100644 sig/resend/emails/receiving.rbs delete mode 100644 sig/resend/response.rbs delete mode 100644 sig/resend/webhooks.rbs diff --git a/.gitignore b/.gitignore index 7dec49e..2bddeb8 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ /pkg/ /spec/reports/ /tmp/ +/sig/ # rspec failure tracking .rspec_status diff --git a/lib/resend/emails.rb b/lib/resend/emails.rb index 9a45484..f233338 100644 --- a/lib/resend/emails.rb +++ b/lib/resend/emails.rb @@ -13,8 +13,6 @@ def send(params, options: {}) # Retrieve a single email. # see more: https://resend.com/docs/api-reference/emails/retrieve-email - # - # @return [Resend::Response] The email object, including +message_id+ (RFC Message-ID header value) def get(email_id = "") path = "emails/#{email_id}" Resend::Request.new(path, {}, "get").perform diff --git a/lib/resend/emails/receiving.rb b/lib/resend/emails/receiving.rb index 8952565..e78076c 100644 --- a/lib/resend/emails/receiving.rb +++ b/lib/resend/emails/receiving.rb @@ -10,7 +10,7 @@ class << self # @param email_id [String] The ID of the received email # @param params [Hash] Optional query parameters # @option params [String] :html_format Format of the HTML content (e.g., "sanitized", "raw") - # @return [Resend::Response] The received email object, including +message_id+ (RFC Message-ID header value) + # @return [Hash] The received email object # # @example # Resend::Emails::Receiving.get("4ef9a417-02e9-4d39-ad75-9611e0fcc33c") diff --git a/lib/resend/webhooks.rb b/lib/resend/webhooks.rb index 3ed0e5c..82b3906 100644 --- a/lib/resend/webhooks.rb +++ b/lib/resend/webhooks.rb @@ -9,9 +9,6 @@ module Resend # # Default tolerance for timestamp validation (5 minutes) WEBHOOK_TOLERANCE_SECONDS = 300 - - # Webhook event payloads include +message_id+ on all email events. - # See +Resend::Webhooks+ RBS types in +sig/resend/webhooks.rbs+ for the full payload shapes. # # @example Create a webhook # Resend::Webhooks.create( diff --git a/resend.gemspec b/resend.gemspec index 9f931d4..f09cf9b 100644 --- a/resend.gemspec +++ b/resend.gemspec @@ -12,7 +12,7 @@ Gem::Specification.new do |spec| spec.author = "Derich Pacheco" spec.email = "carlosderich@gmail.com" - spec.files = Dir["*.{md,txt}", "{lib}/**/*", "sig/**/*.rbs"] + spec.files = Dir["*.{md,txt}", "{lib}/**/*"] spec.require_path = "lib" spec.required_ruby_version = ">= 3.2" spec.add_dependency "base64" diff --git a/sig/resend.rbs b/sig/resend.rbs index 9fe3047..35c5a9f 100644 --- a/sig/resend.rbs +++ b/sig/resend.rbs @@ -1,13 +1,4 @@ module Resend VERSION: String - - def self.api_key: () -> String? - def self.api_key=: (String api_key) -> String - def self.configure: () { (Resend) -> void } -> bool - alias config configure + # See the writing guide of rbs: https://github.com/ruby/rbs#guides end - -require "resend/response" -require "resend/emails" -require "resend/emails/receiving" -require "resend/webhooks" diff --git a/sig/resend/emails.rbs b/sig/resend/emails.rbs deleted file mode 100644 index abb7059..0000000 --- a/sig/resend/emails.rbs +++ /dev/null @@ -1,62 +0,0 @@ -module Resend - module Emails - type email_tag = { name: String, value: String } - - type email_last_event = - | "bounced" - | "canceled" - | "clicked" - | "complained" - | "delivered" - | "delivery_delayed" - | "failed" - | "opened" - | "queued" - | "scheduled" - | "sent" - | "suppressed" - - type get_email_response = { - object: "email", - id: String, - message_id: String, - to: Array[String], - from: String, - created_at: String, - subject: String, - html: String?, - text: String?, - bcc: Array[String]?, - cc: Array[String]?, - reply_to: Array[String]?, - last_event: email_last_event, - scheduled_at: String?, - tags?: Array[email_tag], - topic_id?: String?, - } - - type list_email = { - id: String, - message_id: String, - to: Array[String], - from: String, - created_at: String, - subject: String, - last_event: email_last_event, - scheduled_at: String?, - topic_id?: String?, - } - - type list_emails_response = { - object: "list", - has_more: bool, - data: Array[list_email], - } - - def self.send: (Hash[Symbol, untyped] params, ?options: Hash[Symbol, untyped]) -> Resend::Response - def self.get: (String email_id) -> (Resend::Response & get_email_response) - def self.update: (Hash[Symbol, untyped] params) -> Resend::Response - def self.cancel: (String email_id) -> Resend::Response - def self.list: (?limit: Integer?, ?after: String?, ?before: String?) -> (Resend::Response & list_emails_response) - end -end diff --git a/sig/resend/emails/receiving.rbs b/sig/resend/emails/receiving.rbs deleted file mode 100644 index db25e1e..0000000 --- a/sig/resend/emails/receiving.rbs +++ /dev/null @@ -1,59 +0,0 @@ -module Resend - module Emails - module Receiving - type inbound_attachment = { - id: String, - filename: String?, - size: Integer, - content_type: String, - content_id: String?, - content_disposition: String?, - } - - type get_receiving_email_response = { - object: "email", - id: String, - to: Array[String], - from: String, - created_at: String, - subject: String, - bcc: Array[String]?, - cc: Array[String]?, - reply_to: Array[String]?, - received_for: Array[String], - html: String?, - text: String?, - headers: Hash[String, String]?, - message_id: String, - raw?: { - download_url: String, - expires_at: String, - }?, - attachments: Array[inbound_attachment], - } - - type list_receiving_email = { - id: String, - to: Array[String], - from: String, - created_at: String, - subject: String, - bcc: Array[String], - cc: Array[String], - reply_to: Array[String], - received_for: Array[String], - message_id: String, - attachments: Array[inbound_attachment], - } - - type list_receiving_emails_response = { - object: "list", - has_more: bool, - data: Array[list_receiving_email], - } - - def self.get: (String email_id, ?Hash[Symbol, untyped] params) -> (Resend::Response & get_receiving_email_response) - def self.list: (?limit: Integer?, ?after: String?, ?before: String?) -> (Resend::Response & list_receiving_emails_response) - end - end -end diff --git a/sig/resend/response.rbs b/sig/resend/response.rbs deleted file mode 100644 index 44bd806..0000000 --- a/sig/resend/response.rbs +++ /dev/null @@ -1,19 +0,0 @@ -module Resend - class Response - include Enumerable[untyped] - - attr_reader headers: Hash[String, String] - - def initialize: (Hash[Symbol | String, untyped] data, Hash[Symbol | String, untyped] | HTTParty::Response? headers) -> void - def []: (Symbol | String key) -> untyped - def []=: (Symbol | String key, untyped value) -> untyped - def dig: (*Symbol | String keys) -> untyped - def to_h: () -> Hash[Symbol | String, untyped] - alias to_hash to_h - def keys: () -> Array[Symbol | String] - def values: () -> Array[untyped] - def key?: (Symbol | String key) -> bool - def each: () { (?(Symbol | String), untyped) -> void } -> self - def empty?: () -> bool - end -end diff --git a/sig/resend/webhooks.rbs b/sig/resend/webhooks.rbs deleted file mode 100644 index 84242d0..0000000 --- a/sig/resend/webhooks.rbs +++ /dev/null @@ -1,236 +0,0 @@ -module Resend - module Webhooks - type webhook_event = - | "email.sent" - | "email.scheduled" - | "email.delivered" - | "email.delivery_delayed" - | "email.complained" - | "email.bounced" - | "email.opened" - | "email.clicked" - | "email.received" - | "email.failed" - | "email.suppressed" - | "contact.created" - | "contact.updated" - | "contact.deleted" - | "domain.created" - | "domain.updated" - | "domain.deleted" - - type base_email_event_data = { - broadcast_id?: String, - created_at: String, - email_id: String, - message_id: String, - from: String, - to: Array[String], - subject: String, - template_id?: String, - tags?: Hash[String, String], - } - - type email_bounce = { - message: String, - subType: String, - type: String, - } - - type email_click = { - ipAddress: String, - link: String, - timestamp: String, - userAgent: String, - } - - type email_failed = { - reason: String, - } - - type email_suppressed = { - message: String, - type: String, - } - - type received_email_attachment = { - id: String, - filename: String?, - content_type: String, - content_disposition: String?, - content_id: String?, - } - - type received_email_event_data = { - email_id: String, - created_at: String, - from: String, - to: Array[String], - bcc: Array[String], - cc: Array[String], - received_for: Array[String], - message_id: String, - subject: String, - attachments: Array[received_email_attachment], - } - - type contact_event_data = { - id: String, - audience_id: String, - segment_ids: Array[String], - created_at: String, - updated_at: String, - email: String, - first_name?: String, - last_name?: String, - unsubscribed: bool, - } - - type domain_record = { - record: String, - name: String, - type: String, - ttl: String, - status: String, - value: String, - priority?: Integer, - } - - type domain_event_data = { - id: String, - name: String, - status: String, - created_at: String, - region: String, - records: Array[domain_record], - } - - type email_sent_event = { - type: "email.sent", - created_at: String, - data: base_email_event_data, - } - - type email_scheduled_event = { - type: "email.scheduled", - created_at: String, - data: base_email_event_data, - } - - type email_delivered_event = { - type: "email.delivered", - created_at: String, - data: base_email_event_data, - } - - type email_delivery_delayed_event = { - type: "email.delivery_delayed", - created_at: String, - data: base_email_event_data, - } - - type email_complained_event = { - type: "email.complained", - created_at: String, - data: base_email_event_data, - } - - type email_bounced_event = { - type: "email.bounced", - created_at: String, - data: base_email_event_data & { bounce: email_bounce }, - } - - type email_opened_event = { - type: "email.opened", - created_at: String, - data: base_email_event_data, - } - - type email_clicked_event = { - type: "email.clicked", - created_at: String, - data: base_email_event_data & { click: email_click }, - } - - type email_received_event = { - type: "email.received", - created_at: String, - data: received_email_event_data, - } - - type email_failed_event = { - type: "email.failed", - created_at: String, - data: base_email_event_data & { failed: email_failed }, - } - - type email_suppressed_event = { - type: "email.suppressed", - created_at: String, - data: base_email_event_data & { suppressed: email_suppressed }, - } - - type contact_created_event = { - type: "contact.created", - created_at: String, - data: contact_event_data, - } - - type contact_updated_event = { - type: "contact.updated", - created_at: String, - data: contact_event_data, - } - - type contact_deleted_event = { - type: "contact.deleted", - created_at: String, - data: contact_event_data, - } - - type domain_created_event = { - type: "domain.created", - created_at: String, - data: domain_event_data, - } - - type domain_updated_event = { - type: "domain.updated", - created_at: String, - data: domain_event_data, - } - - type domain_deleted_event = { - type: "domain.deleted", - created_at: String, - data: domain_event_data, - } - - type webhook_event_payload = - | email_sent_event - | email_scheduled_event - | email_delivered_event - | email_delivery_delayed_event - | email_complained_event - | email_bounced_event - | email_opened_event - | email_clicked_event - | email_received_event - | email_failed_event - | email_suppressed_event - | contact_created_event - | contact_updated_event - | contact_deleted_event - | domain_created_event - | domain_updated_event - | domain_deleted_event - - def self.create: (Hash[Symbol, untyped] params) -> Resend::Response - def self.list: (?limit: Integer?, ?after: String?, ?before: String?) -> Resend::Response - def self.get: (String webhook_id) -> Resend::Response - def self.update: (Hash[Symbol, untyped] params) -> Resend::Response - def self.remove: (String webhook_id) -> Resend::Response - def self.verify: (payload: String, headers: Hash[Symbol, String], webhook_secret: String) -> bool - end -end diff --git a/spec/webhooks_spec.rb b/spec/webhooks_spec.rb index eb46a53..35409a8 100644 --- a/spec/webhooks_spec.rb +++ b/spec/webhooks_spec.rb @@ -229,20 +229,7 @@ describe "verify" do let(:webhook_secret) { "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw" } - let(:payload) do - { - type: "email.delivered", - created_at: "2024-01-01T00:00:00.000Z", - data: { - email_id: "56761188-7520-42d8-8898-ff6fc54ce618", - message_id: "<111-222-333@email.example.com>", - created_at: "2024-01-01T00:00:00.000Z", - from: "onboarding@resend.dev", - to: ["delivered@resend.dev"], - subject: "Sending this example" - } - }.to_json - end + let(:payload) { '{"type":"email.sent","created_at":"2024-01-01T00:00:00.000Z"}' } let(:msg_id) { "msg_2Lh9KX9FZ5Z5Z5Z5Z5Z5Z5Z5Z" } let(:timestamp) { Time.now.to_i.to_s }