# Trigger workflows from inbound email

Source: https://developer.nylas.com/docs/cookbook/email/email-to-workflow/

A support ticket lands in a shared inbox. A vendor sends an invoice. A customer replies to confirm an order. In each case you want code to run the moment the message arrives, not the next time someone checks the inbox. Polling for new mail every minute wastes requests and adds latency, and it scales badly once you watch dozens of accounts at once.

This guide builds an inbound email automation with the Nylas Email API: a webhook fires when a message arrives, your handler fetches the full message, extracts what it needs, and triggers an action. The same pattern works across Google, Microsoft, iCloud, Yahoo, and IMAP through one code path.

## How do I create a system that automatically parses email and triggers actions?

You subscribe to the `message.created` webhook trigger, receive a small notification when any connected inbox gets new mail, fetch the full message through `GET /v3/grants/{grant_id}/messages/{message_id}`, then run your parsing and action logic. This event-driven flow replaces polling, so your code reacts within seconds across every provider behind a single grant.

The architecture has three moving parts. First, a webhook destination registered with the API tells it where to send notifications. Second, your HTTPS endpoint receives each `message.created` payload, which carries the `grant_id` and `message_id` but not always the full body. Third, a fetch-and-extract step pulls the complete message and feeds it to your business logic. Notification payloads cap at 1 MB; anything larger gets truncated with a `.truncated` suffix, which is the main reason you fetch the message by ID rather than trusting the webhook to carry everything.

## How can I trigger actions from incoming email with AI?

Route each inbound message through a parsing step before you act: fetch the body, send the `subject` and `body` to a model or rules engine, and branch on the extracted fields. The webhook supplies the trigger and message ID, the fetch supplies the content, and your extraction layer turns unstructured email into structured data your action can use.

The extraction loop sits between the webhook and the action. A model reads the message and returns fields like intent, order number, or sentiment, and your code routes on those values: create a ticket, update a CRM record, or send a reply. The dedicated [extract structured data from email](/docs/cookbook/ai/extract-data-from-email/) guide covers prompt design and JSON output for this step. Keep the AI call idempotent: providers occasionally redeliver a notification, so dedupe on `message_id` before you spend a token or fire a side effect. A short-lived cache keyed by message ID, expiring after roughly 24 hours, prevents double-processing without unbounded growth.

## Register a webhook for the message.created trigger

The `POST /v3/webhooks` endpoint creates a webhook destination. You pass the `trigger_types` you want, a public `webhook_url`, and optional `notification_email_addresses` that get alerted if the destination starts failing. For inbound automation, `message.created` is the trigger that fires on every newly synced message. Keep destinations few: a retry fans out to every destination registered for a trigger type.

```bash
curl --request POST \
  --url 'https://api.us.nylas.com/v3/webhooks' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>' \
  --header 'Content-Type: application/json' \
  --data-raw '{
    "trigger_types": ["message.created"],
    "webhook_url": "https://example.com/webhooks/nylas",
    "description": "Inbound email automation",
    "notification_email_addresses": ["ops@example.com"]
  }'
```

When you create the destination, the API responds with a `webhook_secret`. Store it: every notification arrives with an `x-nylas-signature` header, an HMAC-SHA256 of the raw request body keyed with that secret. Verify it on every request and reject mismatches before parsing. For the full registration and verification walkthrough, see [handle a new email webhook](/docs/cookbook/use-cases/build/new-email-webhook/).

## Handle the webhook and fetch the full message

Each `message.created` notification carries the `grant_id` and the message `id`, but the body may be absent or truncated past the 1 MB payload limit. So the handler's job is small: validate the signature, acknowledge fast, then fetch the complete message through `GET /v3/grants/{grant_id}/messages/{message_id}`. Return a `200` quickly; slow handlers risk redelivery.

The request below retrieves one message by ID and returns the `subject`, `from`, `to`, `body`, and any `attachments` metadata in the same JSON shape across all providers. Do the heavy work after you respond to the webhook, ideally on a queue, so a slow model call never holds the connection open.

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

```python [emailWorkflow-Python SDK]
from nylas import Client

nylas = Client(api_key="<NYLAS_API_KEY>")

def handle_message_created(grant_id, message_id):
    message = nylas.messages.find(grant_id, message_id).data
    # Hand off to your extraction and action layer
    process(message.subject, message.body, message.from_)
```

The unified `from`, `subject`, and `body` fields mean you write one parser. The same handler works whether the message came from Gmail, Outlook, or an IMAP server, which is the difference from wiring up Gmail push notifications and Microsoft Graph subscriptions separately.

## Webhook triggers vs polling for new email

You can find new mail two ways: subscribe to webhooks, or poll `GET /v3/grants/{grant_id}/messages` on a timer. Webhooks push you a notification within seconds of a message syncing, while polling trades latency and wasted requests for control over timing. For most inbound automation, the `message.created` webhook wins on both speed and cost.

| Concern | Polling the messages endpoint | **`message.created` webhook** |
| --- | --- | --- |
| **Latency** | As long as your poll interval | **Seconds after sync** |
| **Request cost** | One call per inbox per interval | **One notification per new message** |
| **Scaling to many inboxes** | Grows linearly, hits rate limits | **Push, no per-inbox polling** |
| **Setup** | A scheduler and cursor tracking | **One destination, verify the signature** |

Polling is the right call in two cases: when your endpoint can't accept inbound HTTPS (a locked-down backend), or when you process a daily batch and second-level latency doesn't matter. Otherwise the push model is simpler to operate. Note that the API blocks Ngrok tunnels as webhook destinations, so use a stable public URL or a different tunneling tool during local development.

## Things to know about inbound email triggers

Inbound email automation has a few sharp edges worth knowing before you ship. The `message.created` trigger fires on newly synced messages, which includes mail your own app sends through a connected account, so filter on the `from` address or a header if you only want truly inbound mail. Notifications can arrive more than once: the platform retries failed deliveries, and a retry fans out to every destination registered for that trigger type.

Three operational facts shape a reliable handler. Payloads truncate at the 1 MB limit and gain a `.truncated` suffix, so never assume the body is present. The `x-nylas-signature` header must be verified with HMAC-SHA256 against your stored secret on every request. And processing must be idempotent: dedupe on `message_id` before triggering any side effect. For a worked end-to-end example that turns inbound mail into tickets, see [monitor an inbox for support tickets](/docs/cookbook/use-cases/ingest/monitor-inbox-support-tickets/).

## How do I build inbox automation into a customer-facing SaaS tool?

Connect each customer's mailbox once through OAuth, store the returned `grant_id`, and register one webhook destination that serves every grant. A single `message.created` subscription covers all connected accounts, so onboarding a new customer adds a grant, not a new integration. Your handler routes each notification by its `grant_id` to the right tenant.

The multi-tenant pattern is straightforward because the grant model does the isolation for you. Each notification names its `grant_id`, so you look up the owning customer, apply their rules, and act within their account, all through one webhook and one API key. Adding Microsoft, iCloud, or Yahoo customers later costs nothing extra: the same trigger and the same message shape cover all six providers. Watch the [grant lifecycle triggers](/docs/reference/notifications/) too, `grant.expired` in particular, so you can prompt a customer to re-authenticate before their automation silently stops. Subscribe to `grant.expired` alongside `message.created` in the same destination.

## What's next

- [Handle a new email webhook](/docs/cookbook/use-cases/build/new-email-webhook/) for the full registration and signature verification walkthrough
- [Extract structured data from email](/docs/cookbook/ai/extract-data-from-email/) for the AI parsing step in your trigger loop
- [Monitor an inbox for support tickets](/docs/cookbook/use-cases/ingest/monitor-inbox-support-tickets/) for a complete inbound-to-action example
- [Getting started with Nylas](/docs/v3/getting-started/) to create a project, connector, and your first grant