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
8 changes: 7 additions & 1 deletion .fern/replay.lock
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,13 @@ generations:
cli_version: unknown
generator_versions:
fernapi/fern-cli-generator: 0.38.10
current_generation: aa8950f5e5516cde201a90cd7a2913b95d7953c9
- commit_sha: 4c9aefd77ae432325a08ca82fed607ef8ae8ca50
tree_hash: 1f70ff47d6ef1d467440ff91e33543e699aa944c
timestamp: 2026-09-28T19:36:15.791Z
cli_version: unknown
generator_versions:
fernapi/fern-cli-generator: 0.38.10
current_generation: 4c9aefd77ae432325a08ca82fed607ef8ae8ca50
patches:
- id: patch-bf63549e
content_hash: sha256:a660ef1be2e6211b2d9aee152022a339ffd879d56f0e8b0b557ce0fb5e4d9b5c
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "agentmail-cli"
version = "1.6.0"
version = "1.7.0"
edition = "2021"
description = "Command-line interface for the AgentMail API. Send, receive, reply, and manage threaded email conversations from your terminal."
license = "MIT"
Expand Down
68 changes: 66 additions & 2 deletions agentmail-sdk/src/api/resources/agent/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ impl AgentClient {
///
/// A 6-digit OTP is sent to the human's email for verification.
///
/// `human_email` is optional. Without it, the inbox can receive email but cannot send to anyone until a human is attached with the attach human endpoint. There is also no way to recover the API key, so store it durably. Calling sign-up again without `human_email` creates a new organization, which needs a different `username`: the original username stays with the lost organization's inbox.
///
/// This endpoint is idempotent. Calling it again with the same `human_email` will rotate the API key and resend the OTP if expired.
///
/// The returned API key has limited permissions until the organization is verified via the verify endpoint.
Expand Down Expand Up @@ -50,8 +52,8 @@ impl AgentClient {
/// .agent
/// .sign_up(
/// &AgentSignupRequest {
/// human_email: "human_email".to_string(),
/// username: "username".to_string(),
/// human_email: None,
/// source: None,
/// referrer: None,
/// },
Expand All @@ -76,11 +78,73 @@ impl AgentClient {
.await
}

/// Attach a human to an unverified agent organization. A 6-digit OTP is sent to the human's email, which you then submit to the verify endpoint.
///
/// Use it after signing up without a `human_email`. Once the human is attached, the organization can send email to that human only, and verification lifts the remaining restrictions. For up to 5 minutes after attaching, sends to the human can still be rejected with a `429` daily send limit error while the API key's cached limits catch up. Wait and retry.
///
/// Calling it again with the same `human_email` does not rotate the API key. It resends the OTP if it was never delivered, or issues a new one if it expired. While the current OTP is still valid, calling it again keeps that OTP and its attempt count. If all 10 attempts are used up, wait until the OTP expires, 24 hours after it was issued, then call it again for a new one.
///
/// Calling it with a different `human_email` replaces the attached human and sends the new human an OTP. An organization can replace its human at most 2 times.
///
/// Only available until the organization is verified.
///
/// **CLI:**
/// ```bash
/// agentmail agent attach-human --human-email user@example.com
/// ```
///
/// # Arguments
///
/// * `options` - Additional request options such as headers, timeout, etc.
///
/// # Returns
///
/// JSON response from the API
///
/// # Examples
///
/// ```no_run
/// use agentmail_sdk::prelude::*;
///
/// #[tokio::main]
/// async fn main() {
/// let config = ClientConfig {
/// token: Some("<token>".to_string()),
/// ..Default::default()
/// };
/// let client = AgentmailClient::new(config).expect("Failed to build client");
/// client
/// .agent
/// .attach_human(
/// &AgentAttachHumanRequest {
/// human_email: "human_email".to_string(),
/// },
/// None,
/// )
/// .await;
/// }
/// ```
pub async fn attach_human(
&self,
request: &AgentAttachHumanRequest,
options: Option<RequestOptions>,
) -> Result<AgentAttachHumanResponse, ApiError> {
self.http_client
.execute_request(
Method::POST,
"v0/agent/human",
Some(serde_json::to_value(request).map_err(ApiError::Serialization)?),
None,
options,
)
.await
}

/// Verify an agent organization using the 6-digit OTP sent to the human's email during sign-up.
///
/// On success, the organization is upgraded from `agent_unverified` to `agent_verified`, the send allowlist is removed, and free plan entitlements are applied.
///
/// The OTP expires after 24 hours and allows a maximum of 10 attempts. If you run into any difficulties receiving the OTP code, you can also create an account on [console.agentmail.to](https://console.agentmail.to) using the human email address you provided to verify your account.
/// The OTP expires after 24 hours and allows a maximum of 10 attempts. If the OTP expired, call the attach human endpoint with the same `human_email` to get a new one without rotating the API key. Once all 10 attempts are used, even the correct OTP is rejected, and attach human keeps returning the same OTP until it expires, so wait for it to expire before asking for a new one. An organization that signed up without a `human_email` has no OTP until a human is attached. If you run into any difficulties receiving the OTP code, you can also create an account on [console.agentmail.to](https://console.agentmail.to) using the human email address you provided to verify your account.
///
/// **CLI:**
/// ```bash
Expand Down
39 changes: 39 additions & 0 deletions agentmail-types/src/types/agent_attach_human_request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
pub use crate::prelude::*;
#[allow(unused_imports)]
use super::*;

#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)]
pub struct AgentAttachHumanRequest {
/// Email address of the human who owns the agent. A 6-digit OTP will be sent to this address.
#[serde(default)]
pub human_email: String,
}

impl AgentAttachHumanRequest {
pub fn builder() -> AgentAttachHumanRequestBuilder {
<AgentAttachHumanRequestBuilder as Default>::default()
}
}

#[derive(Clone, PartialEq, Default, Debug)]
#[non_exhaustive]
pub struct AgentAttachHumanRequestBuilder {
human_email: Option<String>,
}

impl AgentAttachHumanRequestBuilder {
pub fn human_email(mut self, value: impl Into<String>) -> Self {
self.human_email = Some(value.into());
self
}

/// Consumes the builder and constructs a [`AgentAttachHumanRequest`].
/// This method will fail if any of the following fields are not set:
/// - [`human_email`](AgentAttachHumanRequestBuilder::human_email)
pub fn build(self) -> Result<AgentAttachHumanRequest, BuildError> {
Ok(AgentAttachHumanRequest {
human_email: self.human_email.ok_or_else(|| BuildError::missing_field("human_email"))?,
})
}
}

50 changes: 50 additions & 0 deletions agentmail-types/src/types/agent_attach_human_response.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
pub use crate::prelude::*;
#[allow(unused_imports)]
use super::*;

/// Response after attaching a human to an agent organization.
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)]
pub struct AgentAttachHumanResponse {
/// Email address of the attached human.
#[serde(default)]
pub human_email: String,
/// Next steps for the agent, in plain text.
#[serde(default)]
pub instructions: String,
}

impl AgentAttachHumanResponse {
pub fn builder() -> AgentAttachHumanResponseBuilder {
<AgentAttachHumanResponseBuilder as Default>::default()
}
}

#[derive(Clone, PartialEq, Default, Debug)]
#[non_exhaustive]
pub struct AgentAttachHumanResponseBuilder {
human_email: Option<String>,
instructions: Option<String>,
}

impl AgentAttachHumanResponseBuilder {
pub fn human_email(mut self, value: impl Into<String>) -> Self {
self.human_email = Some(value.into());
self
}

pub fn instructions(mut self, value: impl Into<String>) -> Self {
self.instructions = Some(value.into());
self
}

/// Consumes the builder and constructs a [`AgentAttachHumanResponse`].
/// This method will fail if any of the following fields are not set:
/// - [`human_email`](AgentAttachHumanResponseBuilder::human_email)
/// - [`instructions`](AgentAttachHumanResponseBuilder::instructions)
pub fn build(self) -> Result<AgentAttachHumanResponse, BuildError> {
Ok(AgentAttachHumanResponse {
human_email: self.human_email.ok_or_else(|| BuildError::missing_field("human_email"))?,
instructions: self.instructions.ok_or_else(|| BuildError::missing_field("instructions"))?,
})
}
}
8 changes: 4 additions & 4 deletions agentmail-types/src/types/agent_signup_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ use super::*;
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)]
pub struct AgentSignupRequest {
/// Email address of the human who owns the agent. A 6-digit OTP will be sent to this address.
#[serde(default)]
pub human_email: String,
/// Omit it to get a receive-only inbox: it can receive email but cannot send until a human is attached with the attach human endpoint.
#[serde(skip_serializing_if = "Option::is_none")]
pub human_email: Option<String>,
/// Username for the auto-created inbox (e.g. "my-agent" creates my-agent@agentmail.to).
#[serde(default)]
pub username: String,
Expand Down Expand Up @@ -60,11 +61,10 @@ impl AgentSignupRequestBuilder {

/// Consumes the builder and constructs a [`AgentSignupRequest`].
/// This method will fail if any of the following fields are not set:
/// - [`human_email`](AgentSignupRequestBuilder::human_email)
/// - [`username`](AgentSignupRequestBuilder::username)
pub fn build(self) -> Result<AgentSignupRequest, BuildError> {
Ok(AgentSignupRequest {
human_email: self.human_email.ok_or_else(|| BuildError::missing_field("human_email"))?,
human_email: self.human_email,
username: self.username.ok_or_else(|| BuildError::missing_field("username"))?,
source: self.source,
referrer: self.referrer,
Expand Down
10 changes: 10 additions & 0 deletions agentmail-types/src/types/api_key_permissions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ pub struct ApiKeyPermissions {
/// Create pods.
#[serde(skip_serializing_if = "Option::is_none")]
pub pod_create: Option<bool>,
/// Update pods.
#[serde(skip_serializing_if = "Option::is_none")]
pub pod_update: Option<bool>,
/// Delete pods.
#[serde(skip_serializing_if = "Option::is_none")]
pub pod_delete: Option<bool>,
Expand Down Expand Up @@ -174,6 +177,7 @@ pub struct ApiKeyPermissionsBuilder {
account_update: Option<bool>,
pod_read: Option<bool>,
pod_create: Option<bool>,
pod_update: Option<bool>,
pod_delete: Option<bool>,
}

Expand Down Expand Up @@ -368,6 +372,11 @@ impl ApiKeyPermissionsBuilder {
self
}

pub fn pod_update(mut self, value: bool) -> Self {
self.pod_update = Some(value);
self
}

pub fn pod_delete(mut self, value: bool) -> Self {
self.pod_delete = Some(value);
self
Expand Down Expand Up @@ -414,6 +423,7 @@ impl ApiKeyPermissionsBuilder {
account_update: self.account_update,
pod_read: self.pod_read,
pod_create: self.pod_create,
pod_update: self.pod_update,
pod_delete: self.pod_delete,
})
}
Expand Down
6 changes: 5 additions & 1 deletion agentmail-types/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//!
//! ## Type Categories
//!
//! - **Request/Response Types**: 115 types for API operations
//! - **Request/Response Types**: 117 types for API operations
//! - **Model Types**: 240 types for data representation

pub mod limit;
Expand Down Expand Up @@ -68,6 +68,7 @@ pub mod update_account_status;
pub mod account;
pub mod list_accounts_response;
pub mod agent_signup_response;
pub mod agent_attach_human_response;
pub mod agent_verify_response;
pub mod api_key_id;
pub mod prefix;
Expand Down Expand Up @@ -309,6 +310,7 @@ pub mod webhooks_create_webhook_request;
pub mod webhooks_update_webhook_request;
pub mod update_account_request;
pub mod agent_signup_request;
pub mod agent_attach_human_request;
pub mod agent_verify_request;
pub mod connect_provider_body;
pub mod create_draft_request;
Expand Down Expand Up @@ -424,6 +426,7 @@ pub use update_account_status::UpdateAccountStatus;
pub use account::Account;
pub use list_accounts_response::ListAccountsResponse;
pub use agent_signup_response::AgentSignupResponse;
pub use agent_attach_human_response::AgentAttachHumanResponse;
pub use agent_verify_response::AgentVerifyResponse;
pub use api_key_id::ApiKeyId;
pub use prefix::Prefix;
Expand Down Expand Up @@ -665,6 +668,7 @@ pub use webhooks_create_webhook_request::WebhooksCreateWebhookRequest;
pub use webhooks_update_webhook_request::WebhooksUpdateWebhookRequest;
pub use update_account_request::UpdateAccountRequest;
pub use agent_signup_request::AgentSignupRequest;
pub use agent_attach_human_request::AgentAttachHumanRequest;
pub use agent_verify_request::AgentVerifyRequest;
pub use connect_provider_body::ConnectProviderBody;
pub use create_draft_request::CreateDraftRequest;
Expand Down
2 changes: 1 addition & 1 deletion cli/agentmail/openapi0.json

Large diffs are not rendered by default.

27 changes: 26 additions & 1 deletion reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,37 @@ again and retry.

### `agentmail agent`

#### `agentmail agent attach-human`

Attach a human to an unverified agent organization. A 6-digit OTP is sent to the human's email, which you then submit to the verify endpoint.

Use it after signing up without a `human_email`. Once the human is attached, the organization can send email to that human only, and verification lifts the remaining restrictions. For up to 5 minutes after attaching, sends to the human can still be rejected with a `429` daily send limit error while the API key's cached limits catch up. Wait and retry.

Calling it again with the same `human_email` does not rotate the API key. It resends the OTP if it was never delivered, or issues a new one if it expired. While the current OTP is still valid, calling it again keeps that OTP and its attempt count. If all 10 attempts are used up, wait until the OTP expires, 24 hours after it was issued, then call it again for a new one.

Calling it with a different `human_email` replaces the attached human and sends the new human an OTP. An organization can replace its human at most 2 times.

Only available until the organization is verified.

**CLI:**
```bash
agentmail agent attach-human --human-email user@example.com
```

`POST /v0/agent/human`

| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--json` | `JSON` | Yes | Request body as JSON (or use individual body-field flags) |

#### `agentmail agent sign-up`

Create a new agent organization with an inbox and API key. This endpoint is for signing up for the first time. If you've already signed up, you're all set — just use your existing API key.

A 6-digit OTP is sent to the human's email for verification.

`human_email` is optional. Without it, the inbox can receive email but cannot send to anyone until a human is attached with the attach human endpoint. There is also no way to recover the API key, so store it durably. Calling sign-up again without `human_email` creates a new organization, which needs a different `username`: the original username stays with the lost organization's inbox.

This endpoint is idempotent. Calling it again with the same `human_email` will rotate the API key and resend the OTP if expired.

The returned API key has limited permissions until the organization is verified via the verify endpoint.
Expand All @@ -124,7 +149,7 @@ Verify an agent organization using the 6-digit OTP sent to the human's email dur

On success, the organization is upgraded from `agent_unverified` to `agent_verified`, the send allowlist is removed, and free plan entitlements are applied.

The OTP expires after 24 hours and allows a maximum of 10 attempts. If you run into any difficulties receiving the OTP code, you can also create an account on [console.agentmail.to](https://console.agentmail.to) using the human email address you provided to verify your account.
The OTP expires after 24 hours and allows a maximum of 10 attempts. If the OTP expired, call the attach human endpoint with the same `human_email` to get a new one without rotating the API key. Once all 10 attempts are used, even the correct OTP is rejected, and attach human keeps returning the same OTP until it expires, so wait for it to expire before asking for a new one. An organization that signed up without a `human_email` has no OTP until a human is attached. If you run into any difficulties receiving the OTP code, you can also create an account on [console.agentmail.to](https://console.agentmail.to) using the human email address you provided to verify your account.

**CLI:**
```bash
Expand Down
Loading