Skip to content
Merged
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
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,13 @@ Email Sandbox (Testing):
- Sandbox Messages CRUD - [`sandbox_messages_api.rb`](examples/sandbox_messages_api.rb)
- Sandbox Attachments API - [`sandbox_attachments_api.rb`](examples/sandbox_attachments_api.rb)

Inbound Email:

- Inbound Folders API – [`inbound_folders_api.rb`](examples/inbound_folders_api.rb)
- Inbound Inboxes API – [`inbound_inboxes_api.rb`](examples/inbound_inboxes_api.rb)
- Inbound Messages API (list, get, delete, reply, reply_all, forward) – [`inbound_messages_api.rb`](examples/inbound_messages_api.rb)
- Inbound Threads API – [`inbound_threads_api.rb`](examples/inbound_threads_api.rb)

Contact management:

- Contacts CRUD & Listing – [`contacts_api.rb`](examples/contacts_api.rb)
Expand Down
24 changes: 24 additions & 0 deletions examples/inbound_folders_api.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
require 'mailtrap'

client = Mailtrap::Client.new(api_key: 'your-api-key')
folders = Mailtrap::InboundFoldersAPI.new(client)

# Create a folder
folder = folders.create(name: 'Support')
# => #<struct Mailtrap::InboundFolder id=1, name="Support">

# List all folders
folders.list
# => [#<struct Mailtrap::InboundFolder id=1, name="Support">]

# Get a folder
folders.get(folder.id)
# => #<struct Mailtrap::InboundFolder id=1, name="Support">

# Update a folder
folders.update(folder.id, name: 'Customer Success')
# => #<struct Mailtrap::InboundFolder id=1, name="Customer Success">

# Delete a folder (and all of its inboxes)
folders.delete(folder.id)
# => nil
25 changes: 25 additions & 0 deletions examples/inbound_inboxes_api.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
require 'mailtrap'

client = Mailtrap::Client.new(api_key: 'your-api-key')
inboxes = Mailtrap::InboundInboxesAPI.new(client)
folder_id = 1

# Create an inbox. Omit domain_id for a Mailtrap-hosted inbox; pass it for a custom-domain inbox.
inbox = inboxes.create(folder_id, name: 'Tickets')
# => #<struct Mailtrap::InboundInbox id=42, name="Tickets", address="...@inbound-mailtrap.io", domain_id=892>

# List inboxes in a folder
inboxes.list(folder_id)
# => [#<struct Mailtrap::InboundInbox id=42, ...>]

# Get an inbox
inboxes.get(folder_id, inbox.id)
# => #<struct Mailtrap::InboundInbox id=42, ...>

# Update an inbox
inboxes.update(folder_id, inbox.id, name: 'Tickets (renamed)')
# => #<struct Mailtrap::InboundInbox id=42, name="Tickets (renamed)", ...>

# Delete an inbox
inboxes.delete(folder_id, inbox.id)
# => nil
32 changes: 32 additions & 0 deletions examples/inbound_messages_api.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
require 'mailtrap'

client = Mailtrap::Client.new(api_key: 'your-api-key')
messages = Mailtrap::InboundMessagesAPI.new(client)
inbox_id = 42

# List messages in an inbox (cursor-paginated)
page = messages.list(inbox_id)
# => #<struct Mailtrap::InboundMessagesListResponse data=[...], total_count=1, last_id=nil>

# Fetch the next page with the returned cursor
messages.list(inbox_id, last_id: page.last_id) if page.last_id

# Get a single message with its body and attachment download URLs
message = messages.get(inbox_id, page.data.first.id)
Comment thread
mklocek marked this conversation as resolved.
# => #<struct Mailtrap::InboundMessage id="1700000000000123", ...>

# Reply to the original sender
messages.reply(inbox_id, message.id, text: 'Thanks for reaching out.')
# => #<struct Mailtrap::InboundSendResult message_ids=["1a2b3c4d-..."]>

# Reply to everyone on the original message
messages.reply_all(inbox_id, message.id, text: 'Looping everyone in.')
# => #<struct Mailtrap::InboundSendResult message_ids=["1a2b3c4d-..."]>

# Forward to new recipients (at least one `to` is required)
messages.forward(inbox_id, message.id, to: [{ email: 'colleague@example.com' }], text: 'Please take a look.')
# => #<struct Mailtrap::InboundSendResult message_ids=["1a2b3c4d-..."]>

# Delete a message
messages.delete(inbox_id, message.id)
# => nil
20 changes: 20 additions & 0 deletions examples/inbound_threads_api.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
require 'mailtrap'

client = Mailtrap::Client.new(api_key: 'your-api-key')
threads = Mailtrap::InboundThreadsAPI.new(client)
inbox_id = 42

# List threads in an inbox (cursor-paginated)
page = threads.list(inbox_id)
# => #<struct Mailtrap::InboundThreadsListResponse data=[...], total_count=1, last_id=nil>

# Fetch the next page with the returned cursor
threads.list(inbox_id, last_id: page.last_id) if page.last_id

# Get a single thread with its messages embedded (oldest first)
threads.get(inbox_id, page.data.first.id)
# => #<struct Mailtrap::InboundThread id="1700000000000124", messages=[...]>

# Delete a thread
threads.delete(inbox_id, page.data.first.id)
Comment thread
mklocek marked this conversation as resolved.
# => nil
4 changes: 4 additions & 0 deletions lib/mailtrap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
require_relative 'mailtrap/stats_api'
require_relative 'mailtrap/webhooks_api'
require_relative 'mailtrap/webhooks'
require_relative 'mailtrap/inbound_folders_api'
require_relative 'mailtrap/inbound_inboxes_api'
require_relative 'mailtrap/inbound_messages_api'
require_relative 'mailtrap/inbound_threads_api'

module Mailtrap
# @!macro api_errors
Expand Down
8 changes: 8 additions & 0 deletions lib/mailtrap/email_log_message.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ module Mailtrap
# @attr_reader message_id [String] Message UUID
# @attr_reader status [String] delivered, not_delivered, enqueued, opted_out
# @attr_reader subject [String, nil] Email subject
# @attr_reader rfc_message_id [String, nil] Value of the RFC 5322 Message-ID header
# @attr_reader in_reply_to [String, nil] Value of the RFC 5322 In-Reply-To header
# @attr_reader references [Array<String>] Values of the RFC 5322 References header
# @attr_reader thread_id [String, nil] ID of the inbound thread this message belongs to, if any
# @attr_reader from [String] Sender address
# @attr_reader to [String] Recipient address
# @attr_reader sent_at [String] ISO 8601 timestamp
Expand All @@ -24,6 +28,10 @@ module Mailtrap
:message_id,
:status,
:subject,
:rfc_message_id,
:in_reply_to,
:references,
:thread_id,
:from,
:to,
:sent_at,
Expand Down
27 changes: 27 additions & 0 deletions lib/mailtrap/inbound_attachment.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

module Mailtrap
# Data Transfer Object for an inbound message attachment
# @see https://docs.mailtrap.io/developers/inbound
# @attr_reader attachment_id [String] The attachment ID
# @attr_reader size [Integer, nil] The attachment size in bytes
# @attr_reader filename [String, nil] The attachment filename
# @attr_reader content_type [String, nil] The attachment MIME type
# @attr_reader content_disposition [String, nil] attachment or inline
# @attr_reader content_id [String, nil] The content ID for inline attachments
# @attr_reader download_url [String, nil] Signed URL to download the attachment (only when fetched by ID)
# @attr_reader download_url_expires_at [String, nil] When the download URL expires (only when fetched by ID)
# rubocop:disable Lint/StructNewOverride -- +size+ is an API field that shadows Struct#size
InboundAttachment = Struct.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.

BTW i wonder if it would make more sense to use Data
... Data doesn't even have size :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good thought — Data would be a nice fit semantically and would drop the Lint/StructNewOverride disable. The snag is construction: we build these from partial API responses via .new(**hash.slice(*members)), and several fields (download_url, download_url_expires_at, …) are only present when a message is fetched by ID. Struct with keyword_init: true nil-defaults the missing keys for free, whereas Data requires every member and would need a custom initialize defaulting all eight — a bit more boilerplate than the one-line disable. And the rest of the SDK is uniformly Struct, so I lean toward keeping it consistent here. Happy to revisit a repo-wide Struct → Data migration separately if we want it.

:attachment_id,
:size,
:filename,
:content_type,
:content_disposition,
:content_id,
:download_url,
:download_url_expires_at,
keyword_init: true
)
# rubocop:enable Lint/StructNewOverride
end
13 changes: 13 additions & 0 deletions lib/mailtrap/inbound_folder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

module Mailtrap
# Data Transfer Object for an inbound folder
# @see https://docs.mailtrap.io/developers/inbound
# @attr_reader id [Integer] The folder ID
# @attr_reader name [String] The folder name
InboundFolder = Struct.new(
:id,
:name,
keyword_init: true
)
end
69 changes: 69 additions & 0 deletions lib/mailtrap/inbound_folders_api.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# frozen_string_literal: true

require_relative 'base_api'
require_relative 'inbound_folder'

module Mailtrap
class InboundFoldersAPI
include BaseAPI

self.supported_options = %i[name]
self.response_class = InboundFolder

# Inbound is scoped to the token's account, so no account_id is required.
# @param client [Mailtrap::Client] The client instance
def initialize(client = Mailtrap::Client.new)
@client = client
end

# Lists all inbound folders in the account
# @return [Array<InboundFolder>] Array of folders
# @!macro api_errors
def list
base_list
end

# Retrieves a specific inbound folder
# @param folder_id [Integer] The folder ID
# @return [InboundFolder] Folder object
# @!macro api_errors
def get(folder_id)
base_get(folder_id)
end

# Creates a new inbound folder
# @param [Hash] options The parameters to create
# @option options [String] :name The folder name
# @return [InboundFolder] Created folder
# @!macro api_errors
# @raise [ArgumentError] If invalid options are provided
def create(options)
base_create(options)
end

# Updates an inbound folder
# @param folder_id [Integer] The folder ID
# @param [Hash] options The parameters to update
# @option options [String] :name The folder name
# @return [InboundFolder] Updated folder
# @!macro api_errors
# @raise [ArgumentError] If invalid options are provided
def update(folder_id, options)
base_update(folder_id, options)
end

# Deletes an inbound folder along with all of its inboxes
# @param folder_id [Integer] The folder ID
# @return nil
# @!macro api_errors
def delete(folder_id)
base_delete(folder_id)
end

private

def base_path
'/api/inbound/folders'
end
end
end
17 changes: 17 additions & 0 deletions lib/mailtrap/inbound_inbox.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

module Mailtrap
# Data Transfer Object for an inbound inbox
# @see https://docs.mailtrap.io/developers/inbound
# @attr_reader id [Integer] The inbox ID
# @attr_reader name [String] The inbox name
# @attr_reader address [String] The inbox's inbound address
# @attr_reader domain_id [Integer] The sending domain the inbox is attached to
InboundInbox = Struct.new(
:id,
:name,
:address,
:domain_id,
keyword_init: true
)
end
79 changes: 79 additions & 0 deletions lib/mailtrap/inbound_inboxes_api.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# frozen_string_literal: true

require_relative 'base_api'
require_relative 'inbound_inbox'

module Mailtrap
class InboundInboxesAPI
include BaseAPI

self.response_class = InboundInbox

CREATE_OPTIONS = %i[name domain_id].freeze
UPDATE_OPTIONS = %i[name].freeze

# Inbound is scoped to the token's account, so no account_id is required.
# @param client [Mailtrap::Client] The client instance
def initialize(client = Mailtrap::Client.new)
@client = client
end

# Lists all inboxes in a folder
# @param folder_id [Integer] The folder ID
# @return [Array<InboundInbox>] Array of inboxes
# @!macro api_errors
def list(folder_id)
client.get(inboxes_path(folder_id)).map { |item| handle_response(item) }
end

# Retrieves a specific inbox
# @param folder_id [Integer] The folder ID
# @param inbox_id [Integer] The inbox ID
# @return [InboundInbox] Inbox object
# @!macro api_errors
def get(folder_id, inbox_id)
handle_response(client.get("#{inboxes_path(folder_id)}/#{inbox_id}"))
end

# Creates a new inbox in a folder
# @param folder_id [Integer] The folder ID
# @param [Hash] options The parameters to create
# @option options [String] :name The inbox name
# @option options [Integer] :domain_id Attach to a custom domain; omit for a Mailtrap-hosted inbox
# @return [InboundInbox] Created inbox
# @!macro api_errors
# @raise [ArgumentError] If invalid options are provided
def create(folder_id, options)
validate_options!(options, CREATE_OPTIONS)
handle_response(client.post(inboxes_path(folder_id), options))
end

# Updates an inbox
# @param folder_id [Integer] The folder ID
# @param inbox_id [Integer] The inbox ID
# @param [Hash] options The parameters to update
# @option options [String] :name The inbox name
# @return [InboundInbox] Updated inbox
# @!macro api_errors
# @raise [ArgumentError] If invalid options are provided
def update(folder_id, inbox_id, options)
validate_options!(options, UPDATE_OPTIONS)
handle_response(client.patch("#{inboxes_path(folder_id)}/#{inbox_id}", options))
end

# Deletes an inbox
# @param folder_id [Integer] The folder ID
# @param inbox_id [Integer] The inbox ID
# @return nil
# @!macro api_errors
def delete(folder_id, inbox_id)
client.delete("#{inboxes_path(folder_id)}/#{inbox_id}")
end

private

def inboxes_path(folder_id)
"/api/inbound/folders/#{folder_id}/inboxes"
end
end
end
Loading
Loading