# Detect out-of-office and auto-replies

Source: https://developer.nylas.com/docs/cookbook/email/detect-auto-replies/

An outreach sequence or an agent flow that reads inbound mail has one job it must get right: tell a real human reply apart from a machine. When someone is on vacation, their mail server fires back an out-of-office notice within seconds, and a naive flow treats that bounce-back as genuine interest. The fix is to inspect the message headers before you act, because automated responders mark themselves with standard headers that a real reply never carries.

This recipe shows how to fetch a message with its full header set through the Nylas Email API and then classify it as automated or human in your own code.

## Why detect auto-replies before acting on a reply?

An out-of-office notice is a real message in the mailbox, so it shows up in a list query and triggers a `message.created` webhook exactly like a human reply. If you advance a sequence or hand the text to an agent, you act on noise. Studies of cold outreach put auto-reply rates around 8 to 15 percent of all responses during holiday periods. Detecting these messages keeps your pipeline clean and your reply counts honest.

The signal lives in the headers. A vacation responder sets `Auto-Submitted: auto-replied` per RFC 3834, and most servers add a vendor header on top. A human reply carries none of them, so a header check separates the two groups with near-zero false positives.

## How do I fetch a message with its full headers?

Send a `GET /v3/grants/{grant_id}/messages/{message_id}` request with `fields=include_headers`. Nylas returns the message object with a `headers` array of every name-value pair on the message, which is where the auto-reply markers live. Without that query parameter the response omits headers entirely.

The `fields=include_headers` value returns the full set on Google, Microsoft, and EAS. On EWS and IMAP, those 2 providers expose only the headers Nylas generates for MIME, so the vendor auto-reply headers may not survive. For the exact provider behavior, see [using email headers and MIME data](/docs/v3/email/headers-mime-data/). Always pass `fields=include_headers` here rather than `include_basic_headers`, because the basic set carries only the 3 threading headers and none of the auto-reply markers.

```bash
curl --request GET \
  --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages/<MESSAGE_ID>?fields=include_headers' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>'
```

```json {8-21} [autoReplyHeaders-Response (JSON, abbreviated)]
{
  "request_id": "5fa64c92-e840-4357-86b9-2aa364d35b88",
  "data": {
    "grant_id": "<NYLAS_GRANT_ID>",
    "object": "message",
    "id": "<MESSAGE_ID>",
    "subject": "Automatic reply: Re: Project kickoff",
    "headers": [
      {
        "name": "Auto-Submitted",
        "value": "auto-replied"
      },
      {
        "name": "X-Auto-Response-Suppress",
        "value": "All"
      }
    ],
    "from": [{ "name": "Leyah Miller", "email": "leyah@example.com" }],
    "snippet": "I am out of the office until Monday and will reply on my return."
  }
}
```

## Which headers mark an auto-reply?

The most reliable marker is `Auto-Submitted`, defined in RFC 3834. Any value other than `no` means the message was generated by an automated process, and vacation responders set it to `auto-replied`. Treat the header name as case-insensitive and the value as a prefix, since some servers append parameters after a semicolon.

RFC 3834 was published in 2007 and is the one cross-vendor standard, but real mailboxes carry vendor headers too. Match these names verbatim: `Auto-Submitted`, `X-Autoreply`, `X-Autorespond`, and Microsoft's `X-Auto-Response-Suppress`. The first 4 cover the bulk of providers, and `X-Auto-Response-Suppress` flags messages from Exchange and Outlook rules. If any of these is present, classify the message as automated.

| Header | Auto-reply value | Source |
| --- | --- | --- |
| `Auto-Submitted` | `auto-replied` (any value except `no`) | [RFC 3834](https://www.rfc-editor.org/info/rfc3834/) |
| `X-Autoreply` | `yes` | Common vendor header |
| `X-Autorespond` | present (any value) | Common vendor header |
| `X-Auto-Response-Suppress` | `All`, `OOF`, or similar | Microsoft Exchange and Outlook |

## How do I classify a message in code?

Walk the `headers` array and check each name against the marker list, then fall back to a subject heuristic when no header matches. Messages may repeat a header name, and Nylas does not de-duplicate the list, so iterate the full array rather than building a single lookup map. The function below returns true for any automated message and runs in well under 1 ms per message.

A few servers, especially older IMAP setups, send a vacation notice without any auto-reply header. For those, a subject check on the localized "out of office" and "automatic reply" phrases catches most of the gap. Keep the header check primary and the subject check secondary, because a subject match alone can misfire on a human who literally writes about being out of office.

```javascript [classify-Node.js]
const AUTO_HEADERS = new Set([
  "auto-submitted",
  "x-autoreply",
  "x-autorespond",
  "x-auto-response-suppress",
]);

const SUBJECT_HINTS = [/out of office/i, /automatic reply/i, /auto[- ]?reply/i];

function isAutomatedReply(message) {
  const headerHit = (message.headers ?? []).some((header) => {
    const name = header.name.toLowerCase();
    if (name === "auto-submitted") {
      return header.value.trim().toLowerCase() !== "no";
    }
    return AUTO_HEADERS.has(name);
  });
  if (headerHit) return true;
  return SUBJECT_HINTS.some((pattern) => pattern.test(message.subject ?? ""));
}
```

## What should I do when a reply is automated?

When `isAutomatedReply` returns true, suppress the action you would take on a human reply. In an outreach tool that means holding the sequence in place rather than marking the contact as engaged, since an out-of-office notice carries zero intent. In an agent flow it means skipping the message instead of generating an answer to a robot.

Wire the check into your inbound handler so it runs on every message before any downstream logic. The cleanest place is the [`message.created` webhook flow](/docs/cookbook/use-cases/build/new-email-webhook/), where you already fetch each new message by ID. Add `fields=include_headers` to that fetch and gate your business logic on the result. One header check on every inbound message adds 1 field to a call you already make, which is a small price for keeping automated noise out of a flow that should only react to people.

## What's next

- [Using email headers and MIME data](/docs/v3/email/headers-mime-data/) for the full `fields` parameter behavior across providers
- [Read a single message or thread](/docs/cookbook/email/get-message-thread/) to fetch the message body alongside its headers
- [Build a new email webhook](/docs/cookbook/use-cases/build/new-email-webhook/) to run this check on every inbound message in real time