Files
Wez Furlong 407379dd36 track sched->readyq fan in, clean up some internals
This commit removes the queue_name_for_config_change_purposes_only wart
from inside the readyq machinery.  This field had a bunch of caveats
and could in some cases become stale if the originating scheduled
queue had aged out.

It's been replaced by a HashSet of Weak refs back to the associated
scheduled queues so that it is now possible to walk the reverse of
the fan-in graph when we need to.
2026-06-25 13:41:01 +01:00

2614 lines
95 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"openapi": "3.1.0",
"info": {
"title": "kumod",
"description": "",
"license": {
"name": "Apache-2.0"
},
"version": "2026.06.23-db312236"
},
"paths": {
"/api/admin/abort-ready-q-conn/v1": {
"post": {
"tags": [
"inspect",
"kcli:abort-ready-q-conn"
],
"summary": "Abort the dispatcher task identified by `session_id` within the\nnamed ready queue.",
"description": "{{since('dev')}}\n\nReturns 404 if either the queue or the session is unknown.",
"operationId": "abort_v1",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AbortReadyQConnV1Request"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Aborted"
},
"404": {
"description": "No matching queue or session"
}
}
}
},
"/api/admin/bounce/v1": {
"get": {
"tags": [
"bounce",
"kcli:bounce-list"
],
"summary": "Allows the system operator to list all currently active administrative bounces that have been\nconfigured.",
"operationId": "bounce_v1_list",
"responses": {
"200": {
"description": "Returned information about current admin bounces",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/BounceV1ListEntry"
}
}
}
}
}
}
},
"post": {
"tags": [
"bounce",
"kcli:bounce"
],
"summary": "Allows the system operator to administratively bounce messages that match\ncertain criteria, or if no criteria are provided, ALL messages.",
"description": "!!! danger\n There is no way to undo the actions carried out by this request!",
"operationId": "bounce_v1",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BounceV1Request"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Bounce added successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BounceV1Response"
}
}
}
}
}
},
"delete": {
"tags": [
"bounce",
"kcli:bounce-cancel"
],
"summary": "Allows the system operator to delete an administrative bounce entry by its id.",
"operationId": "bounce_v1_delete",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BounceV1CancelRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Removed the requested bounce id"
},
"404": {
"description": "The requested bounce id is no longer, or never was, valid"
}
}
}
},
"/api/admin/bump-config-epoch": {
"post": {
"tags": [
"config"
],
"summary": "Allows the system operator to trigger a configuration epoch bump,\nwhich causes various configs that are using the Epoch strategy to\nbe re-evaluated by triggering the appropriate callbacks.",
"operationId": "bump_config_epoch",
"responses": {
"200": {
"description": "bump successful"
}
}
}
},
"/api/admin/inspect-message/v1": {
"get": {
"tags": [
"inspect",
"kcli:inspect-message"
],
"summary": "Retrieve information about a message given its spool id.",
"operationId": "inspect_v1",
"parameters": [
{
"name": "id",
"in": "query",
"description": "The spool identifier for the message whose information\nis being requested",
"required": true,
"schema": {
"$ref": "#/components/schemas/SpoolId"
}
},
{
"name": "want_body",
"in": "query",
"description": "If true, return the message body in addition to the\nmetadata",
"required": false,
"schema": {
"type": "boolean"
}
}
],
"responses": {
"200": {
"description": "Obtained message information",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InspectMessageV1Response"
}
}
}
}
}
}
},
"/api/admin/inspect-ready-q/v1": {
"get": {
"tags": [
"inspect",
"kcli:inspect-ready-q"
],
"summary": "Retrieve a snapshot of a ready queue's state, configuration, and\nthe dispatcher tasks that are currently handling connections on\nits behalf.",
"description": "{{since('dev')}}",
"operationId": "inspect_v1",
"parameters": [
{
"name": "queue_name",
"in": "query",
"description": "The name of the ready queue to inspect.",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "include_scheduled_queues",
"in": "query",
"description": "When true, the response includes the list of scheduled queue\nnames that have promoted messages into this ready queue.\nWalking the set is bounded but proportional to the number of\nlive scheduled queues, so the field is opt-in.",
"required": false,
"schema": {
"type": "boolean"
}
}
],
"responses": {
"200": {
"description": "Ready queue snapshot",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InspectReadyQV1Response"
}
}
}
},
"404": {
"description": "No such ready queue"
}
}
}
},
"/api/admin/inspect-sched-q/v1": {
"get": {
"tags": [
"inspect",
"kcli:inspect-sched-q"
],
"summary": "Retrieve information about messages in a scheduled queue.",
"operationId": "inspect_v1",
"parameters": [
{
"name": "queue_name",
"in": "query",
"description": "The name of the scheduled queue",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "want_body",
"in": "query",
"description": "If true, return the message body in addition to the\nmetadata",
"required": false,
"schema": {
"type": "boolean"
}
},
{
"name": "limit",
"in": "query",
"description": "Return up to `limit` messages in the queue sample.\nDepending on the strategy configured for the queue,\nmessages may not be directly reachable via this endpoint.\nIf no limit is provided, all messages in the queue will\nbe sampled.",
"required": false,
"schema": {
"type": [
"integer",
"null"
],
"minimum": 0
}
}
],
"responses": {
"200": {
"description": "Obtained queue information",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InspectQueueV1Response"
}
}
}
}
}
}
},
"/api/admin/memory/stats": {
"get": {
"tags": [
"memory"
],
"summary": "Returns information about the system memory usage in an unstructured\nhuman readable format. The output is not machine parseable and may\nchange without notice between versions of kumomta.",
"operationId": "memory_stats",
"responses": {
"200": {
"description": "stats were returned"
}
}
}
},
"/api/admin/ready-q-states/v1": {
"get": {
"tags": [
"inspect",
"kcli:queue-summary"
],
"summary": "Retrieve information about the states that apply to a set of\nready queues, or all queues if no specific queues were named\nin the request.",
"operationId": "readyq_states",
"parameters": [
{
"name": "queues",
"in": "query",
"description": "Which queues to request. If empty, request all queue states.",
"required": false,
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
}
],
"responses": {
"200": {
"description": "Obtained state information",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ReadyQueueStateResponse"
}
}
}
}
}
}
},
"/api/admin/rebind/v1": {
"post": {
"tags": [
"rebind",
"kcli:rebind"
],
"summary": "Allows the system operator to administratively rebind messages.\nRebinding can target queues that match\ncertain criteria, or if no criteria are provided, ALL queues.\nRebinding is moving a message from one scheduled queue into another.\nQueue selection is based upon the envelope recipient and message\nmetadata as described in <https://docs.kumomta.com/reference/queues/>",
"operationId": "rebind_v1",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RebindV1Request"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Rebind added successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RebindV1Response"
}
}
}
}
}
}
},
"/api/admin/resolve-egress-path/v1": {
"get": {
"tags": [
"inspect",
"kcli:resolve-egress-path"
],
"summary": "Resolve the effective egress path configuration and throughput\nceilings for a destination domain and egress source.",
"description": "{{since('dev')}}\n\nMirrors the diagnostic that `kcli inspect-ready-q` provides for\nlive ready queues, but operates from the configuration side: it\ninvokes the `get_queue_config` and `get_egress_path_config` event\ncallbacks for the supplied `domain`/`source` pair, performs the\nassociated MX lookup, and returns the resulting configuration,\nderived ceilings and the ready-queue name that would be used.",
"operationId": "resolve_v1",
"parameters": [
{
"name": "domain",
"in": "query",
"description": "Destination domain. Drives the MX lookup and is passed\nthrough to the `get_egress_path_config` event callback.",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "source",
"in": "query",
"description": "Egress source name. Defaults to \"unspecified\" if omitted.",
"required": false,
"schema": {
"type": [
"string",
"null"
]
}
}
],
"responses": {
"200": {
"description": "Resolved egress path",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ResolveEgressPathV1Response"
}
}
}
}
}
}
},
"/api/admin/set_diagnostic_log_filter/v1": {
"post": {
"tags": [
"logging",
"kcli:set-log-filter"
],
"summary": "Changes the diagnostic log filter dynamically.\nSee <https://docs.kumomta.com/reference/kumo/set_diagnostic_log_filter/>\nfor more information on diagnostic log filters.",
"operationId": "set_diagnostic_log_filter_v1",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SetDiagnosticFilterRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Diagnostic level set successfully"
}
}
}
},
"/api/admin/spool-compact/v1": {
"post": {
"tags": [
"spool",
"kcli:spool-compact"
],
"summary": "Force a flush and major compaction of the named spool.",
"description": "Primarily intended for operational diagnostics and integration\ntests. For rocksdb-backed spools this performs a flush of the\nmemtable followed by a full-keyspace compaction. For other spool\nkinds it is a no-op.\n\nReturns an error if the named spool is not defined, or if the\nunderlying storage reports an error (for example, a missing or\ncorrupt SST file encountered during compaction).",
"operationId": "spool_compact_v1",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SpoolCompactV1Request"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Compaction completed successfully"
}
}
}
},
"/api/admin/suspend-ready-q/v1": {
"get": {
"tags": [
"suspend",
"kcli:suspend-ready-q-list"
],
"summary": "List the active ready-queue suspensions",
"operationId": "list",
"responses": {
"200": {
"description": "Suspended",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SuspendReadyQueueV1ListEntry"
}
}
}
}
}
},
"post": {
"tags": [
"suspend",
"kcli:suspend-ready-q"
],
"summary": "Define a suspension for a ready queue",
"operationId": "suspend",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SuspendReadyQueueV1Request"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Suspended",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SuspendV1Response"
}
}
}
}
}
},
"delete": {
"tags": [
"suspend",
"kcli:suspend-ready-q-cancel"
],
"summary": "Remove a ready-queue suspension",
"operationId": "delete",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SuspendV1CancelRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Removed the suspension"
},
"404": {
"description": "Suspension either expired or was never valid"
}
}
}
},
"/api/admin/suspend/v1": {
"get": {
"tags": [
"suspend",
"kcli:suspend-list"
],
"summary": "List the active scheduled-queue suspensions",
"operationId": "list",
"responses": {
"200": {
"description": "Suspended",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SuspendV1ListEntry"
}
}
}
}
}
},
"post": {
"tags": [
"suspend",
"kcli:suspend"
],
"summary": "Define a suspension for a scheduled queue",
"operationId": "suspend",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SuspendV1Request"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Suspended",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SuspendV1Response"
}
}
}
}
}
},
"delete": {
"tags": [
"suspend",
"kcli:suspend-cancel"
],
"summary": "Remove a scheduled-queue suspension",
"operationId": "delete",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SuspendV1CancelRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Removed the suspension"
},
"404": {
"description": "Suspension either expired or was never valid"
}
}
}
},
"/api/admin/task-dump": {
"get": {
"tags": [
"debugging"
],
"summary": "Returns a dump of the runtime task state.",
"description": "{{since('2026.04.09-ea3b2a9b')}}\n\nThe output is not machine parseable and may change without notice\nbetween versions of kumomta.\n\nCapturing the dump is very expensive and can take several seconds.\nCapturing a dump is not guaranteed to succeed.\n\nAt the time of writing, capturing a dump can cause a subsequent\ngraceful shutdown to get stuck, unless you repeatedly trigger\nthis API endpoint a few more times to \"clock through\" the shutdown\nprocess and enable it to complete successfully.",
"operationId": "task_dump",
"responses": {
"200": {
"description": "data was returned"
}
}
}
},
"/api/admin/trace-smtp-client/v1": {
"get": {
"tags": [
"debugging",
"kcli:trace-smtp-client"
],
"summary": "This is a websocket endpoint that provides outbound SMTP tracing.\nIt cannot be described via auto-generated docs extracted from the JSON Schema.",
"operationId": "trace",
"responses": {}
}
},
"/api/admin/trace-smtp-server/v1": {
"get": {
"tags": [
"debugging",
"kcli:trace-smtp-server"
],
"summary": "This is a websocket endpoint that provides inbound SMTP tracing.\nIt cannot be described via auto-generated docs extracted from the JSON Schema.",
"operationId": "trace",
"responses": {}
}
},
"/api/admin/xfer/cancel/v1": {
"post": {
"tags": [
"xfer"
],
"summary": "Allows the system operator to stop a message transfer that was\npreviously initiated via the `/api/admin/xfer/v1` API endpoint.\nThe cancellation works by walking the xfer scheduled queue,\nreversing the metadata changes made as part of setting up the\nxfer (to restore the original scheduling information) and then\nre-inserting the message into an appropriate scheduled queue.",
"description": "The cancellation is \"instantaneous\" in the sense that it applies\njust once to the specified xfer scheduled queue. Any other messages\nthat are in-flight or imminently about to be reinserted into\nthat scheduled queue will not be considered, so you may need\nto trigger the cancellation a few times over short time span\nto ensure that all messages are taken out of the xfer queue.\n\nCancellation requests always complete asynchronously because\nthey may operate on very large quantities of messages, and it\nis infeasible to wait for completion in the context of\na single HTTP request.",
"operationId": "xfer_cancel_v1",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/XferCancelV1Request"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Xfer added successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/XferCancelV1Response"
}
}
}
}
}
}
},
"/api/admin/xfer/v1": {
"post": {
"tags": [
"xfer"
],
"summary": "Allows the system operator to transfer messages from the current\nnode to some other target node.\nThe transfer (xfer) can target queues that match\ncertain criteria, or if no criteria are provided, ALL queues.\nQueue selection is based upon the envelope recipient and message\nmetadata as described in <https://docs.kumomta.com/reference/queues/>.\nMessages in the selected queues will be moved into an xfer queue\nwhose name is based on the target specified by the transfer request.",
"operationId": "xfer_v1",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/XferV1Request"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Xfer added successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/XferV1Response"
}
}
}
}
}
}
},
"/api/check-liveness/v1": {
"get": {
"tags": [
"liveness"
],
"summary": "Useful for load balancers to determine when service is available\nand ready to receive messages",
"operationId": "check_liveness_v1",
"responses": {
"200": {
"description": "ready to accept messages"
},
"503": {
"description": "service is not currently available"
}
}
}
},
"/api/inject/v1": {
"post": {
"tags": [
"inject"
],
"summary": "Inject a message using a given message body, with template expansion,\nto a list of recipients.\nBoth message assembly and templating are supported, and multiple recipients\nand template substitutions can be passed in a single request.",
"description": "The body of the post request must be a JSON object; here's a very basic\nexample:\n\n```json\n{\n \"envelope_sender\": \"noreply@example.com\",\n \"content\": \"Subject: hello\\n\\nHello there\",\n \"recipients\": [\n {\n \"email\": \"recipient@example.com\",\n }\n ]\n}\n```\n\nThe response will look something like:\n\n```json\n{\n \"success_count\": 1,\n \"fail_count\": 0,\n \"failed_recipients\": [],\n \"errors\": []\n}\n```\n\n!!! note\n The `success_count` will always be reported as `0` when using `deferred_generation: true`.\n\n## Template Substitution\n\nThe injection API embeds the Mini Jinja templating engine, with\nsome supplemental extensions. The syntax and extensions are\n[documented here](../../template/index.md). You may use the\n`template_dialect` field to specify an alternative templating\nengine, or disable templating by setting the dialect to `\"Static\"`.\n\nFor each recipient, the set of variables pre-defined in the template are:\n\n* The set of global substitutions defined by `request.substitutions`\n\n* The set of per-recipient substitutions, if any are defined in\n `request.recipients[].substitutions`, are overlaid and take precedence over\n any global substitutions\n\n* The recipient `name` and `email` fields are assigned to the `\"name\"` and\n `\"email\"` variables respectively.\n\n!!! note\n Both sets of *substitutions* can use any JSON value for the values of\n the variables; they don't have to be strings.\n\nA very basic example of using templating:\n\n```json\n{\n \"envelope_sender\": \"noreply@example.com\",\n \"content\": \"To: \\\"{{ name }}\\\" <{{ email }}>\\nSubject: hello\\n\\nHello {{ name }}!\",\n \"recipients\": [\n {\n \"email\": \"recipient@example.com\",\n \"name\": \"John Smith\"\n }\n ]\n}\n```\n\nwould result in a message with the following content:\n\n```\nTo: \"John Smith\" <recipient@example.com>\nSubject: hello\n\nHello John Smith!\n```",
"operationId": "inject_v1",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InjectV1Request"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Message(s) injected successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InjectV1Response"
}
}
}
},
"422": {
"description": "One or more fields in the content section have syntax errors"
}
}
}
},
"/api/machine-info": {
"get": {
"tags": [
"debugging"
],
"summary": "Returns information identifying this instance",
"operationId": "machine_info",
"responses": {
"200": {
"description": "Machine information",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MachineInfoV1"
}
}
}
}
}
}
},
"/api/xfer/inject/v1": {
"post": {
"tags": [
"xfer"
],
"summary": "Performs a message transfer.",
"description": "!!! warning\n This is considered to be an internal API and should not be\n used by external consumers. It is intentionally under-specified\n in these auto-generated docs.\n\nYou probably want to look at the [/api/admin/xfer/v1](api_admin_xfer_v1_post.md)\nendpoint which is used to request that a specific queue should\nbe re-routed via this XFER endpoint on the target node.",
"operationId": "inject_xfer_v1",
"requestBody": {
"content": {
"text/plain": {
"schema": {
"type": "string"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Message transferred successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/XferResponseV1"
}
}
}
}
}
}
},
"/metrics": {
"get": {
"tags": [],
"summary": "Returns the current set of metrics in\n[Prometheus Text Exposition Format](https://prometheus.io/docs/instrumenting/exposition_formats/).",
"description": "!!! note\n Metrics generally represent data at the current point in time,\n to be consumed by an external system (such as Prometheus) which\n then in turn can build time series data around those metrics.\n\n In addition, in order to avoid unbounded RAM usage for systems\n with many queues, a number of queue- or service-specific metrics\n will be automatically pruned away when the corresponding queue\n idles out for a period of time.\n\nIn the default configuration, access to this endpoint requires *Trusted IP*\nauthentication. See the [Authorization](../../access_control.md) documentation\nfor more information on adjusting ACLs.\n\nSee also [metrics.json](metrics.json_get.md).\n\n## Metric Documentation\n\n* [Metrics exported by kumod](../../metrics/kumod/index.md)\n\n## Example Data\n\nHere's an example of the shape of the data. The precise set of\ncounters will vary as we continue to enhance KumoMTA.\n\nYou can see the current list by querying the endpoint with no arguments:\n\n```console\n$ curl http://localhost:8000/metrics\n```\n\n```txt\n{% include \"reference/http/sample-metrics.txt\" %}\n```",
"operationId": "report_metrics",
"responses": {
"200": {
"description": "",
"content": {
"text/plain": {}
}
}
}
}
},
"/metrics.json": {
"get": {
"tags": [],
"summary": "Returns the current set of metrics in a json representation.\nThis is easier to consume than the Prometheus Exposition format, but\nis more resource intensive to produce and parse when the number of\nmetrics is large, such as for a busy server.",
"description": "!!! note\n Metrics generally represent data at the current point in time,\n to be consumed by an external system (such as Prometheus) which\n then in turn can build time series data around those metrics.\n\n In addition, in order to avoid unbounded RAM usage for systems\n with many queues, a number of queue- or service-specific metrics\n will be automatically pruned away when the corresponding queue\n idles out for a period of time.\n\nIn the default configuration, access to this endpoint requires *Trusted IP*\nauthentication. See the [Authorization](../../access_control.md) documentation\nfor more information on adjusting ACLs.\n\nSee also [metrics](metrics_get.md).\n\n## Metric Documentation\n\n* [Metrics exported by kumod](../../metrics/kumod/index.md)\n\n## Example Data\n\nHere's an example of the shape of the data. The precise set of\ncounters will vary as we continue to enhance KumoMTA:\n\n```json\n{% include \"reference/http/sample-metrics.json\" %}\n```",
"operationId": "report_metrics_json",
"responses": {
"200": {
"description": "",
"content": {
"application/json": {}
}
}
}
}
}
},
"components": {
"schemas": {
"AbortReadyQConnV1Request": {
"type": "object",
"description": "Request body for the abort-ready-q-conn endpoint.\n\n{{since('dev')}}",
"required": [
"queue_name",
"session_id"
],
"properties": {
"queue_name": {
"type": "string"
},
"session_id": {
"type": "string",
"format": "uuid"
}
}
},
"Attachment": {
"type": "object",
"required": [
"data",
"content_type"
],
"properties": {
"base64": {
"type": "boolean",
"description": "If true, the `data` field must be encoded as base64"
},
"content_id": {
"type": [
"string",
"null"
],
"description": "Optional `Content-ID` header for this attachment.\nIf specified, this attachment will be added as an\ninline attachment and a multipart/related MIME\ncontainer will be generated in the message to hold\nit and the textual content."
},
"content_type": {
"type": "string",
"description": "The MIME `Content-Type` header that should be\nset for this attachment."
},
"data": {
"type": "string",
"description": "The content of the payload.\nThis is interpreted as UTF-8 text unless the\n`base64` field is set to `true`."
},
"file_name": {
"type": [
"string",
"null"
],
"description": "The the preferred filename for the attachment"
}
},
"additionalProperties": false,
"examples": [
{
"base64": true,
"content_id": "my-image",
"content_type": "image/gif",
"data": "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
"file_name": "pixel.gif"
}
]
},
"BounceV1CancelRequest": {
"type": "object",
"required": [
"id"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
}
}
},
"BounceV1ListEntry": {
"type": "object",
"required": [
"id",
"reason",
"duration",
"bounced",
"total_bounced"
],
"properties": {
"bounced": {
"type": "object",
"description": "A map of queue name to number of bounced messages that\nwere processed by this entry since it was created.",
"additionalProperties": {
"type": "integer",
"minimum": 0
},
"propertyNames": {
"type": "string"
},
"example": {
"gmail.com": 200,
"yahoo.com": 100
}
},
"campaign": {
"type": [
"string",
"null"
],
"description": "The campaign field of the original request, if any.",
"example": "campaign_name"
},
"domain": {
"type": [
"string",
"null"
],
"description": "The domain field of the original request, if any.",
"example": "example.com"
},
"duration": {
"type": "string",
"description": "The time remaining until this entry expires and is automatically\nremoved."
},
"id": {
"type": "string",
"format": "uuid",
"description": "The id of this bounce rule. Corresponds to the `id` field\nreturned by the originating request that set up the bounce,\nand can be used to identify this particular entry if you\nwish to delete it later.",
"example": "552016f1-08e7-4e90-9da3-fd5c25acd069"
},
"reason": {
"type": "string",
"description": "The reason field of the original request",
"example": "cleaning up a bad send"
},
"routing_domain": {
"type": [
"string",
"null"
],
"description": "The routing_domain field of the original request, if any.",
"example": "routing_domain.com"
},
"tenant": {
"type": [
"string",
"null"
],
"description": "The tenant field of the original request, if any.",
"example": "tenant_name"
},
"total_bounced": {
"type": "integer",
"description": "The sum of the number of bounced messages reported by\nthe `bounced` field.",
"minimum": 0
}
}
},
"BounceV1Request": {
"type": "object",
"description": "Describes which messages should be bounced.\nThe criteria apply to the scheduled queue associated\nwith a given message.\n\n!!! danger\n If you specify none of `domain`, `campaign`, `tenant`,\n `routing_domain` or `queue`, then **ALL** queues will\n be bounced.\n\n With great power comes great responsibility!\n",
"required": [
"reason"
],
"properties": {
"campaign": {
"type": [
"string",
"null"
],
"description": "The campaign name to match. If omitted, any campaign will match.",
"example": "campaign_name"
},
"domain": {
"type": [
"string",
"null"
],
"description": "The domain name to match. If omitted, any domain will match.",
"example": "example.com"
},
"duration": {
"type": [
"string",
"null"
],
"description": "Defaults to \"5m\". Specifies how long this bounce directive remains active.\nWhile active, newly injected messages that match the bounce criteria\nwill also be bounced.",
"example": "20m"
},
"expires": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "instead of specifying the duration, you can set an explicit\nexpiration timestamp"
},
"queue_names": {
"type": "array",
"items": {
"type": "string"
},
"description": "If present, queue_names takes precedence over `campaign`,\n`tenant`, and `domain` and specifies the exact set of\nscheduled queue names to which the bounce applies.",
"example": [
"campaign_name:tenant_name@example.com"
]
},
"reason": {
"type": "string",
"description": "Reason to log in the delivery log. Each matching message will be bounced\nwith an AdminBounce record unless you suppress logging.\nThe reason will also be shown in the list of currently active admin\nbounces.",
"example": "Cleaning up a bad send"
},
"routing_domain": {
"type": [
"string",
"null"
],
"description": "The routing_domain name to match. If omitted, any routing_domain will match.\n{{since('2023.08.22-4d895015', inline=True)}}",
"example": "routing_domain.com"
},
"suppress_logging": {
"type": "boolean",
"description": "If true, do not generate AdminBounce delivery logs for matching\nmessages.",
"default": false
},
"tenant": {
"type": [
"string",
"null"
],
"description": "The tenant to match. If omitted, any tenant will match.",
"example": "tenant_name"
}
},
"additionalProperties": false
},
"BounceV1Response": {
"type": "object",
"required": [
"id",
"bounced",
"total_bounced"
],
"properties": {
"bounced": {
"type": "object",
"description": "Deprecated: this field is no longer populated, as bounces\nare now always asynchronous. In earlier versions the following\napplies:\n\nA map of queue name to number of bounced messages that\nwere processed as part of the initial sweep.\nAdditional bounces may be generated if/when other messages\nthat match the rule are discovered, but those obviously\ncannot be reported in the context of the initial request.",
"additionalProperties": {
"type": "integer",
"minimum": 0
},
"propertyNames": {
"type": "string"
},
"deprecated": true,
"example": {
"gmail.com": 200,
"yahoo.com": 100
}
},
"id": {
"type": "string",
"format": "uuid",
"description": "The id of the bounce rule that was registered.\nThis can be used later to delete the rule if desired.",
"example": "552016f1-08e7-4e90-9da3-fd5c25acd069"
},
"total_bounced": {
"type": "integer",
"description": "Deprecated: this field is no longer populated, as bounces are\nnow always asynchronous. In earlier versions the following applies:\n\nThe sum of the number of bounced messages reported by\nthe `bounced` field.",
"deprecated": true,
"example": 300,
"minimum": 0
}
}
},
"CeilingSource": {
"oneOf": [
{
"type": "object",
"description": "The primary configured term for this axis:\n`connection_limit`, `max_message_rate`,\n`max_connection_rate`, or `source_selection_rate`.",
"required": [
"kind"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"primary"
]
}
}
},
{
"type": "object",
"description": "A named entry from the corresponding `additional_*` map.",
"required": [
"name",
"kind"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"additional"
]
},
"name": {
"type": "string"
}
}
},
{
"type": "object",
"description": "Synthetic ceiling formed from\n`max_deliveries_per_connection × max_connection_rate`.\nApplies only to the message-rate axis: each connection\ndelivers at most `max_deliveries_per_connection` messages\nbefore reconnecting, and new connections are throttled by\n`max_connection_rate`, so the product is a hard ceiling on\nsystem-wide message rate independent of `max_message_rate`.",
"required": [
"kind"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"reconnect_cycling"
]
}
}
},
{
"type": "object",
"description": "A constraint contributed from a configuration layer outside\nthe egress path config. `name` is a free-form, human-readable\ndescription of where the constraint came from (for example,\n`\"scheduled queue max_message_rate\"`).",
"required": [
"name",
"kind"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"other"
]
},
"name": {
"type": "string"
}
}
}
],
"description": "Which configuration term produced an `EffectiveCeiling`.\n\n{{since('dev')}}"
},
"Content": {
"oneOf": [
{
"type": "string",
"description": "A complete MIME message string",
"example": "From: user@example.com\nSubject: Hello\n\nHello there"
},
{
"type": "object",
"description": "Describe the MIME structure to be created",
"properties": {
"amp_html_body": {
"type": [
"string",
"null"
],
"description": "If set, will be used to create a text/x-amp-html part.\nThis is available {{since('2026.03.04-bb93ecb1', inline=True)}}",
"example": "<!doctype html><html amp4email>...</html>"
},
"attachments": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Attachment"
},
"description": "Optional list of attachments."
},
"from": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/components/schemas/FromHeader",
"description": "Set the From: header"
}
]
},
"headers": {
"type": "object",
"description": "Optional map of headers to include in the message.\nThis is a map of header name to header value",
"additionalProperties": {
"type": "string"
},
"propertyNames": {
"type": "string"
},
"example": {
"X-Tenant": "MyTenant"
}
},
"html_body": {
"type": [
"string",
"null"
],
"description": "If set, will be used to create a text/html part",
"example": "<p>This is the <b>HTML</b> part</p>"
},
"reply_to": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/components/schemas/FromHeader",
"description": "Set the Reply-To: header"
}
]
},
"subject": {
"type": [
"string",
"null"
],
"description": "Set the Subject: header"
},
"text_body": {
"type": [
"string",
"null"
],
"description": "If set, will be used to create a text/plain part",
"example": "This is the plain text part"
}
}
}
],
"description": "The message content.\nCan either be a fully formed MIME message, or a json\nobject describing the MIME structure that should be created."
},
"DispatcherPhase": {
"oneOf": [
{
"type": "string",
"enum": [
"Starting"
]
},
{
"type": "object",
"required": [
"AcquiringLease"
],
"properties": {
"AcquiringLease": {
"type": "object",
"required": [
"label"
],
"properties": {
"label": {
"type": "string"
}
}
}
}
},
{
"type": "string",
"enum": [
"Idle"
]
},
{
"type": "object",
"required": [
"AccumulatingBatch"
],
"properties": {
"AccumulatingBatch": {
"type": "object",
"required": [
"have",
"want"
],
"properties": {
"have": {
"type": "integer",
"format": "u-int32",
"minimum": 0
},
"want": {
"type": "integer",
"format": "u-int32",
"minimum": 0
}
}
}
}
},
{
"type": "string",
"enum": [
"ConnectionRateThrottled"
]
},
{
"type": "string",
"enum": [
"MessageRateThrottled"
]
},
{
"type": "string",
"enum": [
"AttemptingConnection"
]
},
{
"type": "string",
"enum": [
"DeliveringMessage"
]
},
{
"type": "string",
"enum": [
"Closing"
]
}
],
"description": "Phase of a dispatcher task within a ready queue.\n\n{{since('dev')}}"
},
"DispatcherSummary": {
"type": "object",
"description": "Per-dispatcher summary returned by the inspect-ready-q endpoint.\n\n{{since('dev')}}",
"required": [
"session_id",
"started_at",
"age",
"phase",
"time_in_current_phase",
"messages_delivered",
"messages_transfailed",
"messages_failed",
"delivered_this_connection",
"overall_rate_per_sec"
],
"properties": {
"age": {
"type": "string"
},
"delivered_this_connection": {
"type": "integer",
"format": "u-int64",
"minimum": 0
},
"detail": {
"type": [
"string",
"null"
]
},
"messages_delivered": {
"type": "integer",
"format": "u-int64",
"minimum": 0
},
"messages_failed": {
"type": "integer",
"format": "u-int64",
"minimum": 0
},
"messages_transfailed": {
"type": "integer",
"format": "u-int64",
"minimum": 0
},
"overall_rate_per_sec": {
"type": "number",
"format": "double"
},
"phase": {
"$ref": "#/components/schemas/DispatcherPhase"
},
"session_id": {
"type": "string",
"format": "uuid"
},
"started_at": {
"type": "string",
"format": "date-time"
},
"time_in_current_phase": {
"type": "string"
}
}
},
"EffectiveCeiling": {
"type": "object",
"description": "Steady-state ceiling for a single throughput axis, with a tag\nfor which configuration term produced it.\n\n{{since('dev')}}",
"required": [
"value",
"source",
"display"
],
"properties": {
"display": {
"type": "string",
"description": "Pre-formatted human display preserving the operator's\noriginal configuration units. A rate configured as\n`10000/hr` renders here as `10000/h` rather than `2.78/s`.\nFor concurrency, the integer count. For the synthetic\nreconnect-cycling ceiling, the formula\n`max_deliveries_per_connection × <connection_rate>`."
},
"source": {
"$ref": "#/components/schemas/CeilingSource"
},
"value": {
"type": "number",
"format": "double",
"description": "Canonical value. For rate axes: events per second; useful\nfor numeric comparison. For concurrency: a count."
}
}
},
"EffectiveConstraints": {
"type": "object",
"description": "Steady-state ceilings implied by an `EgressPathConfig`. Each\nceiling carries a tag for which configuration term produced it.\n\n{{since('dev')}}\n\nThese are per-queue ceilings; shared limits in `additional_*`\nmaps are reported at their full value and may be tighter in\npractice when the bucket is contended.",
"required": [
"max_concurrent_dispatchers"
],
"properties": {
"max_concurrent_dispatchers": {
"$ref": "#/components/schemas/EffectiveCeiling"
},
"max_connection_rate": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/components/schemas/EffectiveCeiling"
}
]
},
"max_message_rate": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/components/schemas/EffectiveCeiling",
"description": "None when neither `max_message_rate` nor any\n`additional_message_rate_throttles` entry nor\n`max_connection_rate` is configured."
}
]
},
"max_message_rate_declared": {
"type": [
"string",
"null"
],
"description": "Pre-formatted display of the declared `max_message_rate` when\na different term (typically `ReconnectCycling`) wins the\nminimum. Lets renderers show a \"declared but unreachable\"\nannotation. Uses the operator's original units."
},
"max_source_selection_rate": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/components/schemas/EffectiveCeiling"
}
]
}
}
},
"FromHeader": {
"type": "object",
"required": [
"email"
],
"properties": {
"email": {
"type": "string",
"format": "email",
"description": "The email address of the sender",
"example": "sales@sender-example.com"
},
"name": {
"type": [
"string",
"null"
],
"description": "The displayable name of the sender",
"example": "Sales"
}
},
"additionalProperties": false
},
"HttpTraceHeaders": {
"$ref": "#/components/schemas/TraceHeaders"
},
"InjectV1Request": {
"type": "object",
"required": [
"envelope_sender",
"recipients",
"content"
],
"properties": {
"content": {
"$ref": "#/components/schemas/Content",
"description": "Specifies the message content. It can either be a string value or\na JSON object describing how to build a the message.\n\nIf a simple string is provided, it must be an RFC822 compliant\nmessage. If template substitutions are used in the request, then\nthe entire RFC822 message string is used as-is for the template;\nno message parsing or decoding is performed as part of template\nexpansion.\n\nAlternatively the content can be specified as a JSON object as\ndemonstrated in the docs for the `Content` type.\n\nWhen building a message, template substitutions are\napplied to the `text_body`, `html_body`,\n`amp_html_body` and `headers` fields.\n\nAttachments are not subject to template substitution."
},
"deferred_generation": {
"type": "boolean",
"description": "{{since('2024.11.08-d383b033', inline=True)}}\n\nWhen set to true, the injection request will be queued\nand the actual generation and substitution will happen\nasynchronously with respect to the injection request.\nThe default mode of operation is to respond to the injection request only\nonce every message in the request has been enqueued to the internal queue\nsystem. This provides *back pressure* to the injection system and prevents\nthe service from being overwhelmed if the rate of ingress exceeds the\nmaximum rate of egress.\n\nThe result of this back pressure is that the latency of the injection request\ndepends on the load of the system.\n\nSetting `deferred_generation: true` in the request alters the processing flow:\ninstead of immediately expanding the request into the desired number of\nmessages and queueing them up, the injection request is itself queued up and\nprocessed asynchronously with respect to the incoming request.\n\nThis `deferred_generation` submission is typically several orders of magnitude\nfaster than the immediate generation mode, so it is possible to very very quickly\nqueue up large batches of messages this way.\n\nThe deferred generation requests are queued internally to a special queue\nnamed `generator.kumomta.internal` that will process them by spawning each\nrequest into the `httpinject` thread pool.\n\nYou will likely want and need to configure shaping to accomodate this queue\nfor best performance:\n\n```lua\n-- Locate this before any other helpers or modules that define\n-- `get_egress_path_config` event handlers in order for it to take effect\nkumo.on(\n 'get_egress_path_config',\n function(routing_domain, egress_source, site_name)\n if routing_domain == 'generator.kumomta.internal' then\n return kumo.make_egress_path {\n -- This is a good place to start, but you may want to\n -- experiment with 1/2, 3/4, or 1.5 times this to find\n -- what works best in your environment\n connection_limit = kumo.available_parallelism(),\n refresh_strategy = 'Epoch',\n max_ready = 80000,\n }\n end\n end\n)\n```\n\n!!! note\n It is possible to very quickly generate millions of queued messages when\n using `deferred_generation: true`. You may wish to look into configuring\n a rate limit to constrain the system appropriately for your environment.\n <https://docs.kumomta.com/reference/kumo/set_httpinject_recipient_rate_limit/>\n can be used for this purpose.",
"default": false
},
"deferred_spool": {
"type": "boolean",
"description": "{{since('2024.11.08-d383b033', inline=True)}}\n\n!!! danger\n Enabling deferred spooling may result in loss\n of accountability for messages. You should satisfy\n yourself that your system is able to recognize and\n deal with that scenario if/when it arises.\n\nWhen set to `true`, the generated message(s) will\nnot be written to the spool until it encounters its\nfirst transient failure. This can improve injection\nrate but introduces the risk of loss of\naccountability for the message if the system were to\ncrash before the message is delivered or written to\nspool, so use with caution!\n\nWhen used in conjunction with `deferred_generation`,\nboth the queued generation request and the messages\nwhich it produces are subject to deferred spooling.",
"default": false
},
"envelope_sender": {
"type": "string",
"format": "email",
"description": "Specify the envelope sender that will be sent in the\nMAIL FROM portion of SMTP.",
"example": "some.id@bounces.sender-example.com"
},
"recipients": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Recipient"
},
"description": "Specifies the list of recipients to which message(s) will be sent.\nWhen generating the message for the recipient, a suitable `To` header will be\nconstructed using the provided fields.\n\nIf you also set a `To` header using the `headers` field, then the behavior\ndepends on the version of kumomta:\n\n|Behavior|Version|\n|--------|-------|\n|The per-recipient `To` header will not be generated|{{since('2026.03.04-bb93ecb1', inline=True)}}|\n|Two `To` headers will be generated|All previous versions|"
},
"substitutions": {
"type": "object",
"description": "When using templating, this is the map of placeholder\nname to replacement value that should be used by\nthe templating engine. This map applies to all\nrecipients, with the per-recipient substitutions\ntaking precedence.",
"additionalProperties": true,
"example": {
"campaign_title": "Fall Campaign"
}
},
"template_dialect": {
"oneOf": [
{
"$ref": "#/components/schemas/TemplateDialectWithSchema",
"description": "{{since('2025.12.02-67ee9e96', inline=True)}}\n\nIt is now possible to specify which template engine will be\nused for template expansion via the `template_dialect` field.\nIt can have one of the following values:\n\n * `Jinja` - this is the implied default. The Mini Jinja\n template dialect will be parsed and evaluated.\n * `Static` - The content is treated as a static string and\n no template expansion will be performed\n * `Handlebars` - The content will be evaluated by a handlebars\n compatible template engine."
}
],
"default": "Jinja"
},
"trace_headers": {
"$ref": "#/components/schemas/HttpTraceHeaders",
"description": "{{since('2024.11.08-d383b033', inline=True)}}\n\nControls the addition of tracing headers to received messages.\n\nKumoMTA can add two different headers to aid in later tracing:\n\n* The standard `\"Received\"` header which captures SMTP relay\n hops on their path to the inbox\n\n* A supplemental header which can be used to match feedback\n reports back to the originating mailing\n\nPrior to triggering the\n<https://docs.kumomta.com/reference/events/http_message_generated/>\nevent the standard `\"Received\"` header will be added to the\nmessage. Then, once the event completes and your policy has had the\nopportunity to alter the meta data associated with the message, the\nsupplemental header will be added.\n\n```json\n{\n \"trace_headers\": {\n // this is the default: do NOT add the Received: header\n \"received_header\": false,\n\n // this is the default: add the supplemental header\n \"supplemental_header\": true,\n\n // this is the default: the name of the supplemental header\n \"header_name\": \"X-KumoRef\",\n\n // names of additional meta data fields\n // to include in the header. TAKE CARE! The header will be\n // base64 encoded to prevent casual introspection, but the\n // header is NOT encrypted and the values of the meta data\n // fields included here should be considered to be public.\n // The default is not to add any meta data fields, but you\n // might consider setting something like:\n // \"include_meta_names\": { \"tenant\", \"campaign\" },\n \"include_meta_names\": {},\n },\n}\n```\n\nHere's an example of a supplemental header from a message:\n\n```\nX-KumoRef: eyJfQF8iOiJcXF8vIiwicmVjaXBpZW50IjoidGVzdEBleGFtcGxlLmNvbSJ9\n```\n\nthe decoded payload contains a magic marker key as well as the recipient of the\noriginal message:\n\n```json\n{\"_@_\":\"\\\\_/\",\"recipient\":\"test@example.com\"}\n```\n\nAny meta data fields that were listed in `include_meta_names`, if the corresponding\nmeta data was set in the message, would also be captured in the decoded payload.\n\nKumoMTA will automatically extract this supplemental trace header information\nfrom any `X-` header that is successfully parsed and has the magic marker key\nwhen processing the original message payload of an incoming ARF report."
}
},
"additionalProperties": false
},
"InjectV1Response": {
"type": "object",
"required": [
"success_count",
"fail_count",
"failed_recipients",
"errors"
],
"properties": {
"errors": {
"type": "array",
"items": {
"type": "string"
},
"description": "The list of error messages"
},
"fail_count": {
"type": "integer",
"description": "The number of messages that failed to inject",
"minimum": 0
},
"failed_recipients": {
"type": "array",
"items": {
"type": "string",
"format": "email"
},
"description": "The list of failed recipients"
},
"success_count": {
"type": "integer",
"description": "The number of messages that were injected successfully",
"minimum": 0
}
}
},
"InspectMessageV1Response": {
"type": "object",
"required": [
"id",
"message"
],
"properties": {
"id": {
"$ref": "#/components/schemas/SpoolId",
"description": "The spool identifier of the message"
},
"message": {
"$ref": "#/components/schemas/MessageInformation",
"description": "The message information"
}
}
},
"InspectQueueV1Response": {
"type": "object",
"required": [
"queue_name",
"messages",
"num_scheduled",
"queue_config",
"delayed_metric",
"now",
"last_changed"
],
"properties": {
"delayed_metric": {
"type": "integer",
"minimum": 0
},
"last_changed": {
"type": "string",
"format": "date-time"
},
"messages": {
"type": "array",
"items": {
"$ref": "#/components/schemas/InspectMessageV1Response"
}
},
"now": {
"type": "string",
"format": "date-time"
},
"num_scheduled": {
"type": "integer",
"minimum": 0
},
"queue_config": {
"type": "object"
},
"queue_name": {
"type": "string",
"example": "campaign_name:tenant_name@example.com"
}
}
},
"InspectReadyQV1Response": {
"type": "object",
"description": "Response body for the inspect-ready-q endpoint.\n\n{{since('dev')}}",
"required": [
"queue_name",
"egress_source",
"egress_pool",
"protocol",
"state",
"path_config",
"constraints",
"dispatchers",
"now"
],
"properties": {
"constraints": {
"$ref": "#/components/schemas/EffectiveConstraints",
"description": "Steady-state throughput ceilings implied by `path_config`.\nEach axis is tagged with the configuration term that\nproduced it, so operators can tell whether a plateau in the\nobserved rate is the result of shaping configuration or the\nremote side."
},
"dispatchers": {
"type": "array",
"items": {
"$ref": "#/components/schemas/DispatcherSummary"
}
},
"egress_pool": {
"type": "string"
},
"egress_source": {
"type": "string"
},
"mx": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/components/schemas/MxResolution",
"description": "MX resolution result for the destination. `None` for\nprotocols that don't use MX (e.g. Lua-protocol queues) or\nwhen MX resolution wasn't applicable."
}
]
},
"now": {
"type": "string",
"format": "date-time"
},
"path_config": {
"type": "object",
"description": "Snapshot of the egress path configuration in effect for this\nqueue."
},
"protocol": {
"type": "string",
"description": "Protocol identifier as it appears in the ready queue name.",
"example": "smtp_client"
},
"queue_name": {
"type": "string"
},
"scheduled_queue_names": {
"type": [
"array",
"null"
],
"items": {
"type": "string"
},
"description": "Scheduled queue names that currently feed this ready queue.\nPopulated only when the request's `include_scheduled_queues`\nflag is true; otherwise None."
},
"state": {
"$ref": "#/components/schemas/ReadyQueueStateSnapshot"
}
}
},
"MachineInfoV1": {
"type": "object",
"required": [
"node_id",
"hostname",
"mac_address",
"num_cores",
"platform",
"distribution",
"os_version",
"total_memory_bytes",
"cpu_brand",
"fingerprint",
"online_since",
"process_kind",
"version"
],
"properties": {
"container_runtime": {
"type": [
"string",
"null"
],
"description": "If we detected that we're running in a container, the name\nof the container runtime"
},
"cpu_brand": {
"type": "string",
"description": "Identifies the CPU. If you have a mixture of different CPUs,\nthis will be a comma separated list of the different CPUs",
"example": "Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz"
},
"distribution": {
"type": "string",
"description": "The OS distribution",
"example": "ubuntu"
},
"fingerprint": {
"type": "string",
"description": "Additional metadata hash(es) that can identify the running machine.\nFor example, when running in AWS, the instance-id will be\nincluded.",
"example": "aws_instance_id=i-09aebefac97cf0000,machine_uid=ec22130d1de33cf52413457ac040000"
},
"hostname": {
"type": "string",
"description": "The hostname of the system, as reported by `gethostname(2)`",
"example": "mta1.example.com"
},
"kernel_version": {
"type": [
"string",
"null"
],
"description": "The kernel version",
"example": "6.8.0-1016-aws"
},
"mac_address": {
"type": "string",
"description": "The MAC address of the primary, non-loopback, network interface",
"example": "02:02:02:02:02:02"
},
"node_id": {
"type": "string",
"description": "The NodeID of the system",
"example": "9745bb48-14d7-48f2-a1fb-7df8d5844217"
},
"num_cores": {
"type": "integer",
"description": "The number of available CPUs as reported by\n<https://docs.rs/num_cpus/latest/num_cpus/fn.get.html>",
"example": 64,
"minimum": 0
},
"online_since": {
"type": "string",
"format": "date-time",
"description": "The date/time at which the process was last started"
},
"os_version": {
"type": "string",
"description": "The OS version (which often includes the distribution)",
"example": "Linux (Ubuntu 24.04)"
},
"platform": {
"type": "string",
"description": "Identifies the running platform",
"example": "linux/x86_64"
},
"process_kind": {
"type": "string",
"description": "Which process is running. eg: `kumod` vs `tsa-daemon` vs. `proxy-server`.",
"example": "kumod"
},
"total_memory_bytes": {
"type": "integer",
"format": "u-int64",
"description": "Total physical memory installed in the instance",
"example": 1003929600,
"minimum": 0
},
"version": {
"type": "string",
"description": "The version of KumoMTA that is running",
"example": "2026.02.24-2d1a3174"
}
}
},
"MessageInformation": {
"type": "object",
"required": [
"sender",
"recipient",
"meta"
],
"properties": {
"data": {
"type": [
"string",
"null"
],
"description": "If `want_body` was set in the original request,\nholds the message body",
"example": "From: user@example.com\nSubject: Hello\n\nHello there"
},
"due": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"meta": {
"type": "object",
"description": "The message metadata"
},
"num_attempts": {
"type": [
"integer",
"null"
],
"format": "u-int16",
"minimum": 0
},
"recipient": {
"type": "array",
"items": {
"type": "string",
"format": "email"
},
"description": "The envelope-to address.\nMay be either an individual string or an array of strings\nfor multi-recipient messages.",
"example": "recipient@example.com"
},
"scheduling": {
"type": "object"
},
"sender": {
"type": "string",
"description": "The envelope sender",
"example": "sender@sender.example.com"
}
}
},
"MxResolution": {
"type": "object",
"description": "Summary of an MX resolution attempt for a destination.\n\n{{since('dev')}}",
"required": [
"site_name",
"by_preference",
"is_mx",
"is_domain_literal",
"is_secure"
],
"properties": {
"by_preference": {
"type": "object",
"description": "MX hostnames grouped by preference. Lower preference is\npreferred. Empty when DNS returned no MX records and the\nresolver synthesised A/AAAA against `domain` directly.",
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
},
"propertyNames": {
"type": "integer",
"format": "u-int16",
"minimum": 0
}
},
"is_domain_literal": {
"type": "boolean",
"description": "True if the destination was a domain literal like `[1.2.3.4]`."
},
"is_mx": {
"type": "boolean",
"description": "True if real MX records were found in DNS. False when the\nresolver synthesised an A/AAAA lookup against the domain."
},
"is_secure": {
"type": "boolean",
"description": "True if the DNS result was DNSSEC validated."
},
"site_name": {
"type": "string",
"description": "Canonical site name; the value used as the queue grouping\nkey for this destination."
}
}
},
"QueueState": {
"type": "object",
"required": [
"context",
"since"
],
"properties": {
"context": {
"type": "string",
"example": "TooManyLeases for queue"
},
"since": {
"type": "string",
"format": "date-time"
}
}
},
"ReadyQueueStateResponse": {
"type": "object",
"required": [
"states_by_ready_queue"
],
"properties": {
"states_by_ready_queue": {
"type": "object",
"additionalProperties": {
"type": "object",
"additionalProperties": {
"$ref": "#/components/schemas/QueueState"
},
"propertyNames": {
"type": "string"
}
},
"propertyNames": {
"type": "string"
}
}
}
},
"ReadyQueueStateSnapshot": {
"type": "object",
"description": "Snapshot of the operational state of a ready queue.\n\n{{since('dev')}}",
"required": [
"ready_count",
"connection_count",
"watchdog_threshold"
],
"properties": {
"connection_count": {
"type": "integer",
"minimum": 0
},
"connection_limited": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/components/schemas/QueueState"
}
]
},
"connection_rate_throttled": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/components/schemas/QueueState"
}
]
},
"ready_count": {
"type": "integer",
"minimum": 0
},
"suspended": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/components/schemas/SuspendReadyQueueV1ListEntry"
}
]
},
"watchdog_threshold": {
"type": "string",
"description": "Effective progress watchdog timeout for this queue, honoring\nthe per-egress-path config or the protocol-derived default."
}
}
},
"RebindV1Request": {
"type": "object",
"description": "Describes which messages should be rebound.\nThe criteria apply to the scheduled queue associated\nwith a given message.",
"required": [
"reason",
"data"
],
"properties": {
"always_flush": {
"type": "boolean",
"description": "If true, make all matched messages immediately eligible for\ndelivery. When false, (the default), only messages whose\nqueue has changed will be made immediately eligible."
},
"campaign": {
"type": [
"string",
"null"
],
"description": "The campaign name to match. If omitted, any campaign will match."
},
"data": {
"type": "object",
"description": "The data, a json object with string keys AND values to pass to the\nrebind operation",
"additionalProperties": {
"type": "string"
},
"propertyNames": {
"type": "string"
}
},
"domain": {
"type": [
"string",
"null"
],
"description": "The domain name to match. If omitted, any domain will match.",
"example": "example.com"
},
"reason": {
"type": "string",
"description": "Reason to log in the delivery log. Each matching message will log\nwith an AdminRebind record unless you suppress logging.",
"example": "Cleaning up a bad send"
},
"routing_domain": {
"type": [
"string",
"null"
],
"description": "The routing_domain name to match. If omitted, any routing_domain will match."
},
"suppress_logging": {
"type": "boolean",
"description": "If true, do not generate AdminRebind delivery logs for matching\nmessages."
},
"tenant": {
"type": [
"string",
"null"
],
"description": "The tenant to match. If omitted, any tenant will match."
},
"trigger_rebind_event": {
"type": "boolean",
"description": "If true, a `rebind` event will be triggered and passed each\nmessage and the supplied data.\nIf false, no event will be triggered and each field in data\nwill be applied to the msg metadata, overwriting any previous\nvalue for that key."
}
}
},
"RebindV1Response": {
"type": "object"
},
"Recipient": {
"type": "object",
"required": [
"email"
],
"properties": {
"email": {
"type": "string",
"format": "email",
"description": "The email address of the recipient",
"example": "john.smith@mailbox-example.com"
},
"metadata": {
"type": "object",
"description": "Per-recipient metadata key-value pairs. When non-empty, these are\nstored on the resulting message under the `extra` metadata key,\naccessible from Lua hooks via `msg:get_meta('extra')`.",
"additionalProperties": true,
"example": {
"campaign_id": "promo-2026-q2",
"user_segment": "premium"
}
},
"name": {
"type": [
"string",
"null"
],
"description": "The optional displayable name of the recipient.\nThis field will be set as the `name` field when\nprocessing template expansion.",
"example": "John Smith"
},
"substitutions": {
"type": "object",
"description": "When using templating, this is the map of placeholder\nname to replacement value that should be used by the\ntemplating engine when processing just this recipient.\n\nThe following substitutions are pre-defined:\n\n|Name|Value|Version|\n|----|-----|-------|\n|email|Populated from the recipient `email` field|Always|\n|name|Populated from the recipient `name` field|Always|\n|to_header|A suitable value for use in a `To:` header, constructed from the recipient `email` and `name` fields. eg: `\"John Smith\" <john.smith@mailbox-example.com>`|{{since('2026.04.09-ea3b2a9b', inline=True)}}|",
"additionalProperties": true,
"example": {
"age": 42,
"gender": "male"
}
}
},
"additionalProperties": false
},
"ResolveEgressPathV1Response": {
"type": "object",
"description": "Response body for the resolve-egress-path endpoint.\n\n{{since('dev')}}",
"required": [
"domain",
"source",
"queue_name",
"queue_config",
"path_config",
"constraints"
],
"properties": {
"constraints": {
"$ref": "#/components/schemas/EffectiveConstraints"
},
"domain": {
"type": "string"
},
"mx": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/components/schemas/MxResolution",
"description": "MX resolution result. `None` when MX lookup wasn't applicable\n(e.g. non-SMTP protocols) or failed (e.g. internal sentinel\ndomains, network errors)."
}
]
},
"path_config": {
"type": "object"
},
"queue_config": {
"type": "object",
"description": "Snapshot of the resolved scheduled-queue configuration for\nthis domain. Carried as an untyped JSON object because\n`QueueConfig` contains protocol variants that don't all\nround-trip cleanly through the OpenAPI schema."
},
"queue_name": {
"type": "string",
"description": "The ready-queue name that this domain/source pair would\nresolve to. Lets the caller pivot to inspect-ready-q for\nlive runtime detail when the queue exists."
},
"source": {
"type": "string"
}
}
},
"SetDiagnosticFilterRequest": {
"type": "object",
"required": [
"filter"
],
"properties": {
"filter": {
"type": "string",
"description": "The diagnostic filter spec to use",
"example": "kumod=trace"
}
}
},
"SpoolCompactV1Request": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "Name of the spool to compact, matching a `kumo.define_spool` name."
}
}
},
"SpoolId": {
"type": "string",
"description": "Identifies a message within the spool of its host node.",
"example": "d7ef132b5d7711eea8c8000c29c33806"
},
"SuspendReadyQueueV1ListEntry": {
"type": "object",
"required": [
"id",
"name",
"reason",
"duration",
"expires"
],
"properties": {
"duration": {
"type": "string",
"description": "how long until this suspension expires and is automatically removed"
},
"expires": {
"type": "string",
"format": "date-time",
"description": "The time at which the suspension will expire"
},
"id": {
"type": "string",
"format": "uuid",
"description": "The id for the suspension. Can be used to cancel the suspension."
},
"name": {
"type": "string",
"description": "The name of the ready queue that is suspended",
"example": "source_name->(alt1|alt2|alt3|alt4)?.gmail-smtp-in.l.google.com@smtp_client"
},
"reason": {
"type": "string",
"description": "The reason for the suspension",
"example": "pause while working on resolving a block with the destination postmaster"
}
}
},
"SuspendReadyQueueV1Request": {
"type": "object",
"required": [
"name",
"reason"
],
"properties": {
"duration": {
"type": [
"string",
"null"
],
"description": "Specifies how long this suspension remains active."
},
"expires": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"name": {
"type": "string",
"description": "The name of the ready queue that should be suspended",
"example": "source_name->(alt1|alt2|alt3|alt4)?.gmail-smtp-in.l.google.com@smtp_client"
},
"reason": {
"type": "string",
"description": "The reason for the suspension",
"example": "pause while working on resolving a block with the destination postmaster"
}
}
},
"SuspendV1CancelRequest": {
"type": "object",
"required": [
"id"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The id of the suspension to cancel"
}
}
},
"SuspendV1ListEntry": {
"type": "object",
"required": [
"id",
"reason",
"duration"
],
"properties": {
"campaign": {
"type": [
"string",
"null"
],
"description": "The campaign name to match. If omitted, any campaign will match.",
"example": "campaign_name"
},
"domain": {
"type": [
"string",
"null"
],
"description": "The domain name to match. If omitted, any domain will match.",
"example": "example.com"
},
"duration": {
"type": "string",
"description": "Specifies how long this suspension remains active."
},
"id": {
"type": "string",
"format": "uuid",
"description": "The id of the suspension. This can be used later to cancel\nthe suspension."
},
"reason": {
"type": "string",
"description": "The reason for the suspension",
"example": "pause while working on resolving a deliverability issue"
},
"tenant": {
"type": [
"string",
"null"
],
"description": "The tenant name to match. If omitted, any tenant will match.",
"example": "tenant_name"
}
}
},
"SuspendV1Request": {
"type": "object",
"required": [
"reason"
],
"properties": {
"campaign": {
"type": [
"string",
"null"
],
"description": "The campaign name to match. If omitted, any campaign will match.",
"example": "campaign_name"
},
"domain": {
"type": [
"string",
"null"
],
"description": "The domain name to match. If omitted, any domain will match.",
"example": "example.com"
},
"duration": {
"type": [
"string",
"null"
],
"description": "Specifies how long this suspension remains active."
},
"expires": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "instead of specifying the duration, you can set an explicit\nexpiration timestamp"
},
"queue_names": {
"type": "array",
"items": {
"type": "string"
},
"description": "If present, queue_names takes precedence over `campaign`,\n`tenant`, and `domain` and specifies the exact set of\nscheduled queue names to which the suspension applies.",
"example": [
"campaign_name:tenant_name@example.com"
]
},
"reason": {
"type": "string",
"description": "The reason for the suspension",
"example": "pause while working on resolving a block with the destination postmaster"
},
"tenant": {
"type": [
"string",
"null"
],
"description": "The tenant name to match. If omitted, any tenant will match.",
"example": "tenant_name"
}
}
},
"SuspendV1Response": {
"type": "object",
"required": [
"id"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "The id of the suspension. This can be used later to cancel\nthe suspension."
}
}
},
"TemplateDialectWithSchema": {
"type": "string",
"enum": [
"Jinja",
"Static",
"Handlebars"
]
},
"TraceHeaders": {
"type": "object",
"properties": {
"header_name": {
"type": "string",
"description": "The name of the supplemental trace header"
},
"include_meta_names": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of meta keys that should be included in the\nsupplemental header"
},
"received_header": {
"type": "boolean",
"description": "Whether to add a Received: header"
},
"supplemental_header": {
"type": "boolean",
"description": "Whether to add a supplemental trace header to encode\nadditional metadata"
}
},
"additionalProperties": false
},
"XferCancelV1Request": {
"type": "object",
"required": [
"queue_name",
"reason"
],
"properties": {
"queue_name": {
"type": "string",
"description": "The name of the xfer scheduled queue"
},
"reason": {
"type": "string",
"description": "Reason to log in the delivery log. Each matching message will log\nwith an AdminRebind record to indicate that it was moved from\nits containing queue, and this reason will be included in that record.",
"example": "Scaling down"
}
}
},
"XferCancelV1Response": {
"type": "object"
},
"XferProtocol": {
"type": "object",
"required": [
"target"
],
"properties": {
"target": {
"type": "string",
"format": "uri",
"description": "Expected to be an HTTP url prefix like:\n`https://host.name:8008`\n`http://127.0.0.1:8000`",
"examples": [
"http://127.0.0.1:8000",
"https://host.name:8008"
]
}
}
},
"XferResponseV1": {
"type": "object",
"required": [
"id"
],
"properties": {
"id": {
"$ref": "#/components/schemas/SpoolId"
}
}
},
"XferV1Request": {
"allOf": [
{
"$ref": "#/components/schemas/XferProtocol"
},
{
"type": "object",
"required": [
"reason"
],
"properties": {
"campaign": {
"type": [
"string",
"null"
],
"description": "The campaign name to match. If omitted, any campaign will match."
},
"domain": {
"type": [
"string",
"null"
],
"description": "The domain name to match. If omitted, any domain will match.",
"example": "example.com"
},
"queue_names": {
"type": "array",
"items": {
"type": "string"
},
"description": "If present, queue_names takes precedence over `campaign`,\n`tenant`, and `domain` and specifies the exact set of\nscheduled queue names to which the xfer applies."
},
"reason": {
"type": "string",
"description": "Reason to log in the delivery log. Each matching message will log\nwith an AdminRebind record to indicate that it was moved from\nits containing queue, and this reason will be included in that record.",
"example": "Scaling down"
},
"routing_domain": {
"type": [
"string",
"null"
],
"description": "The routing_domain name to match. If omitted, any routing_domain will match."
},
"tenant": {
"type": [
"string",
"null"
],
"description": "The tenant to match. If omitted, any tenant will match."
}
}
}
],
"description": "Describes which messages should be transferred to another\nkumomta node.\nThe criteria apply to the scheduled queue associated\nwith a given message."
},
"XferV1Response": {
"type": "object"
}
},
"securitySchemes": {
"basic_auth": {
"type": "http",
"scheme": "basic"
}
}
},
"security": [
{
"basic_auth": [
""
]
}
]
}