diff --git a/examples/receiving_emails.rb b/examples/receiving_emails.rb new file mode 100644 index 0000000..acb5716 --- /dev/null +++ b/examples/receiving_emails.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: true + +require_relative "../lib/resend" + +raise if ENV["RESEND_API_KEY"].nil? + +Resend.api_key = ENV["RESEND_API_KEY"] + +puts "=== Listing Received Emails ===" + +puts "\nListing all received emails:" +emails = Resend::Emails::Receiving.list + +puts "Total emails in response: #{emails[:data].length}" +puts "Has more: #{emails[:has_more]}" + +emails[:data].each do |e| + puts " - #{e["id"]}: #{e["subject"]} from #{e["from"]}" + puts " Created: #{e["created_at"]}" + if e["attachments"] && !e["attachments"].empty? + puts " Attachments: #{e["attachments"].length}" + end +end + +puts "\n\nListing with limit of 5:" +limited_emails = Resend::Emails::Receiving.list(limit: 5) + +puts "Retrieved #{limited_emails[:data].length} emails" +puts "Has more: #{limited_emails[:has_more]}" + +# Example of pagination (if you have more emails) +if limited_emails[:has_more] && limited_emails[:data].last + last_id = limited_emails[:data].last["id"] + puts "\n\nGetting next page after ID: #{last_id}" + next_page = Resend::Emails::Receiving.list(limit: 5, after: last_id) + puts "Next page has #{next_page[:data].length} emails" +end + +puts "\n\n=== Retrieving Single Received Email ===" + +# Use the first email from the list, or specify a known ID +if emails[:data] && emails[:data].first + email_id = emails[:data].first["id"] +else + # Replace with an actual received email ID from your account + email_id = "006e2796-ff6a-4436-91ad-0429e600bf8a" +end + +email = Resend::Emails::Receiving.get(email_id) + +puts "\nEmail Details:" +puts " ID: #{email[:id]}" +puts " From: #{email[:from]}" +puts " To: #{email[:to].join(', ')}" +puts " Subject: #{email[:subject]}" +puts " Created At: #{email[:created_at]}" +puts " Message ID: #{email[:message_id]}" + +if email[:cc] && !email[:cc].empty? + puts " CC: #{email[:cc].join(', ')}" +end + +if email[:bcc] && !email[:bcc].empty? + puts " BCC: #{email[:bcc].join(', ')}" +end + +if email[:attachments] && !email[:attachments].empty? + puts "\n Attachments:" + email[:attachments].each do |attachment| + puts " - #{attachment["filename"]} (#{attachment["content_type"]})" + puts " ID: #{attachment["id"]}" + puts " Size: #{attachment["size"]} bytes" if attachment["size"] + puts " Content ID: #{attachment["content_id"]}" if attachment["content_id"] + end + + puts "\n Listing all attachments for email: #{email[:id]}" + attachments_list = Resend::Attachments::Receiving.list( + email_id: email[:id] + ) + + puts " Total attachments: #{attachments_list[:data].length}" + puts " Has more: #{attachments_list[:has_more]}" + + # Retrieve full attachment details for the first attachment + if email[:attachments].first + first_attachment_id = email[:attachments].first["id"] + puts "\n Retrieving full attachment details for: #{first_attachment_id}" + + attachment_details = Resend::Attachments::Receiving.get( + id: first_attachment_id, + email_id: email[:id] + ) + + puts " Download URL: #{attachment_details[:download_url]}" + puts " Expires At: #{attachment_details[:expires_at]}" + end +end + +puts "\n HTML Content:" +puts " #{email[:html][0..100]}..." if email[:html] + +puts "\n Text Content:" +puts " #{email[:text]}" if email[:text] diff --git a/lib/resend.rb b/lib/resend.rb index f254b27..3a638dc 100644 --- a/lib/resend.rb +++ b/lib/resend.rb @@ -20,6 +20,8 @@ require "resend/contacts" require "resend/domains" require "resend/emails" +require "resend/emails/receiving" +require "resend/attachments/receiving" require "resend/topics" # Rails diff --git a/lib/resend/attachments/receiving.rb b/lib/resend/attachments/receiving.rb new file mode 100644 index 0000000..47aada1 --- /dev/null +++ b/lib/resend/attachments/receiving.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +module Resend + module Attachments + # Module for receiving email attachments API operations + module Receiving + class << self + # Retrieve a single attachment from a received email + # + # @param params [Hash] Parameters for retrieving the attachment + # @option params [String] :id The attachment ID (required) + # @option params [String] :email_id The email ID (required) + # @return [Hash] The attachment object + # + # @example + # Resend::Attachments::Receiving.get( + # id: "2a0c9ce0-3112-4728-976e-47ddcd16a318", + # email_id: "4ef9a417-02e9-4d39-ad75-9611e0fcc33c" + # ) + def get(params = {}) + attachment_id = params[:id] + email_id = params[:email_id] + + path = "emails/receiving/#{email_id}/attachments/#{attachment_id}" + Resend::Request.new(path, {}, "get").perform + end + + # List attachments from a received email with optional pagination + # + # @param params [Hash] Parameters for listing attachments + # @option params [String] :email_id The email ID (required) + # @option params [Integer] :limit Maximum number of attachments to return (1-100) + # @option params [String] :after Cursor for pagination (newer attachments) + # @option params [String] :before Cursor for pagination (older attachments) + # @return [Hash] List of attachments with pagination info + # + # @example List all attachments + # Resend::Attachments::Receiving.list( + # email_id: "4ef9a417-02e9-4d39-ad75-9611e0fcc33c" + # ) + # + # @example List with custom limit + # Resend::Attachments::Receiving.list( + # email_id: "4ef9a417-02e9-4d39-ad75-9611e0fcc33c", + # limit: 50 + # ) + # + # @example List with pagination + # Resend::Attachments::Receiving.list( + # email_id: "4ef9a417-02e9-4d39-ad75-9611e0fcc33c", + # limit: 20, + # after: "attachment_id_123" + # ) + def list(params = {}) + email_id = params[:email_id] + path = "emails/receiving/#{email_id}/attachments" + + # Build query parameters, filtering out nil values + query_params = {} + query_params[:limit] = params[:limit] if params[:limit] + query_params[:after] = params[:after] if params[:after] + query_params[:before] = params[:before] if params[:before] + + Resend::Request.new(path, query_params, "get").perform + end + end + end + end +end diff --git a/lib/resend/emails/receiving.rb b/lib/resend/emails/receiving.rb new file mode 100644 index 0000000..22f2b4d --- /dev/null +++ b/lib/resend/emails/receiving.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module Resend + module Emails + # Module for receiving emails API operations + module Receiving + class << self + # Retrieve a single received email + # + # @param email_id [String] The ID of the received email + # @return [Hash] The received email object + # + # @example + # Resend::Emails::Receiving.get("4ef9a417-02e9-4d39-ad75-9611e0fcc33c") + def get(email_id = "") + path = "emails/receiving/#{email_id}" + Resend::Request.new(path, {}, "get").perform + end + + # List received emails with optional pagination + # + # @param params [Hash] Optional parameters for pagination + # @option params [Integer] :limit Maximum number of emails to return (1-100) + # @option params [String] :after Cursor for pagination (newer emails) + # @option params [String] :before Cursor for pagination (older emails) + # @return [Hash] List of received emails with pagination info + # + # @example List all received emails + # Resend::Emails::Receiving.list + # + # @example List with custom limit + # Resend::Emails::Receiving.list(limit: 50) + # + # @example List with pagination + # Resend::Emails::Receiving.list(limit: 20, after: "email_id_123") + def list(params = {}) + path = Resend::PaginationHelper.build_paginated_path("emails/receiving", params) + Resend::Request.new(path, {}, "get").perform + end + end + end + end +end diff --git a/spec/attachments/receiving_spec.rb b/spec/attachments/receiving_spec.rb new file mode 100644 index 0000000..c340ccd --- /dev/null +++ b/spec/attachments/receiving_spec.rb @@ -0,0 +1,192 @@ +# frozen_string_literal: true + +RSpec.describe "Attachments::Receiving" do + before do + Resend.api_key = "re_123" + end + + describe "get" do + it "should retrieve an attachment from a received email" do + resp = { + "object" => "attachment", + "id" => "2a0c9ce0-3112-4728-976e-47ddcd16a318", + "filename" => "avatar.png", + "content_type" => "image/png", + "content_disposition" => "inline", + "content_id" => "img001", + "download_url" => "https://inbound-cdn.resend.com/4ef9a417-02e9-4d39-ad75-9611e0fcc33c/attachments/2a0c9ce0-3112-4728-976e-47ddcd16a318?some-params=example&signature=sig-123", + "expires_at" => "2025-10-17T14:29:41.521Z" + } + allow(resp).to receive(:body).and_return(resp) + allow(HTTParty).to receive(:send).and_return(resp) + + attachment = Resend::Attachments::Receiving.get( + id: "2a0c9ce0-3112-4728-976e-47ddcd16a318", + email_id: "4ef9a417-02e9-4d39-ad75-9611e0fcc33c" + ) + + expect(attachment[:object]).to eql("attachment") + expect(attachment[:id]).to eql("2a0c9ce0-3112-4728-976e-47ddcd16a318") + expect(attachment[:filename]).to eql("avatar.png") + expect(attachment[:content_type]).to eql("image/png") + expect(attachment[:download_url]).to include("inbound-cdn.resend.com") + end + + it "should call the correct API endpoint" do + resp = { + "object" => "attachment", + "id" => "2a0c9ce0-3112-4728-976e-47ddcd16a318" + } + allow(resp).to receive(:body).and_return(resp) + allow(HTTParty).to receive(:send).and_return(resp) + + Resend::Attachments::Receiving.get( + id: "2a0c9ce0-3112-4728-976e-47ddcd16a318", + email_id: "4ef9a417-02e9-4d39-ad75-9611e0fcc33c" + ) + + expect(HTTParty).to have_received(:send).with( + :get, + "#{Resend::Request::BASE_URL}emails/receiving/4ef9a417-02e9-4d39-ad75-9611e0fcc33c/attachments/2a0c9ce0-3112-4728-976e-47ddcd16a318", + hash_including( + headers: { + "Content-Type" => "application/json", + "Accept" => "application/json", + "Authorization" => "Bearer re_123", + "User-Agent" => "resend-ruby:#{Resend::VERSION}" + } + ) + ) + end + + it "should raise an error when attachment is not found" do + resp = { + "statusCode" => 404, + "name" => "not_found", + "message" => "Attachment not found" + } + allow(resp).to receive(:body).and_return(resp) + allow(HTTParty).to receive(:send).and_return(resp) + + expect { + Resend::Attachments::Receiving.get( + id: "invalid_id", + email_id: "invalid_email_id" + ) + }.to raise_error( + Resend::Error::InvalidRequestError, + /Attachment not found/ + ) + end + end + + describe "list" do + it "should list attachments from a received email without parameters" do + resp = { + "object" => "list", + "has_more" => false, + "data" => [ + { + "id" => "2a0c9ce0-3112-4728-976e-47ddcd16a318", + "filename" => "avatar.png", + "content_type" => "image/png", + "content_disposition" => "inline", + "content_id" => "img001", + "download_url" => "https://inbound-cdn.resend.com/4ef9a417-02e9-4d39-ad75-9611e0fcc33c/attachments/2a0c9ce0-3112-4728-976e-47ddcd16a318?some-params=example&signature=sig-123", + "expires_at" => "2025-10-17T14:29:41.521Z" + } + ] + } + allow(resp).to receive(:body).and_return(resp) + allow(HTTParty).to receive(:send).and_return(resp) + + result = Resend::Attachments::Receiving.list( + email_id: "4ef9a417-02e9-4d39-ad75-9611e0fcc33c" + ) + + expect(result[:object]).to eql("list") + expect(result[:has_more]).to eql(false) + expect(result[:data].length).to eql(1) + expect(result[:data].first["filename"]).to eql("avatar.png") + end + + it "should list attachments with limit parameter" do + resp = { + "object" => "list", + "has_more" => false, + "data" => [] + } + allow(resp).to receive(:body).and_return(resp) + allow(HTTParty).to receive(:send).and_return(resp) + + result = Resend::Attachments::Receiving.list( + email_id: "4ef9a417-02e9-4d39-ad75-9611e0fcc33c", + limit: 50 + ) + + expect(HTTParty).to have_received(:send).with( + :get, + "#{Resend::Request::BASE_URL}emails/receiving/4ef9a417-02e9-4d39-ad75-9611e0fcc33c/attachments", + hash_including( + query: { limit: 50 } + ) + ) + expect(result[:object]).to eql("list") + expect(result[:has_more]).to eql(false) + end + + it "should list attachments with pagination parameters" do + resp = { + "object" => "list", + "has_more" => true, + "data" => [] + } + allow(resp).to receive(:body).and_return(resp) + allow(HTTParty).to receive(:send).and_return(resp) + + result = Resend::Attachments::Receiving.list( + email_id: "4ef9a417-02e9-4d39-ad75-9611e0fcc33c", + limit: 20, + after: "cursor_123", + before: "cursor_456" + ) + + expect(HTTParty).to have_received(:send).with( + :get, + "#{Resend::Request::BASE_URL}emails/receiving/4ef9a417-02e9-4d39-ad75-9611e0fcc33c/attachments", + hash_including( + query: { limit: 20, after: "cursor_123", before: "cursor_456" } + ) + ) + expect(result[:object]).to eql("list") + expect(result[:has_more]).to eql(true) + end + + it "should call the correct API endpoint for list" do + resp = { + "object" => "list", + "has_more" => false, + "data" => [] + } + allow(resp).to receive(:body).and_return(resp) + allow(HTTParty).to receive(:send).and_return(resp) + + Resend::Attachments::Receiving.list( + email_id: "4ef9a417-02e9-4d39-ad75-9611e0fcc33c" + ) + + expect(HTTParty).to have_received(:send).with( + :get, + "#{Resend::Request::BASE_URL}emails/receiving/4ef9a417-02e9-4d39-ad75-9611e0fcc33c/attachments", + hash_including( + headers: { + "Content-Type" => "application/json", + "Accept" => "application/json", + "Authorization" => "Bearer re_123", + "User-Agent" => "resend-ruby:#{Resend::VERSION}" + } + ) + ) + end + end +end diff --git a/spec/emails/receiving_spec.rb b/spec/emails/receiving_spec.rb new file mode 100644 index 0000000..b2b1f89 --- /dev/null +++ b/spec/emails/receiving_spec.rb @@ -0,0 +1,181 @@ +# frozen_string_literal: true + +RSpec.describe "Emails::Receiving" do + before do + Resend.api_key = "re_123" + end + + describe "get" do + it "should retrieve a received email" do + resp = { + "object" => "email", + "id" => "4ef9a417-02e9-4d39-ad75-9611e0fcc33c", + "to" => ["delivered@resend.dev"], + "from" => "Acme ", + "created_at" => "2023-04-03T22:13:42.674981+00:00", + "subject" => "Hello World", + "html" => "Congrats on sending your first email!", + "text" => nil, + "bcc" => [], + "cc" => [], + "reply_to" => [], + "message_id" => "", + "attachments" => [ + { + "id" => "2a0c9ce0-3112-4728-976e-47ddcd16a318", + "filename" => "avatar.png", + "content_type" => "image/png", + "content_disposition" => "inline", + "content_id" => "img001" + } + ] + } + allow(resp).to receive(:body).and_return(resp) + allow(HTTParty).to receive(:send).and_return(resp) + + email = Resend::Emails::Receiving.get(resp["id"]) + expect(email[:subject]).to eql("Hello World") + expect(email[:id]).to eql("4ef9a417-02e9-4d39-ad75-9611e0fcc33c") + expect(email[:from]).to eql("Acme ") + expect(email[:to]).to eql(["delivered@resend.dev"]) + expect(email[:attachments].length).to eql(1) + end + + it "should call the correct API endpoint" do + resp = { + "object" => "email", + "id" => "4ef9a417-02e9-4d39-ad75-9611e0fcc33c" + } + allow(resp).to receive(:body).and_return(resp) + allow(HTTParty).to receive(:send).and_return(resp) + + Resend::Emails::Receiving.get("4ef9a417-02e9-4d39-ad75-9611e0fcc33c") + + expect(HTTParty).to have_received(:send).with( + :get, + "#{Resend::Request::BASE_URL}emails/receiving/4ef9a417-02e9-4d39-ad75-9611e0fcc33c", + hash_including( + headers: { + "Content-Type" => "application/json", + "Accept" => "application/json", + "Authorization" => "Bearer re_123", + "User-Agent" => "resend-ruby:#{Resend::VERSION}" + } + ) + ) + end + + it "should raise an error when email is not found" do + resp = { + "statusCode" => 404, + "name" => "not_found", + "message" => "Email not found" + } + allow(resp).to receive(:body).and_return(resp) + allow(HTTParty).to receive(:send).and_return(resp) + + expect { Resend::Emails::Receiving.get("invalid_id") }.to raise_error( + Resend::Error::InvalidRequestError, + /Email not found/ + ) + end + end + + describe "list" do + it "should list received emails without parameters" do + resp = { + "object" => "list", + "has_more" => true, + "data" => [ + { + "id" => "a39999a6-88e3-48b1-888b-beaabcde1b33", + "to" => ["recipient@example.com"], + "from" => "sender@example.com", + "created_at" => "2025-10-09 14:37:40.951732+00", + "subject" => "Hello World", + "bcc" => [], + "cc" => [], + "reply_to" => [], + "message_id" => "<111-222-333@email.provider.example.com>", + "attachments" => [ + { + "filename" => "example.txt", + "content_type" => "text/plain", + "content_id" => nil, + "content_disposition" => "attachment", + "id" => "47e999c7-c89c-4999-bf32-aaaaa1c3ff21", + "size" => 13 + } + ] + } + ] + } + allow(resp).to receive(:body).and_return(resp) + allow(HTTParty).to receive(:send).and_return(resp) + + result = Resend::Emails::Receiving.list + expect(result[:object]).to eql("list") + expect(result[:has_more]).to eql(true) + expect(result[:data].length).to eql(1) + expect(result[:data].first["subject"]).to eql("Hello World") + end + + it "should list received emails with limit parameter" do + resp = { + "object": "list", + "has_more": false, + "data": [] + } + + request_instance = instance_double(Resend::Request) + allow(request_instance).to receive(:perform).and_return(resp) + allow(Resend::Request).to receive(:new) do |path, body, verb| + expect(path).to include("limit=50") + request_instance + end + + result = Resend::Emails::Receiving.list(limit: 50) + expect(result[:object]).to eql("list") + expect(result[:has_more]).to eql(false) + end + + it "should list received emails with pagination parameters" do + resp = { + "object": "list", + "has_more": true, + "data": [] + } + + request_instance = instance_double(Resend::Request) + allow(request_instance).to receive(:perform).and_return(resp) + allow(Resend::Request).to receive(:new) do |path, body, verb| + expect(path).to include("limit=20") + expect(path).to include("after=cursor_123") + expect(path).to include("before=cursor_456") + request_instance + end + + result = Resend::Emails::Receiving.list(limit: 20, after: "cursor_123", before: "cursor_456") + expect(result[:object]).to eql("list") + expect(result[:has_more]).to eql(true) + end + + it "should call the correct API endpoint for list" do + resp = { + "object" => "list", + "has_more" => false, + "data" => [] + } + + request_instance = instance_double(Resend::Request) + allow(request_instance).to receive(:perform).and_return(resp) + allow(Resend::Request).to receive(:new) do |path, body, verb| + expect(path).to eq("emails/receiving") + expect(verb).to eq("get") + request_instance + end + + Resend::Emails::Receiving.list + end + end +end