agentmail 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 91279f5876443c121efd7dd19c26ef9dd5ca1ccd8d6b2f3401ae5a800600d1ca
4
+ data.tar.gz: 4a4d20ae2ded9c37bb3793370c3d3135f31903b4c4ba783a145d1363a9fc0a83
5
+ SHA512:
6
+ metadata.gz: 2d902cb79eba2890efb15919f66a0644522b7c21c4f66ddc55d4132517b71a27c64b2e05ada8539c28b5c21bdbb8a1eefb3b566ac8b7a3673d5833feb340c94b
7
+ data.tar.gz: 25cdfd7ddd6918cdea284fe33a970bc84eb072809720be5262672077f392e285a2e0325f30d03c9cada820974e2f304bef16321764d2c5a25fe6a5fe4a722517
data/.env.example ADDED
@@ -0,0 +1,4 @@
1
+ # Copy this file to .env and fill in your credentials for smoke testing.
2
+ AGENTMAIL_API_KEY=your_agentmail_api_key
3
+ # Optional recipient for manual email testing when SMOKE_TEST_SEND_EMAIL=true
4
+ SMOKE_TEST_RECIPIENT=recipient@example.com
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 4.0.1
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Toadstool Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # Agentmail Ruby Client
2
+
3
+ This is a lightweight Ruby wrapper for the [AgentMail](https://agentmail.to/) API inspired by the official [Python SDK](https://github.com/agentmail-to/agentmail-python). It focuses on the core HTTP plumbing and provides a small helper surface around the most common endpoints, while keeping extensibility simple via a generic request method.
4
+
5
+ ## Installation
6
+
7
+ The gem has not been published yet, so install it straight from the repository:
8
+
9
+ ```ruby
10
+ gem "agentmail", github: "agentmail-to/agentmail-ruby"
11
+ ```
12
+
13
+ Run `bundle install` after updating your `Gemfile`.
14
+
15
+ ## Usage
16
+
17
+ ```ruby
18
+ require "agentmail"
19
+
20
+ client = Agentmail::Client.new(api_key: ENV.fetch("AGENTMAIL_API_KEY"))
21
+
22
+ # List inboxes
23
+ client.inboxes.list
24
+
25
+ # Create an inbox
26
+ client.inboxes.create(display_name: "Support")
27
+
28
+ # Fetch a thread with filters similar to the Python SDK
29
+ client.threads.list(limit: 25, include_spam: false)
30
+
31
+ # Use the low-level request helper for endpoints that don't have a helper yet
32
+ client.request(:post, "/v0/inboxes/inbox_123/drafts", body: { subject: "Hello" })
33
+ ```
34
+
35
+ ### Configuration
36
+
37
+ ```ruby
38
+ client = Agentmail::Client.new(
39
+ api_key: -> { ENV["AGENTMAIL_API_KEY"] }, # lazily evaluated for rotations
40
+ environment: :eu_prod, # or :prod, :prod_x_402, :prod_mpp
41
+ timeout: 30,
42
+ open_timeout: 2,
43
+ extra_headers: { "X-Debug" => "1" }
44
+ )
45
+ ```
46
+
47
+ The `environment` option maps to the same host list used by the Python SDK. You can also override `base_url` entirely if you are testing against a mock server.
48
+
49
+ ## Development
50
+
51
+ ```bash
52
+ bin/setup
53
+ bundle exec rake test
54
+ ```
55
+
56
+ The codebase deliberately avoids code generation so a contributor can read through the API surface. If you need an endpoint that is not wrapped yet, either open a PR or call it directly via `Client#request`.
57
+
58
+ ### Publishing to RubyGems
59
+
60
+ 1. Grab your RubyGems API key (`curl -u you https://rubygems.org/api/v1/api_key.yaml`) and add it to the repository as the `RUBYGEMS_API_KEY` secret under **Settings → Secrets and variables → Actions**.
61
+ 2. Bump `Agentmail::VERSION` in `lib/agentmail/version.rb`, update the changelog, and commit the changes.
62
+ 3. Create a version tag that matches the gem version: `git tag vX.Y.Z`.
63
+ 4. Push the tag: `git push origin vX.Y.Z`.
64
+
65
+ When GitHub receives the tag, the `release.yml` workflow builds the gem, runs the test suite, and publishes the `.gem` to RubyGems using the stored API key. If the tag and gem versions do not match, or the tests fail, the workflow aborts without publishing.
66
+
67
+ ## Manual Smoke Test
68
+
69
+ 1. Copy `.env.example` to `.env` and add your `AGENTMAIL_API_KEY`. Optionally set `SMOKE_TEST_RECIPIENT` if you want to exercise outbound email delivery.
70
+ 2. Set `SMOKE_TEST_SEND_EMAIL=true` if you want the script to send a short message to `SMOKE_TEST_RECIPIENT`; otherwise the run is read-only.
71
+ 3. Run:
72
+
73
+ ```bash
74
+ bin/smoke_test
75
+ ```
76
+
77
+ The script loads the `.env` file and exercises every read-only endpoint we can touch with your credentials (organization, pods, domains, lists, threads, inbox messages, etc.). If `SMOKE_TEST_SEND_EMAIL=true`, it also delivers a short email to the configured recipient so you can confirm outbound delivery end-to-end.
78
+
79
+ ## License
80
+
81
+ MIT © [Toadstool Labs](http://toadstool.tech)
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs << "test"
8
+ t.test_files = FileList["test/**/*_test.rb"]
9
+ t.warning = false
10
+ end
11
+
12
+ task default: :test
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "faraday"
5
+
6
+ require_relative "error"
7
+ require_relative "response"
8
+ require_relative "resources/inboxes"
9
+ require_relative "resources/threads"
10
+
11
+ module Agentmail
12
+ class Client
13
+ DEFAULT_TIMEOUT = 60
14
+ DEFAULT_OPEN_TIMEOUT = 5
15
+ DEFAULT_ENVIRONMENTS = {
16
+ prod: "https://api.agentmail.to",
17
+ prod_x_402: "https://x402.api.agentmail.to",
18
+ prod_mpp: "https://mpp.api.agentmail.to",
19
+ eu_prod: "https://api.agentmail.eu"
20
+ }.freeze
21
+
22
+ attr_reader :base_url, :timeout, :open_timeout
23
+
24
+ def initialize(
25
+ api_key: ENV["AGENTMAIL_API_KEY"],
26
+ base_url: nil,
27
+ environment: :prod,
28
+ timeout: DEFAULT_TIMEOUT,
29
+ open_timeout: DEFAULT_OPEN_TIMEOUT,
30
+ adapter: Faraday.default_adapter,
31
+ user_agent: nil,
32
+ extra_headers: {},
33
+ logger: nil
34
+ )
35
+ @api_key = api_key
36
+ @base_url = (base_url || resolve_environment(environment)).to_s.sub(%r{\/+\z}, "")
37
+ @timeout = timeout || DEFAULT_TIMEOUT
38
+ @open_timeout = open_timeout || DEFAULT_OPEN_TIMEOUT
39
+ @user_agent = user_agent || default_user_agent
40
+ @extra_headers = extra_headers
41
+ @adapter = adapter
42
+ @logger = logger
43
+ validate_api_key!
44
+ end
45
+
46
+ def inboxes
47
+ @inboxes ||= Resources::Inboxes.new(self)
48
+ end
49
+
50
+ def threads
51
+ @threads ||= Resources::Threads.new(self)
52
+ end
53
+
54
+ def request(method, path, params: nil, body: nil, headers: {}, timeout: nil)
55
+ response = connection.run_request(
56
+ method.to_sym,
57
+ normalize_path(path),
58
+ encoded_body(body),
59
+ build_headers(body, headers)
60
+ ) do |req|
61
+ req.params.update(stringify_keys(params)) if params
62
+ req.options.timeout = timeout || @timeout if req.options.respond_to?(:timeout=)
63
+ req.options.open_timeout = @open_timeout if @open_timeout && req.options.respond_to?(:open_timeout=)
64
+ end
65
+
66
+ parsed_body = parse_body(response)
67
+ wrapped = Response.new(status: response.status, headers: response.headers.to_h, body: parsed_body)
68
+ return wrapped if wrapped.success?
69
+
70
+ raise Error.new(
71
+ "AgentMail request failed with status #{response.status}",
72
+ status: response.status,
73
+ headers: response.headers.to_h,
74
+ body: parsed_body
75
+ )
76
+ end
77
+
78
+ private
79
+
80
+ def validate_api_key!
81
+ key = api_key_value
82
+ raise ConfigurationError, "An AgentMail API key is required" if key.nil? || key.empty?
83
+ end
84
+
85
+ def api_key_value
86
+ value = @api_key.respond_to?(:call) ? @api_key.call : @api_key
87
+ value&.to_s&.strip
88
+ end
89
+
90
+ def build_headers(body, overrides)
91
+ headers = {
92
+ "Authorization" => "Bearer #{api_key_value}",
93
+ "User-Agent" => @user_agent,
94
+ "Accept" => "application/json"
95
+ }.merge(@extra_headers).merge(overrides.transform_keys(&:to_s))
96
+ headers["Content-Type"] = "application/json" if body && !headers.key?("Content-Type")
97
+ headers
98
+ end
99
+
100
+ def connection
101
+ @connection ||= Faraday.new(url: @base_url) do |faraday|
102
+ faraday.response :logger, @logger if @logger
103
+ faraday.adapter @adapter
104
+ end
105
+ end
106
+
107
+ def encoded_body(body)
108
+ return nil if body.nil?
109
+ return body if body.is_a?(String)
110
+
111
+ JSON.generate(body)
112
+ end
113
+
114
+ def parse_body(response)
115
+ return nil if response.body.nil? || response.body.empty?
116
+
117
+ content_type = Array(response.headers["content-type"] || response.headers["Content-Type"]).first.to_s
118
+ if content_type.include?("json")
119
+ JSON.parse(response.body)
120
+ else
121
+ response.body
122
+ end
123
+ rescue JSON::ParserError
124
+ response.body
125
+ end
126
+
127
+ def stringify_keys(hash)
128
+ hash.each_with_object({}) do |(key, value), acc|
129
+ acc[key.to_s] = value
130
+ end
131
+ end
132
+
133
+ def normalize_path(path)
134
+ if path.to_s.start_with?("http://", "https://")
135
+ raise ArgumentError, "path must be a relative path, not a full URL"
136
+ end
137
+
138
+ path.to_s.start_with?("/") ? path.to_s : "/#{path}"
139
+ end
140
+
141
+ def resolve_environment(env)
142
+ DEFAULT_ENVIRONMENTS.fetch(env.to_sym) do
143
+ raise ConfigurationError, "Unknown environment #{env.inspect}"
144
+ end
145
+ end
146
+
147
+ def default_user_agent
148
+ "agentmail-ruby/#{Agentmail::VERSION}"
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Agentmail
4
+ # Generic error raised when the API responds with a non-success status or when
5
+ # the client is configured incorrectly.
6
+ class Error < StandardError
7
+ attr_reader :status, :headers, :body
8
+
9
+ def initialize(message = nil, status: nil, headers: {}, body: nil)
10
+ super(message || "AgentMail request failed")
11
+ @status = status
12
+ @headers = headers || {}
13
+ @body = body
14
+ end
15
+ end
16
+
17
+ class ConfigurationError < StandardError; end
18
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Agentmail
4
+ module Resources
5
+ class BaseResource
6
+ def initialize(client)
7
+ @client = client
8
+ end
9
+
10
+ private
11
+
12
+ attr_reader :client
13
+
14
+ def request(...)
15
+ client.request(...)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base_resource"
4
+
5
+ module Agentmail
6
+ module Resources
7
+ class Inboxes < BaseResource
8
+ def list(params = {})
9
+ request(:get, "/v0/inboxes", params: params).body
10
+ end
11
+
12
+ def retrieve(inbox_id)
13
+ request(:get, inbox_path(inbox_id)).body
14
+ end
15
+ alias get retrieve
16
+
17
+ def create(attributes = {})
18
+ request(:post, "/v0/inboxes", body: attributes).body
19
+ end
20
+
21
+ def update(inbox_id, attributes = {})
22
+ request(:patch, inbox_path(inbox_id), body: attributes).body
23
+ end
24
+
25
+ def delete(inbox_id)
26
+ request(:delete, inbox_path(inbox_id)).body
27
+ end
28
+
29
+ private
30
+
31
+ def inbox_path(inbox_id)
32
+ "/v0/inboxes/#{inbox_id}"
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base_resource"
4
+
5
+ module Agentmail
6
+ module Resources
7
+ class Threads < BaseResource
8
+ def list(params = {})
9
+ request(:get, "/v0/threads", params: params).body
10
+ end
11
+
12
+ def retrieve(thread_id)
13
+ request(:get, thread_path(thread_id)).body
14
+ end
15
+ alias get retrieve
16
+
17
+ def attachment(thread_id, attachment_id)
18
+ request(:get, "#{thread_path(thread_id)}/attachments/#{attachment_id}").body
19
+ end
20
+
21
+ private
22
+
23
+ def thread_path(thread_id)
24
+ "/v0/threads/#{thread_id}"
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Agentmail
4
+ # Wraps the raw Faraday response to provide a predictable surface.
5
+ class Response
6
+ attr_reader :status, :headers, :body
7
+
8
+ def initialize(status:, headers:, body:)
9
+ @status = status
10
+ @headers = headers
11
+ @body = body
12
+ end
13
+
14
+ def success?
15
+ status.to_i >= 200 && status.to_i < 300
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Agentmail
4
+ VERSION = "0.1.0"
5
+ end
data/lib/agentmail.rb ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "agentmail/version"
4
+ require_relative "agentmail/error"
5
+ require_relative "agentmail/response"
6
+ require_relative "agentmail/client"
7
+
8
+ module Agentmail
9
+ def self.new(...)
10
+ Client.new(...)
11
+ end
12
+ end
data/sig/agentmail.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Agentmail
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: agentmail
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Toadstool Labs
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2026-02-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.20'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5.20'
41
+ - !ruby/object:Gem::Dependency
42
+ name: webmock
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.23'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.23'
55
+ description: A small, well-tested HTTP wrapper around the AgentMail API inspired by
56
+ the official Python SDK.
57
+ email:
58
+ - hello@toadstool.tech
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".env.example"
64
+ - ".ruby-version"
65
+ - LICENSE
66
+ - README.md
67
+ - Rakefile
68
+ - lib/agentmail.rb
69
+ - lib/agentmail/client.rb
70
+ - lib/agentmail/error.rb
71
+ - lib/agentmail/resources/base_resource.rb
72
+ - lib/agentmail/resources/inboxes.rb
73
+ - lib/agentmail/resources/threads.rb
74
+ - lib/agentmail/response.rb
75
+ - lib/agentmail/version.rb
76
+ - sig/agentmail.rbs
77
+ homepage: https://github.com/agentmail-to/agentmail-ruby
78
+ licenses:
79
+ - MIT
80
+ metadata:
81
+ allowed_push_host: https://rubygems.org
82
+ homepage_uri: https://github.com/agentmail-to/agentmail-ruby
83
+ source_code_uri: https://github.com/agentmail-to/agentmail-ruby
84
+ changelog_uri: https://github.com/agentmail-to/agentmail-ruby/blob/main/CHANGELOG.md
85
+ post_install_message:
86
+ rdoc_options: []
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: 3.2.0
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ requirements: []
100
+ rubygems_version: 3.5.22
101
+ signing_key:
102
+ specification_version: 4
103
+ summary: Lightweight Ruby client for the AgentMail API.
104
+ test_files: []