--- title: "GET /mail/v2/messages/{message_id}" slug: "transactional-email-get-mail-v2-messages-message-id" description: "Explains the GET /mail/v2/messages/{messageId} endpoint for retrieving transactional email details, delivery status, metadata, and message information." updated: 2026-10-09T10:06:25Z published: 2026-10-09T10:06:25Z canonical: "academy.insiderone.com/transactional-email-get-mail-v2-messages-message-id" --- > ## Documentation Index > Fetch the complete documentation index at: https://academy.insiderone.com/llms.txt > Use this file to discover all available pages before exploring further. # GET /mail/v2/messages/{message_id} A 202 from the send endpoint confirms queueing only. Delivery then runs asynchronously. Use this endpoint to check the state of a message, including per-recipient progress for multi-recipient sends. ## Endpoint and headers > **GET** https://email.useinsider.com/mail/v2/messages/{message_id}[](https://developers.insiderone.com/#95301f68-14d8-4c64-bc24-733dd572ff5c) | Header | Value | Purpose | | --- | --- | --- | | X-INS-AUTH-KEY | API token, in INS.{id}.{secret} format | Authentication. The partner is implicit; a message_id belonging to another partner returns 404. | ## Request parameter Below are the parameters required for the request. | Parameter | Description | Data type | Required | | --- | --- | --- | --- | | message_id | The ID returned by the send endpoint. | String (UUID) | Yes | ## Code samples These samples are provided as is, as a starting point for your integration. Insider One does not guarantee that they work in every environment, so test them in your own environment before you use them in production. Each sample polls one message every 5 seconds and prints the aggregate status and summary until **completed_at** is set. 1. Set your API key in the **INSIDER_API_KEY** environment variable. 2. Copy the sample for your language into a file named **get_message_status.py** or **get_message_status.mjs**. 3. Run the file with a **message_id**. Each poll prints a line such as partial: {pending: 1, sent: 2, dropped: 0}, and the sample stops when the message is complete. ### Python Requires Python 3.8 or later and the requests package (pip install requests). Run **python get_message_status.py** **550e8400-e29b-41d4-a716-446655440000**. ```python """Poll a message's delivery status with GET /mail/v2/messages/{message_id}. Sample code, provided as is. Test it in your own environment before you use it in production. Requires Python 3.8+ and the requests package: pip install requests """ import os import sys import time import requests URL = "https://email.useinsider.com/mail/v2/messages/{message_id}" # OAuth 2.0: call https://gw.useinsider.com/api/em/v2/tx/messages/{message_id} with "Authorization: Bearer {token}" instead. HEADERS = {"X-INS-AUTH-KEY": os.environ["INSIDER_API_KEY"], "Content-Type": "application/json"} def call(method, url, payload=None, retries=3):    """Send the request. On 429 or 5xx, wait and retry up to `retries` times."""    for attempt in range(retries + 1):        response = requests.request(method, url, json=payload, headers=HEADERS, timeout=30)        if (response.status_code != 429 and response.status_code < 500) or attempt == retries:            return response        wait = float(response.headers.get("Retry-After", 2 ** attempt))  # 1, 2, 4 s without Retry-After        print(f"HTTP {response.status_code}: retrying in {wait:.0f}s")        time.sleep(wait) message_id = sys.argv[1]  # The message_id returned by POST /mail/v2/send while True:    response = call("GET", URL.format(message_id=message_id))    if response.status_code == 404:        raise SystemExit("Unknown message_id, or the send is older than 2 days.")    if response.status_code != 200:        raise SystemExit(f"Status request failed ({response.status_code}): {response.text[:300]}")    status = response.json()    print(f"{status['status']}: {status['summary']}")    if status["completed_at"] is not None:  # Every recipient has reached a final state        break    time.sleep(5) ``` ### Node.js Requires Node.js 18 or later. No packages are needed. Run **node get_message_status.mjs** **550e8400-e29b-41d4-a716-446655440000**. ```node-repl // Poll a message's delivery status with GET /mail/v2/messages/{message_id}. // Sample code, provided as is. Test it in your own environment before you use it in production. // Requires Node.js 18+ (built-in fetch). No dependencies. const URL = "https://email.useinsider.com/mail/v2/messages/{message_id}"; // OAuth 2.0: call https://gw.useinsider.com/api/em/v2/tx/messages/{message_id} with "Authorization: Bearer {token}" instead. const HEADERS = { "X-INS-AUTH-KEY": process.env.INSIDER_API_KEY, "Content-Type": "application/json" }; const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); // Send the request. On 429 or 5xx, wait and retry up to `retries` times. async function call(method, url, payload, retries = 3) {  for (let attempt = 0; ; attempt++) {    const response = await fetch(url, {      method,      headers: HEADERS,      body: payload === undefined ? undefined : JSON.stringify(payload),      signal: AbortSignal.timeout(30000),    });    if ((response.status !== 429 && response.status < 500) || attempt === retries) return response;    const wait = Number(response.headers.get("Retry-After") ?? 2 ** attempt); // 1, 2, 4 s without Retry-After    console.warn(`HTTP ${response.status}: retrying in ${wait}s`);    await sleep(wait * 1000);  } } const messageId = process.argv[2]; // The message_id returned by POST /mail/v2/send for (;;) {  const response = await call("GET", URL.replace("{message_id}", messageId));  if (response.status === 404) throw new Error("Unknown message_id, or the send is older than 2 days.");  if (response.status !== 200) {    throw new Error(`Status request failed (${response.status}): ${(await response.text()).slice(0, 300)}`);  }  const status = await response.json();  console.log(`${status.status}:`, status.summary);  if (status.completed_at !== null) break; // Every recipient has reached a final state  await sleep(5000); } ``` ## Sample responses The aggregate status is pending (all recipients pending), partial (some sent), or sent (all sent). Per-recipient status is pending or sent; the email is omitted when the recipient was sent by UUID only. ### Mid-delivery, some delivered ```json {  "message_id": "550e8400-e29b-41d4-a716-446655440000",  "status": "partial",  "submitted_at": 1700000000,  "completed_at": null,  "recipient_count": 3,  "summary": {    "pending": 1,    "sent": 2,    "dropped": 0  },  "recipients": [    {      "email": "alice@example.com",      "status": "sent",      "last_updated": 1700000100    },    {      "email": "bob@example.com",      "status": "sent",      "last_updated": 1700000105    },    {      "status": "pending",      "last_updated": 1700000000    }  ] } ``` ### Finished, all delivered ```json {  "message_id": "550e8400-e29b-41d4-a716-446655440000",  "status": "sent",  "submitted_at": 1700000000,  "completed_at": 1700000160,  "recipient_count": 3,  "summary": {    "pending": 0,    "sent": 3,    "dropped": 0  } } ``` ## Keep in mind - Poll periodically until the status is **sent** or **completed_at** is set. Timestamps are Unix seconds; completed_at stays **null** until every recipient reaches a terminal state. - Status is available **for 2 days** after submission; older IDs return **404**. - The rate limit is **1000 requests** per minute per partner. For status codes, refer to the [Error Codes](/v1/docs/transactional-email-error-codes).