Customer Emails
Configure the IMAP mailbox the platform reads withdrawal verification codes from, then verify, enable, or disable that email poller.
For withdrawals funded from your treasury, the platform reads the verification code from your mailbox and forwards it to your webhook. Point the platform at an IMAP inbox here, verify the connection, and turn polling on or off.
Authentication: every endpoint on this page takes your dashboard session token as a Bearer token. The session must own the {customer_id} in the path; any other id answers 404 CUSTOMER_NOT_FOUND.
Authorization: Bearer YOUR_SESSION_TOKENOne poller per customer
The email poller is a singleton: it has no id of its own and lives at /v1/customers/{customer_id}/email/poller. Read it with GET, create or replace it with PUT, change part of it with PATCH, and run verify, enable, or disable as POST actions. Each call returns the poller. The mailbox user is always your owner credential's email, and the IMAP password is never returned—imap.has_password tells you one is stored.
Responses use the standard envelope and error format. POST and PATCH accept an optional x-idempotency-key header.
Poller Fields
| Field | Type | Description |
|---|---|---|
| object | string | Always email_poller. |
| customer_id | string | Owning customer. |
| string | null | Mailbox address (your owner credential's email). | |
| imap | object | null | {host, port, is_tls_enabled, user, has_password}, or null when no mailbox is set. |
| description | string | null | Your label for the mailbox. |
| is_enabled | boolean | Whether polling is on. |
| connection_status | string | UNVERIFIED, CONNECTED, or FAILED. Any change to imap resets it to UNVERIFIED. |
| status_reason | object | null | {code, message} when connection_status is FAILED: IMAP_AUTHENTICATION_FAILED, IMAP_TIMEOUT, or IMAP_UNREACHABLE. |
| last_verified_at | string | null | Last verify call. |
| last_polled_at / last_successful_poll_at / last_error_at | string | null | Poll loop timestamps. |
| last_error | object | null | Last poll failure as {code, message}, same codes as status_reason. |
| total_emails_processed / total_pins_extracted | integer | Running counters. |
| created_at / updated_at | string | UTC timestamps without milliseconds. |
| created_by / updated_by | string | null | Actor that created or last changed the poller. |
Create or Replace Email Poller
Set the mailbox. The first call creates the poller enabled and answers 201 Created; later calls replace the mailbox settings, keep the current is_enabled, and answer 200 OK. The connection status resets to UNVERIFIED—run Verify next.
POST /v1/customers/{customer_id}/email/poller still works as a deprecated alias of this call.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| imap | object | Yes | Mailbox settings (below). |
| description | string | null | No | Label for the mailbox (max 255). |
imap object
| Field | Type | Required | Description |
|---|---|---|---|
| host | string | Yes | IMAP server host, e.g. imap.gmail.com. |
| port | integer | Yes | IMAP port, e.g. 993. |
| is_tls_enabled | boolean | No | Use TLS (default true). The server certificate is always verified. tls is a deprecated alias. |
| password | string | Yes | Mailbox or app password. Write-only. |
{
"imap": {
"host": "imap.gmail.com",
"port": 993,
"is_tls_enabled": true,
"password": "YOUR_APP_PASSWORD"
},
"description": "Main trading account"
}Success Response (201 Created, or 200 OK on replace):
{
"success": true,
"data": {
"object": "email_poller",
"customer_id": "corp-redundant-firewall",
"email": "[email protected]",
"imap": {
"host": "imap.gmail.com",
"port": 993,
"is_tls_enabled": true,
"user": "[email protected]",
"has_password": true
},
"description": "Main trading account",
"is_enabled": true,
"connection_status": "UNVERIFIED",
"status_reason": null,
"last_verified_at": null,
"last_polled_at": null,
"last_successful_poll_at": null,
"last_error_at": null,
"last_error": null,
"total_emails_processed": 0,
"total_pins_extracted": 0,
"created_at": "2026-10-12T14:10:00Z",
"updated_at": "2026-10-12T14:10:00Z",
"created_by": "1d8cb40d-8740-5a6b-94c3-7406e08ab4fd",
"updated_by": "1d8cb40d-8740-5a6b-94c3-7406e08ab4fd"
},
"meta": {
"timestamp": "2026-10-12T14:10:00Z",
"version": "v1",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}curl -X PUT "https://api.sandbox.sovera.io/sovx/v1/customers/corp-redundant-firewall/email/poller" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_SESSION_TOKEN" \
-d '{
"imap": { "host": "imap.gmail.com", "port": 993, "is_tls_enabled": true, "password": "YOUR_APP_PASSWORD" },
"description": "Main trading account"
}'Errors: 401 UNAUTHORIZED missing or invalid session · 404 CUSTOMER_NOT_FOUND not owned by the session, or deleted · 404 OWNER_CREDENTIAL_NOT_FOUND generate credentials first · 422 VALIDATION_ERROR invalid or unknown field (email isn't accepted).
Get Email Poller
Return the poller with its connection status and counters.
GET https://api.sandbox.sovera.io/sovx/v1/customers/corp-redundant-firewall/email/pollerSuccess Response (200 OK):
{
"success": true,
"data": {
"object": "email_poller",
"customer_id": "corp-redundant-firewall",
"email": "[email protected]",
"imap": {
"host": "imap.gmail.com",
"port": 993,
"is_tls_enabled": true,
"user": "[email protected]",
"has_password": true
},
"description": "Main trading account",
"is_enabled": true,
"connection_status": "CONNECTED",
"status_reason": null,
"last_verified_at": "2026-10-12T14:12:30Z",
"last_polled_at": "2026-10-12T14:29:00Z",
"last_successful_poll_at": "2026-10-12T14:29:00Z",
"last_error_at": null,
"last_error": null,
"total_emails_processed": 42,
"total_pins_extracted": 41,
"created_at": "2026-10-12T14:10:00Z",
"updated_at": "2026-10-12T14:29:00Z",
"created_by": "1d8cb40d-8740-5a6b-94c3-7406e08ab4fd",
"updated_by": "1d8cb40d-8740-5a6b-94c3-7406e08ab4fd"
},
"meta": {
"timestamp": "2026-10-12T14:30:02Z",
"version": "v1",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}curl -X GET "https://api.sandbox.sovera.io/sovx/v1/customers/corp-redundant-firewall/email/poller" \
-H "Authorization: Bearer YOUR_SESSION_TOKEN"Errors: 401 UNAUTHORIZED missing or invalid session · 404 CUSTOMER_NOT_FOUND not owned by the session, or deleted · 404 EMAIL_POLLER_NOT_FOUND no poller configured.
Update Email Poller
Change part of the poller. imap fields merge into the stored settings, so rotating the password doesn't require resending host and port. "imap": null clears the mailbox and stops polling. Any imap change resets connection_status to UNVERIFIED and restarts the poller. To turn polling on or off, use the enable and disable actions.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| imap | object | null | No | Any of host, port, is_tls_enabled, password. null clears the mailbox. |
| description | string | null | No | Label for the mailbox (max 255). |
{
"description": "Updated trading mailbox",
"imap": { "password": "YOUR_NEW_APP_PASSWORD" }
}Success Response (200 OK):
{
"success": true,
"data": {
"object": "email_poller",
"customer_id": "corp-redundant-firewall",
"email": "[email protected]",
"imap": {
"host": "imap.gmail.com",
"port": 993,
"is_tls_enabled": true,
"user": "[email protected]",
"has_password": true
},
"description": "Updated trading mailbox",
"is_enabled": true,
"connection_status": "UNVERIFIED",
"status_reason": null,
"last_verified_at": "2026-10-12T14:12:30Z",
"last_polled_at": "2026-10-12T14:29:00Z",
"last_successful_poll_at": "2026-10-12T14:29:00Z",
"last_error_at": null,
"last_error": null,
"total_emails_processed": 42,
"total_pins_extracted": 41,
"created_at": "2026-10-12T14:10:00Z",
"updated_at": "2026-10-12T15:00:00Z",
"created_by": "1d8cb40d-8740-5a6b-94c3-7406e08ab4fd",
"updated_by": "1d8cb40d-8740-5a6b-94c3-7406e08ab4fd"
},
"meta": {
"timestamp": "2026-10-12T15:00:00Z",
"version": "v1",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}curl -X PATCH "https://api.sandbox.sovera.io/sovx/v1/customers/corp-redundant-firewall/email/poller" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_SESSION_TOKEN" \
-d '{ "description": "Updated trading mailbox", "imap": { "password": "YOUR_NEW_APP_PASSWORD" } }'Errors: 401 UNAUTHORIZED missing or invalid session · 404 CUSTOMER_NOT_FOUND · 404 EMAIL_POLLER_NOT_FOUND · 404 OWNER_CREDENTIAL_NOT_FOUND · 422 VALIDATION_ERROR invalid field, or imap without host, port, and password when no mailbox is stored (source.pointer: /imap).
Verify Mailbox Connection
Test the stored mailbox and record the outcome on the poller. The call answers 200 OK whether the mailbox connects or not: read connection_status and status_reason. Send no body.
POST https://api.sandbox.sovera.io/sovx/v1/customers/corp-redundant-firewall/email/poller/verifySuccess Response (200 OK), mailbox rejected the password:
{
"success": true,
"data": {
"object": "email_poller",
"customer_id": "corp-redundant-firewall",
"email": "[email protected]",
"imap": {
"host": "imap.gmail.com",
"port": 993,
"is_tls_enabled": true,
"user": "[email protected]",
"has_password": true
},
"description": "Main trading account",
"is_enabled": true,
"connection_status": "FAILED",
"status_reason": {
"code": "IMAP_AUTHENTICATION_FAILED",
"message": "The mailbox rejected the stored credentials."
},
"last_verified_at": "2026-10-12T15:02:11Z",
"last_polled_at": "2026-10-12T14:29:00Z",
"last_successful_poll_at": "2026-10-12T14:29:00Z",
"last_error_at": null,
"last_error": null,
"total_emails_processed": 42,
"total_pins_extracted": 41,
"created_at": "2026-10-12T14:10:00Z",
"updated_at": "2026-10-12T15:02:11Z",
"created_by": "1d8cb40d-8740-5a6b-94c3-7406e08ab4fd",
"updated_by": "1d8cb40d-8740-5a6b-94c3-7406e08ab4fd"
},
"meta": {
"timestamp": "2026-10-12T15:02:11Z",
"version": "v1",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}curl -X POST "https://api.sandbox.sovera.io/sovx/v1/customers/corp-redundant-firewall/email/poller/verify" \
-H "Authorization: Bearer YOUR_SESSION_TOKEN"Errors: 401 UNAUTHORIZED missing or invalid session · 404 CUSTOMER_NOT_FOUND · 404 EMAIL_POLLER_NOT_FOUND · 422 INVALID_STATE_TRANSITION no mailbox credentials stored.
Enable Email Poller
Turn polling on and return the poller with is_enabled: true. Send no body.
POST https://api.sandbox.sovera.io/sovx/v1/customers/corp-redundant-firewall/email/poller/enableSuccess Response (200 OK): the poller with "is_enabled": true.
curl -X POST "https://api.sandbox.sovera.io/sovx/v1/customers/corp-redundant-firewall/email/poller/enable" \
-H "Authorization: Bearer YOUR_SESSION_TOKEN"Errors: 401 UNAUTHORIZED · 404 CUSTOMER_NOT_FOUND · 404 EMAIL_POLLER_NOT_FOUND · 422 INVALID_STATE_TRANSITION already enabled, or no mailbox credentials stored.
Disable Email Poller
Turn polling off and return the poller with is_enabled: false. The mailbox settings stay, so you can enable it again later. Send no body.
POST https://api.sandbox.sovera.io/sovx/v1/customers/corp-redundant-firewall/email/poller/disableSuccess Response (200 OK): the poller with "is_enabled": false.
curl -X POST "https://api.sandbox.sovera.io/sovx/v1/customers/corp-redundant-firewall/email/poller/disable" \
-H "Authorization: Bearer YOUR_SESSION_TOKEN"Errors: 401 UNAUTHORIZED · 404 CUSTOMER_NOT_FOUND · 404 EMAIL_POLLER_NOT_FOUND · 422 INVALID_STATE_TRANSITION already disabled.
Deprecated routes
These still answer for one release. Move to the routes above.
| Deprecated | Use instead | Behavior today |
|---|---|---|
POST /v1/customers/{customer_id}/email/poller | PUT …/email/poller | Same create-or-replace. |
DELETE /v1/customers/{customer_id}/email/poller | POST …/email/poller/disable | Disables the poller (no physical delete) and answers 204. |
POST /v1/customers/{customer_id}/email/poller/{poller_id}/enable | POST …/email/poller/enable | {poller_id} must equal your customer_id; any other value is 404 EMAIL_POLLER_NOT_FOUND. |
POST /v1/customers/{customer_id}/email/poller/{poller_id}/disable | POST …/email/poller/disable | Same rule as above. |