API Reference
API Reference
GitHub
Admin Panel

Platform Settings

Configure platform-wide settings

Administrators configure platform-wide settings from the dashboard under Admin -> Platform Settings, or through GET/PUT /api/v1/admin/settings.

These are the knobs that can change at runtime. Everything in mailyard.yaml (listeners, database, TLS, worker sizing) needs a restart and is not settable here.

Get Settings

GET /api/v1/admin/settings

Returns every key in the registry with its definition, its effective value, and whether that value is an administrator override or the built-in default:

{
    "settings": [
        {
            "key": "retention_days",
            "type": "int",
            "unit": "days",
            "default": "0",
            "value": "30",
            "overridden": true,
            "description": "Days to keep email log rows. 0 keeps them forever.",
            "updated_at": "2026-08-06T02:11:00Z",
            "updated_by": "admin@example.com"
        }
    ]
}

Update Settings

PUT /api/v1/admin/settings
{
    "settings": [
        {
            "key": "retention_days",
            "value": "60"
        },
        {
            "key": "maintenance_mode",
            "value": "true"
        }
    ]
}

Only the listed keys change. The whole batch is validated before anything is written, so a bad value in one entry rejects the request rather than half-applying it. Writing a value equal to the default removes the override instead of storing a redundant copy.

An unknown key is rejected. Settings exist only when something reads them, so the registry is the full list of what can be set.

Both routes require the platform admin role.

Available Settings

KeyTypeDefaultDescription
retention_daysint30Days to keep email log rows. 0 keeps them forever. The emails table is partitioned by week, so whole partitions past this window are dropped rather than deleted row by row.
email_body_retention_daysint0Days to keep rendered HTML and text on an email row. 0 follows retention_days.
email_attachment_retention_daysint0Days to keep attachment bytes, including the blobs in object storage. 0 follows retention_days.
inbound_retention_daysint0Days to keep received mail. 0 follows retention_days.
sandbox_retention_daysint7Days to keep a captured sandbox message. 0 keeps it until the per-project cap pushes it out. A sender may ask for a shorter window per message, never a longer one.
sandbox_max_messagesint500How many sandbox messages one project keeps. The oldest are dropped past this. 0 is unlimited, which on a project wired into CI means the table grows until the disk says otherwise.
webhook_delivery_retention_daysint30Days to keep webhook delivery history. 0 keeps it forever.
audit_log_retention_daysint90Days to keep audit log entries. 0 keeps them forever.
tracking_event_retention_daysint0Days to keep open and click events. 0 keeps them forever. Per-message and per-link counters are aggregated and always kept.
maintenance_modeboolfalseRefuse write requests from everyone but platform admins.
tls_certificate_serverstring-Name of the managed certificate the HTTP listener serves. Empty falls through to ACME, then to the self-signed pair.
tls_certificate_submissionstring-Same, for the SMTP submission listener.
tls_certificate_inboundstring-Same, for the inbound MX listener.
acme_enabledboolfalseOrder certificates from Let’s Encrypt .
acme_hostslist-Hostnames to issue for. One per line in the console, a JSON array of strings over the API. A name that is not listed falls through to the self-signed pair rather than failing the handshake.
acme_emailstring-ACME account contact, where the CA sends expiry warnings. Used when the account is first registered.
acme_directory_urlstring-A different ACME directory. Empty is Let’s Encrypt production - point it at the staging one while working out why issuance fails.
platform_mail_fromstring-Address the platform’s own mail comes from - invitations, password resets, signup confirmations. Empty turns platform mail off.
platform_mail_from_namestring-Display name beside that address.
notification_retention_daysint30Days to keep notifications that have been read. Unread ones are always kept.
bounce_alert_percentint10Bounce rate over the last hour that raises a project alert. 0 turns the alert off.
bounce_alert_min_volumeint20How many sends must finish in the hour before the bounce rate is judged.
relay_nodes_auto_approveboolfalseLet a relay node start delivering as soon as it enrols, without an admin approving it.
user_project_creationboolfalseLet any signed-in account create a project. Off, only platform administrators can, and everybody else joins by invitation . Administrators are never subject to it.

Leave auto-approve off unless nodes are created automatically

A relay node in the sending pool receives the content of real messages to deliver, and every node enrols with the same relay_nodes.auto_register_token. With approval in the way, a leaked token gets somebody a pending row an admin will not recognise. Without it, it gets them a copy of everybody’s mail. Turn it on only where nodes come and go on their own, such as an autoscaling group.

Nothing expires by default

Every retention window except the webhook delivery log starts at 0, which means keep forever. An install that never visits this page never loses data - and never reclaims space either. Set the windows deliberately.

Retention behavior

  • Content windows are clamped to retention_days. Keeping a body longer than the row that owns it is meaningless, so a body window longer than the metadata window is silently reduced to it.
  • Attachment blobs are deleted from object storage before the database rows that hold their keys. A blob that will not delete is logged and skipped rather than blocking the sweep - the result is an orphaned object, not a stuck job.
  • Emails still in flight (queued, scheduled, processing) are never purged, however old. Deleting one would strand work the delivery queue is about to claim.

The sweep runs as the retention-cleanup scheduled job .

Maintenance Mode

With maintenance_mode on, every mutating request (POST, PUT, PATCH, DELETE) on both the console API and the machine API returns:

503 Service Unavailable
{ "error": "the platform is in maintenance mode, writes are temporarily disabled" }

Reads stay open so the console remains usable and an incident stays diagnosable, and platform admins are exempt entirely - somebody has to be able to switch it back off.

The delivery worker and campaign runner keep draining whatever is already queued. Maintenance mode stops new work arriving, it does not pause the pipeline.