# Dokploy alerts without the spam

URL: https://notifly.io/docs/dokploy-alerts

> Point Dokploy's Notifly channel at a digest workflow so a burst of build, backup and server alerts arrives as one digest email, grouped by payload.projectName. Setup, the 11 event types and their…

Dokploy can send its deployment, backup and server alerts through a Notifly channel. Dokploy sends one trigger per event and does not deduplicate, digest or rate limit them, so a busy night of builds turns into one email per build. A [digest step](https://notifly.io/docs/workflows#digest) in the workflow Dokploy triggers collects those events and sends one summary instead.

This page gives you that workflow, the payload each Dokploy event carries, and a burst test.

<Callout type="warn">
  Batching and the immediate restart were checked on 2026-10-30 on a Notifly dev stack. Real email delivery, a live Dokploy channel, and the "failures immediate" variant have not been checked yet. See the verification status at the end of the page.
</Callout>

## Set up the Dokploy channel

In Dokploy, add a Notifly notification channel and fill in:

| Dokploy field | What to enter                                                                                              |
| ------------- | ---------------------------------------------------------------------------------------------------------- |
| API key       | A Notifly secret key for the environment that holds the workflow.                                          |
| Workflow key  | The `workflowId` of the digest workflow (`dokploy-alerts-digest` below).                                   |
| Subscriber ID | The subscriber who should receive the digest. Leave it empty and Dokploy uses the subscriber ID `dokploy`. |
| Base URL      | Leave empty for `https://api.notifly.io`.                                                                  |

For every alert Dokploy sends `POST /v1/events/trigger` with the workflow key as `name`, the subscriber ID as `to`, and the event's fields as `payload` (see [Workflows](https://notifly.io/docs/workflows#triggering-a-workflow)). Create the subscriber first, with an email address, or the email step has nowhere to send. See [Subscribers](https://notifly.io/docs/subscribers).

All 11 event types go to the one workflow key, so the workflow tells them apart by the `payload.event` field.

## The digest workflow

The workflow has three steps:

1. **Dokploy restart (immediate)**, an email step that runs only when `payload.event` equals `dokploy.restart`. A restart is the one alert you want right away.
2. **Digest**, a regular digest with a 5 minute window, grouped by `{{payload.projectName}}`. It runs for every event except `dokploy.restart`.
3. **Dokploy alerts (digest)**, an email step after the digest that lists the batched events. It also runs for every event except `dokploy.restart`.

The first event of a group opens the digest and starts the 5 minute window. Events that arrive inside the window for the same subscriber and the same `projectName` join it instead of sending. When the window closes the steps after the digest run once, with every collected event available as `steps.digest-step.events`. Events without a `projectName` (`docker.cleanup`, `dokploy.backup`, `server.threshold`) have no group value to differ on, so they share one group. Read more in the [digest notifications guide](https://notifly.io/use-cases/digest-notifications).

The conditions use [step conditions](https://notifly.io/docs/step-conditions): a `skip` rule that evaluates to true lets the step run. A skipped digest runs the next step straight away, which is why the digest email carries the same condition.

Create it with one request. `POST /v2/workflows` takes the same shape the dashboard editor saves:

```bash
curl -X POST https://api.notifly.io/v2/workflows \
  -H "Authorization: ApiKey $NOTIFLY_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Dokploy alerts (digest)",
    "workflowId": "dokploy-alerts-digest",
    "__source": "dashboard",
    "steps": [
      {
        "name": "Dokploy restart (immediate)",
        "type": "email",
        "controlValues": {
          "skip": { "==": [{ "var": "payload.event" }, "dokploy.restart"] },
          "subject": "Dokploy restarted",
          "editorType": "html",
          "body": "<p>{{payload.message | escape}}</p><p>{{payload.timestamp}}</p>"
        }
      },
      {
        "name": "Digest step",
        "type": "digest",
        "controlValues": {
          "skip": { "!=": [{ "var": "payload.event" }, "dokploy.restart"] },
          "type": "regular",
          "amount": 5,
          "unit": "minutes",
          "digestKey": "{{payload.projectName}}"
        }
      },
      {
        "name": "Dokploy alerts (digest)",
        "type": "email",
        "controlValues": {
          "skip": { "!=": [{ "var": "payload.event" }, "dokploy.restart"] },
          "subject": "Dokploy: {% if steps.digest-step.eventCount == 1 %}1 alert{% else %}{{steps.digest-step.eventCount}} alerts{% endif %}",
          "editorType": "html",
          "body": "<ul>{% for e in steps.digest-step.events %}<li><b>{{e.payload.event}}</b> {{e.payload.projectName}} {{e.payload.applicationName}}: {{e.payload.message | escape}} ({{e.payload.timestamp}}){% if e.payload.link %} <a href=\"{{e.payload.link}}\">open</a>{% endif %}</li>{% endfor %}</ul>"
        }
      }
    ]
  }'
```

The digest step's ID is generated from its name, which is why it is named `Digest step`: the templates reference it as `steps.digest-step`. After creating the workflow, open it in the dashboard editor and confirm the digest step's ID matches the one the email body uses.

If you run the `curl` again for a workflow ID that already exists, the API returns 201 with a new key such as `dokploy-alerts-digest-<shortId>` instead of replacing the old one, and Dokploy keeps triggering the old workflow. Check the `workflowId` in the response and use it as Dokploy's workflow key, or delete the duplicate.

Change the window by editing `amount` and `unit`. Longer windows mean fewer emails and later ones. How windows work is covered in the [digest notifications guide](https://notifly.io/use-cases/digest-notifications).

<Callout type="info">
  Restart alerts skip the digest entirely, so they are never merged into a summary. The other ten types, including failures such as `build.error`, wait out the window. If you also want failures immediate, change all three rules together: the first step's rule becomes `{"in": [{"var": "payload.event"}, ["dokploy.restart", "build.error"]]}`, and the digest and digest email rules become `{"!": {"in": [{"var": "payload.event"}, ["dokploy.restart", "build.error"]]}}`. This variant is unverified.
</Callout>

## Event types and payload fields

Dokploy builds the payload per alert type. Every payload carries `event`, `status`, `timestamp` (an ISO 8601 string) and `message`. The last column shows the rest.

| Dokploy alert              | `payload.event`              | `status`             | Extra fields                                                                                        |
| -------------------------- | ---------------------------- | -------------------- | --------------------------------------------------------------------------------------------------- |
| Build failed               | `build.error`                | `error`              | `projectName`, `applicationName`, `link`                                                            |
| Build succeeded            | `build.success`              | `success`            | `projectName`, `applicationName`, `link`                                                            |
| Database backup            | `database.backup`            | `success` or `error` | `projectName`, `applicationName`                                                                    |
| Docker cleanup             | `docker.cleanup`             | `success`            | none                                                                                                |
| Dokploy instance backup    | `dokploy.backup`             | `success` or `error` | none                                                                                                |
| Dokploy restart            | `dokploy.restart`            | `success`            | none                                                                                                |
| Domain verification failed | `domain.verification_failed` | `error`              | `projectName`, `applicationName`, `host`, `link`                                                    |
| Scheduled job failed       | `schedule.failure`           | `error`              | `projectName`, `applicationName` (the job name), `link`                                             |
| Server threshold           | `server.threshold`           | `alert`              | `serverName`                                                                                        |
| Snapvisor changes          | `snapvisor.changes`          | `changes-detected`   | `projectName`, `applicationName`, `environmentName`, `branch`, `commit`, `changedSnapshots`, `link` |
| Volume backup              | `volume.backup`              | `success` or `error` | `projectName`, `applicationName`                                                                    |

These fields follow Dokploy's own source, so check them against your Dokploy version if an email renders a blank. Optional fields that an alert does not carry render as empty in the template.

## Test with a burst

Run these against a Development environment, with a test subscriber you own. Replace `<subscriber-id>` with it.

```bash
for i in 1 2 3 4 5 6 7 8 9 10; do
  curl -sS -X POST https://api.notifly.io/v1/events/trigger \
    -H "Authorization: ApiKey $NOTIFLY_SECRET_KEY" \
    -H "Content-Type: application/json" \
    -d "{
      \"name\": \"dokploy-alerts-digest\",
      \"to\": \"<subscriber-id>\",
      \"payload\": {
        \"event\": \"build.error\",
        \"projectName\": \"demo\",
        \"applicationName\": \"web-$i\",
        \"status\": \"error\",
        \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",
        \"message\": \"Build failed for web-$i\"
      }
    }" > /dev/null
done
```

Expected result: after the 5 minute window, one email listing 10 alerts. The activity feed shows the first trigger as the digest and the other nine as merged into it. Send one more trigger with `"event": "dokploy.restart"` and it arrives straight away, without waiting for the window.

## Verification status

Checked on 2026-10-30 on a Notifly dev stack, using the burst test above: 10 `build.error` events became one digest after the 5 minute window, and `dokploy.restart` skipped the digest and ran its own step immediately. The digest email subject and body also rendered with the digest events.

TODO: end-to-end verification is not complete. Not yet checked: real email delivery (the dev stack had no email provider), a live Dokploy channel posting into this workflow, and the "failures immediate" variant above. Until then, treat the workflow as a starting point.
