AhaSend SMTP Credentials API
Manage SMTP authentication credentials
Operations 4
Each operation below carries the questions people ask an LLM about it and the instructions they give an agent to run it. Generated by API Evangelist overlay
Manage SMTP authentication credentials
Each operation below carries the questions people ask an LLM about it and the instructions they give an agent to run it. Generated by API Evangelist overlay
Every API here is available over the APIs.io API and to AI agents over MCP.
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
find_apisBrowse and filter every API in the catalog.get_api_artifactsOne API's artifacts, grouped by type.get_openapiThe primary OpenAPI for this API.find_similar_apisAPIs that look like this one.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.curl "https://apis.io/api/v1/apis/ahasend:ahasend-smtp-credentials-api"
curl "https://apis.io/api/v1/apis?limit=25"
Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.
Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
title: AhaSend API v2 SMTP Credentials API
description: 'The AhaSend API v2 allows you to send transactional emails, manage domains, webhooks, routes, API keys, and view statistics. ## Authentication
All API requests must be authenticated using a Bearer token in the Authorization header:
```
Authorization: Bearer aha-sk-64-CHARACTER-RANDOM-STRING
```
## Scopes
API keys have specific scopes that control access to different resources and actions:
### Message Scopes
- `messages:send:all` - Send messages from any domain in the account
- `messages:send:{domain}` - Send messages from a specific domain
- `messages:cancel:all` - Cancel messages from any domain
- `messages:cancel:{domain}` - Cancel messages from a specific domain
- `messages:read:all` - Read messages from any domain
- `messages:read:{domain}` - Read messages from a specific domain
### Domain Scopes
- `domains:read` - Read all domains
- `domains:write` - Create and update domains
- `domains:delete:all` - Delete any domain
- `domains:delete:{domain}` - Delete a specific domain
### Account Scopes
- `accounts:read` - Read account information
- `accounts:write` - Update account settings
- `accounts:billing` - Access billing information
- `accounts:members:read` - Read account members
- `accounts:members:add` - Add account members
- `accounts:members:update` - Update account members
- `accounts:members:remove` - Remove account members
### Webhook Scopes
- `webhooks:read:all` - Read all webhooks
- `webhooks:read:{domain}` - Read webhooks for a specific domain
- `webhooks:write:all` - Create and update webhooks
- `webhooks:write:{domain}` - Create and update webhooks for a specific domain
- `webhooks:delete:all` - Delete any webhook
- `webhooks:delete:{domain}` - Delete webhooks for a specific domain
### Route Scopes
- `routes:read:all` - Read all routes
- `routes:read:{domain}` - Read routes for a specific domain
- `routes:write:all` - Create and update routes
- `routes:write:{domain}` - Create and update routes for a specific domain
- `routes:delete:all` - Delete any route
- `routes:delete:{domain}` - Delete routes for a specific domain
### Suppression Scopes
- `suppressions:read` - Read suppressions
- `suppressions:write` - Create suppressions
- `suppressions:delete` - Delete suppressions
- `suppressions:wipe` - Delete the account''s suppressions except protected ones (dangerous)
### Template Scopes
- `templates:read` - Read transactional templates, their drafts and their published versions, and campaign templates
- `templates:write` - Create and update transactional templates, and publish, discard and restore their drafts; create and update campaign templates
- `templates:delete` - Delete transactional templates and campaign templates
### Campaign Scopes
- `campaigns:read` - Read campaigns, their stats and their recipients
- `campaigns:write` - Create, update and duplicate campaigns
- `campaigns:delete` - Delete draft campaigns
- `campaigns:send` - Send, unschedule and test-send campaigns
### Contact Scopes
- `contacts:read` - Read contacts
- `contacts:write` - Create and update contacts
- `contacts:delete` - Delete contacts
### Contact lifecycle and attributes
Contacts are account-global. A contact created through this API joins no
list on its own; campaigns target lists, so add it to one with the Lists
endpoints before a campaign can reach it. Contact subscription state and suppression records are independent:
creating, updating, unsubscribing, resubscribing, or deleting a contact
never creates, changes, or removes complaint, permanent, or protected
suppressions. Prefer unsubscribe over delete when the goal is only to stop
marketing mail, because unsubscribe is reversible and preserves contact
history. Writable attribute keys and types come from the account''s contact-attribute
definitions managed in the dashboard; this API does not expose property
definition CRUD.'
version: 2.0.0
contact:
email: support@ahasend.com
license:
name: MIT
identifier: MIT
servers:
- url: https://api.ahasend.com
description: Production server
security:
- BearerAuth: []
tags:
- name: SMTP Credentials
description: Manage SMTP authentication credentials
paths:
/v2/accounts/{account_id}/smtp-credentials:
get:
summary: Get SMTP Credentials
description: 'Returns a list of SMTP credentials for the account. The global read role
sees all credentials; domain-scoped roles return credentials associated
with at least one authorized domain.'
operationId: getSMTPCredentials
tags:
- SMTP Credentials
parameters:
- name: account_id
in: path
required: true
description: Account ID
schema:
type: string
format: uuid
- name: limit
in: query
description: Maximum number of items to return (1-100)
schema:
type: integer
minimum: 1
maximum: 100
default: 100
- name: after
in: query
description: Pagination cursor for the next page. Provide the value provided in `next_cursor` from the response.
schema:
type: string
- name: before
in: query
description: Pagination cursor for the previous page.
schema:
type: string
security:
- BearerAuth:
- smtp-credentials:read:all
- BearerAuth:
- smtp-credentials:read:{domain}
x-code-samples:
- lang: go
label: AhaSend Go SDK
source: "package main\n\nimport (\n \"context\"\n \"fmt\"\n \"log\"\n \"os\"\n\n \"github.com/AhaSend/ahasend-go/api\"\n \"github.com/google/uuid\"\n)\n\nfunc main() {\n client := api.NewAPIClient(api.WithAPIKey(os.Getenv(\"AHASEND_API_KEY\")))\n accountID := uuid.MustParse(os.Getenv(\"AHASEND_ACCOUNT_ID\"))\n ctx := context.Background()\n\n response, _, err := client.SMTPCredentialsAPI.GetSMTPCredentials(ctx, accountID, nil)\n if err != nil {\n log.Fatalf(\"Error getting SMTP credentials: %v\", err)\n }\n for _, credential := range response.Data {\n fmt.Printf(\"%s %s %s\\n\", credential.ID, credential.Name, credential.Username)\n }\n}\n"
- lang: javascript
label: Node.js 22+ (AhaSend SDK)
source: 'import { AhaSendClient } from "@ahasend/sdk";
const client = AhaSendClient.fromEnv();
const page = await client.smtpCredentials.list({ limit: 20 });
console.log("SMTP credentials listed.", { count: page.data.length });
'
responses:
'200':
description: List of SMTP credentials
content:
application/json:
schema:
$ref: '#/components/schemas/PaginatedSMTPCredentialsResponse'
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
post:
summary: Create SMTP Credential
description: 'Creates a new SMTP credential for SMTP authentication. A `scoped`
credential requires at least one domain and the key must have write
permission for every supplied domain. A `global` credential requires
`smtp-credentials:write:all`; supplied domains are accepted but ignored,
and the response contains `domains: []`. SMTP credentials have no
update endpoint.'
operationId: createSMTPCredential
tags:
- SMTP Credentials
parameters:
- name: account_id
in: path
required: true
description: Account ID
schema:
type: string
format: uuid
- $ref: '#/components/parameters/IdempotencyKey'
security:
- BearerAuth:
- smtp-credentials:write:all
- BearerAuth:
- smtp-credentials:write:{domain}
x-code-samples:
- lang: go
label: AhaSend Go SDK
source: "package main\n\nimport (\n \"context\"\n \"fmt\"\n \"log\"\n \"os\"\n\n \"github.com/AhaSend/ahasend-go/api\"\n \"github.com/AhaSend/ahasend-go/models/requests\"\n \"github.com/google/uuid\"\n)\n\nfunc main() {\n client := api.NewAPIClient(api.WithAPIKey(os.Getenv(\"AHASEND_API_KEY\")))\n accountID := uuid.MustParse(os.Getenv(\"AHASEND_ACCOUNT_ID\"))\n ctx := context.Background()\n\n // The SDK sends a fresh Idempotency-Key with this POST and reuses it on\n // its own retries, so a retried request is not applied twice.\n response, _, err := client.SMTPCredentialsAPI.CreateSMTPCredential(ctx, accountID, requests.CreateSMTPCredentialRequest{\n Name: \"My SMTP Credential\",\n Scope: \"global\",\n Sandbox: true,\n })\n if err != nil {\n log.Fatalf(\"Error creating SMTP credential: %v\", err)\n }\n // response.Password is returned only on create: put it in your secret\n // store now, and never log it.\n fmt.Printf(\"Created SMTP credential for username %s\\n\", response.Username)\n}\n"
- lang: javascript
label: Node.js 22+ (AhaSend SDK)
source: "import { AhaSendClient } from \"@ahasend/sdk\";\n\nconst client = AhaSendClient.fromEnv();\n// The SDK sends a fresh Idempotency-Key automatically and reuses it on its own retries.\nconst credential = await client.smtpCredentials.create({\n name: \"Production SMTP\",\n scope: \"global\",\n});\nconsole.log(\"SMTP credential created.\", { id: credential.id, name: credential.name });\n"
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateSMTPCredentialRequest'
responses:
'201':
description: SMTP credential created successfully
headers:
Idempotent-Replayed:
$ref: '#/components/headers/IdempotentReplayed'
content:
application/json:
schema:
$ref: '#/components/schemas/CreatedSMTPCredential'
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'409':
$ref: '#/components/responses/IdempotencyConflict'
'422':
$ref: '#/components/responses/IdempotencyPayloadMismatch'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/v2/accounts/{account_id}/smtp-credentials/{smtp_credential_id}:
get:
summary: Get SMTP Credential
description: 'Returns a specific SMTP credential by ID. Access requires the global
read role or a read role matching at least one associated domain.'
operationId: getSMTPCredential
tags:
- SMTP Credentials
parameters:
- name: account_id
in: path
required: true
description: Account ID
schema:
type: string
format: uuid
- name: smtp_credential_id
in: path
required: true
description: SMTP Credential ID
schema:
type: string
format: uuid
security:
- BearerAuth:
- smtp-credentials:read:all
- BearerAuth:
- smtp-credentials:read:{domain}
x-code-samples:
- lang: go
label: AhaSend Go SDK
source: "package main\n\nimport (\n \"context\"\n \"fmt\"\n \"log\"\n \"os\"\n\n \"github.com/AhaSend/ahasend-go/api\"\n \"github.com/google/uuid\"\n)\n\nfunc main() {\n client := api.NewAPIClient(api.WithAPIKey(os.Getenv(\"AHASEND_API_KEY\")))\n accountID := uuid.MustParse(os.Getenv(\"AHASEND_ACCOUNT_ID\"))\n ctx := context.Background()\n\n smtpCredentialID := uuid.MustParse(\"0194f7ac-2f6a-7d29-9f0c-9a27b19f2d1c\")\n\n response, _, err := client.SMTPCredentialsAPI.GetSMTPCredential(ctx, accountID, smtpCredentialID)\n if err != nil {\n log.Fatalf(\"Error getting SMTP credential: %v\", err)\n }\n fmt.Printf(\"%s %s (%s)\\n\", response.Name, response.Username, response.Scope)\n}\n"
- lang: javascript
label: Node.js 22+ (AhaSend SDK)
source: 'import { AhaSendClient } from "@ahasend/sdk";
const client = AhaSendClient.fromEnv();
const credentialId = "00000000-0000-4000-8000-000000000008";
const credential = await client.smtpCredentials.get(credentialId);
console.log("SMTP credential found.", { id: credential.id, name: credential.name });
'
responses:
'200':
description: SMTP credential details
content:
application/json:
schema:
$ref: '#/components/schemas/SMTPCredential'
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: SMTP credential not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
delete:
summary: Delete SMTP Credential
description: 'Deletes an SMTP credential. Access requires the global delete role or a
delete role matching at least one associated domain.'
operationId: deleteSMTPCredential
tags:
- SMTP Credentials
parameters:
- name: account_id
in: path
required: true
description: Account ID
schema:
type: string
format: uuid
- name: smtp_credential_id
in: path
required: true
description: SMTP Credential ID
schema:
type: string
format: uuid
security:
- BearerAuth:
- smtp-credentials:delete:all
- BearerAuth:
- smtp-credentials:delete:{domain}
x-code-samples:
- lang: go
label: AhaSend Go SDK
source: "package main\n\nimport (\n \"context\"\n \"fmt\"\n \"log\"\n \"os\"\n\n \"github.com/AhaSend/ahasend-go/api\"\n \"github.com/google/uuid\"\n)\n\nfunc main() {\n client := api.NewAPIClient(api.WithAPIKey(os.Getenv(\"AHASEND_API_KEY\")))\n accountID := uuid.MustParse(os.Getenv(\"AHASEND_ACCOUNT_ID\"))\n ctx := context.Background()\n\n smtpCredentialID := uuid.MustParse(\"0194f7ac-2f6a-7d29-9f0c-9a27b19f2d1c\")\n\n response, _, err := client.SMTPCredentialsAPI.DeleteSMTPCredential(ctx, accountID, smtpCredentialID)\n if err != nil {\n log.Fatalf(\"Error deleting SMTP credential: %v\", err)\n }\n fmt.Println(response.Message)\n}\n"
- lang: javascript
label: Node.js 22+ (AhaSend SDK)
source: 'import { AhaSendClient } from "@ahasend/sdk";
const client = AhaSendClient.fromEnv();
const credentialId = "00000000-0000-4000-8000-000000000008";
const result = await client.smtpCredentials.delete(credentialId);
console.log("SMTP credential deleted.", { message: result.message });
'
responses:
'200':
description: SMTP credential deleted successfully
content:
application/json:
schema:
$ref: '#/components/schemas/SuccessResponse'
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: SMTP credential not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
components:
schemas:
CreateSMTPCredentialRequest:
type: object
required:
- name
- scope
properties:
name:
type: string
maxLength: 255
description: Credential name
sandbox:
type: boolean
description: Whether this is a sandbox credential
default: false
scope:
type: string
enum:
- global
- scoped
description: Credential scope - "global" or "scoped"
domains:
type:
- array
- 'null'
items:
type: string
description: 'For `scoped`, a non-empty array is required. For `global`, this may
be omitted, null, empty, or non-empty; supplied values are accepted
but ignored.
'
allOf:
- if:
properties:
scope:
const: scoped
required:
- scope
then:
required:
- domains
properties:
domains:
type: array
minItems: 1
items:
type: string
example:
name: Production SMTP
scope: global
CreatedSMTPCredential:
allOf:
- $ref: '#/components/schemas/SMTPCredential'
- type: object
properties:
password:
type: string
description: SMTP password. Returned only when the credential is created.
required:
- password
PaginationInfo:
type: object
required:
- has_more
properties:
has_more:
type: boolean
description: Whether more items exist after the last item of this page
next_cursor:
type: string
description: Pass as `after` to fetch the next page
previous_cursor:
type: string
description: Pass as `before` to fetch the previous page; absent on a `before` page when the start of the list has been reached
example:
has_more: true
next_cursor: eyJpZCI6MTIzNH0=
ErrorResponse:
type: object
description: 'Human-readable API error. The server currently sends no stable machine
error code. In particular, clients must not parse `message` to
distinguish IP-allow-list, scope, ownership, plan, self-lockout,
suppression-duplicate, or other errors that share an HTTP status.
'
additionalProperties: false
required:
- message
properties:
message:
type: string
description: Error description
example:
message: Error message
SuccessResponse:
type: object
required:
- message
properties:
message:
type: string
description: Success message
example:
message: Operation completed successfully
PaginatedSMTPCredentialsResponse:
type: object
required:
- object
- data
- pagination
properties:
object:
type: string
enum:
- list
description: Object type identifier
data:
type: array
items:
$ref: '#/components/schemas/SMTPCredential'
description: Array of SMTP credentials
pagination:
$ref: '#/components/schemas/PaginationInfo'
SMTPCredential:
type: object
properties:
object:
type: string
enum:
- credential_smtp
description: Object type identifier
id:
type: string
format: uuid
description: Unique identifier for the SMTP credential
created_at:
type: string
format: date-time
description: When the credential was created
updated_at:
type: string
format: date-time
description: When the credential was last updated
name:
type: string
description: Credential name
username:
type: string
description: SMTP username
sandbox:
type: boolean
description: Whether this is a sandbox credential
scope:
type: string
enum:
- global
- scoped
description: Credential scope
domains:
type: array
items:
type: string
description: Domains this credential can send from. Always present; global credentials return an empty array.
required:
- object
- id
- created_at
- updated_at
- name
- username
- sandbox
- scope
- domains
headers:
IdempotencyInProgress:
description: 'Identifies an idempotency-key execution that is still in progress. This
header is emitted only on the specialized HTTP 409 response.
'
schema:
type: string
enum:
- 'false'
IdempotentReplayed:
description: 'Indicates that this response is a stored replay of a previous identical request.
When this header is absent, the response is not a stored replay. Ordinary fresh
responses do not send `false`; that value is reserved for an in-progress 409.
'
schema:
type: string
enum:
- 'true'
IdempotencyRetryAfter:
description: 'Positive whole number of seconds after which the in-progress execution
lease may be retried with the same key and unchanged request.
'
schema:
type: integer
minimum: 1
parameters:
IdempotencyKey:
name: Idempotency-Key
in: header
required: false
description: 'Optional idempotency key for safe request retries. Must be a unique string for each logical request.
An identical request with a completed stored outcome returns the original status and body. An in-progress
execution returns 409, a changed method/path/body returns 422, and a released 5xx execution may run again.
Keys for non-secret responses expire after 24 hours. API-key create responses include a one-time `secret_key`,
so successful encrypted replay responses for those operations expire after 5 minutes.
'
schema:
type: string
maxLength: 255
example: user-12345-create-domain-20240101
responses:
IdempotencyConflict:
description: 'Request in progress. This response always includes
`Idempotent-Replayed: false` and a positive integer `Retry-After`.
Clients must use that header tuple, not the human-readable message, to
distinguish this retryable idempotency state from other HTTP 409s.
'
headers:
Idempotent-Replayed:
$ref: '#/components/headers/IdempotencyInProgress'
Retry-After:
$ref: '#/components/headers/IdempotencyRetryAfter'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
message: A request with this idempotency key is already in progress
IdempotencyPayloadMismatch:
description: 'Idempotency key was already used with a different HTTP method, resolved
request path, or request body. This response includes neither
`Idempotent-Replayed` nor `Retry-After`.
'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
message: idempotency key was already used with a different request payload
securitySchemes:
BearerAuth:
type: http
scheme: bearer
bearerFormat: aha-sk-64-CHARACTER-RANDOM-STRING
description: 'API key for authentication. Non-empty Security Requirement values are
AhaSend API-key roles. Roles listed within one requirement object are
jointly required; separate requirement objects are alternatives.
'