---
title: "Poll an async email run (Snowflake native app only)."
method: GET
path: "/v1/dashboards/{id}/email-runs/{runId}"
tags: ["dashboards"]
---

# Poll an async email run (Snowflake native app only).

`GET /v1/dashboards/{id}/email-runs/{runId}`

## Request

- Base URL: `http://localhost:8080`
- URL: `http://localhost:8080/v1/dashboards/{id}/email-runs/{runId}`
- Auth: one of:
  - API key in cookie `dvt_session`
  - HTTP bearer

Returns the run recorded by an async `POST /v1/dashboards/{id}/email`
(DVT-5096). Only the user who started the run can read it; any other caller, a
run on a different dashboard, or a non-Snowflake install gets `404`. Requires
`data:query` AND `data:export`. A run that stays `queued` or `rendering` well
past its budget is closed on read as `unconfirmed` (it had started; check
inboxes) or `not_attempted` (it never started; safe to retry).

## Response `200`

The run.

- object
  - `data` EmailRun, required: One async attended "Email now" run (DVT-5096): the 202/200 body of async `POST /v1/dashboards/{id}/email` and the body of `GET /v1/dashboards/{id}/email-runs/{runId}`. `queued` and `rendering` are open; the rest are terminal.
    - `runId` string, uuid, required
    - `dashboardId` string, uuid, required
    - `status` 'queued' | 'rendering' | 'sent' | 'unconfirmed' | 'failed' | 'not_attempted', required: `queued` and `rendering` are open. `sent` = the mail was handed off. `failed` = the send did not complete; some recipients may already have received it (reason `relay_partial`) — check before retrying. `unconfirmed` = may have been sent — check inboxes before retrying. `not_attempted` = nothing was sent; safe to retry with a NEW Idempotency-Key.
    - `outcome` 'sent' | 'failed' | 'unconfirmed' | 'not-sent-timeout' | 'not-sent-failed' | 'not-sent-too-large': The send outcome (the `dashboard.email` audit spelling); absent while open.
    - `panelsFailed` integer, required
    - `panelsOmitted` integer, required
    - `reason` string: Short machine code (a string, not an enum) for why a run did not send cleanly; never customer data. Set by the sweep of a lost worker (`worker_lost_before_start` for a run that never started, `worker_lost` for one that had) or the worker (`window_spent`, `mark_rendering_failed`, `worker_panic`, `no_outcome`, `unknown_outcome`), otherwise one of the send-failure codes (for example `relay_rejected`, `relay_partial`, `send_unconfirmed`, `over_budget`).
    - `createdAt` string, date-time, required
    - `finishedAt` string, date-time
  - `meta` Meta, required: Response metadata.
    - `requestId` string, required: Correlates with the X-Request-Id header.
    - `total` integer: Item count, present on collection responses.
    - `nextCursor` string, nullable: Opaque keyset-pagination cursor, present on paginated responses (e.g. GET /v1/dashboards) when more rows exist beyond this page. Pass it back as the `cursor` query param to fetch the next page; absent or null on the last page.
    - `roleResolveDegraded` boolean: Snowflake mode only (DVT-2260), on GET /v1/dashboards: present and true when the caller's live Snowflake role resolution failed or was unavailable — role-shared dashboards may be missing from this page. Omitted (not merely false) whenever resolution succeeded or the role-share arm does not apply (off snowflake mode, an org admin/owner, or off the ingress listener).

## Other responses

- `401`: Missing, invalid, or expired session.
- `403`: The caller's role does not permit this action.
- `404`: Resource does not exist.

---

[API](https://skmtc.dev/dvt/apis/dvt-rest-api.md) · [All operations](https://skmtc.dev/dvt/apis/dvt-rest-api/llms.txt) · [OpenAPI document](https://skmtc.dev/dvt/apis/dvt-rest-api/revisions/00016f504165?raw)
