> ## Documentation Index
> Fetch the complete documentation index at: https://docs.spojit.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Trigger

> The entry point for every workflow: manual, webhook, schedule, email, or mailhook.

Every workflow starts with a **Trigger** node. It defines how and when the workflow runs. Select the trigger type in the properties panel when you click on the trigger node.

## Trigger types

| Type         | Description                                                                                                                                               |
| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Manual**   | Run the workflow on demand by clicking **Run** in the toolbar                                                                                             |
| **Webhook**  | Run when an external HTTP request hits the workflow's webhook URL. Supports Spojit's own scheme plus Shopify, GitHub, Slack, and custom HMAC verification |
| **Schedule** | Run on a cron schedule (e.g., every hour, daily at 9 AM)                                                                                                  |
| **Email**    | Run when a new email arrives in a connected mailbox (Gmail or Outlook)                                                                                    |
| **Mailhook** | Run when an email is sent to the workflow's own generated address (no mailbox required). See the [Mailhook Trigger](/workflow-editor/nodes/mailhook) page |

***

## Manual

No additional configuration needed. Click **Run** in the designer toolbar to execute the workflow. The request body you pass (if any) is available as the trigger output for downstream nodes.

***

## Webhook

A webhook trigger fires when an external HTTP service POSTs to its unique URL. Spojit verifies the request's HMAC signature against a **webhook signing connection** that you create once per upstream app and reuse across as many workflows as you like.

### How signing connections work

Each webhook trigger references a `Connection` that owns:

* A **scheme**: one of `Spojit`, `Shopify`, `GitHub`, `Slack`, or `Custom`. The scheme determines which header carries the signature, what gets signed, and how it's encoded.
* One or two **signing secrets**. Two enables zero-downtime rotation; both verify simultaneously during the rotation window.

Multiple workflows can share the same connection. This matches how providers like Shopify, GitHub, and Slack issue **one signing secret per app** that signs every webhook from that app; rotating the secret upstream means updating exactly one connection in Spojit, and every workflow keeps working.

### Setup

1. Select the trigger node, set **Trigger Type** to **Webhook**.
2. Click **+** next to the **Signing connection** picker.
3. Pick a connector tile:
   * **Spojit**: we generate the secret. Pick this when you control both sides of the integration (your own backend, scripts, internal tools).
   * **Shopify / GitHub / Slack**: paste the signing secret from your provider's app settings.
   * **Custom**: for any provider we don't have a preset for; you configure header, encoding, algorithm, and payload shape.
4. Name the connection (e.g. "Shopify production"), provide the secret if applicable, and **Create**.
5. The connection is auto-selected. Copy the **Webhook URL** and paste it into your provider's webhook configuration.

Manage existing connections (rotate secrets, see which workflows use them, delete) from **Connections → My Connections**; click **Configure** on a webhook row to open its detail page.

### Choosing a scheme

| Scheme      | Header                                                        | Signed payload     | Encoding     | Pick when                                 |
| ----------- | ------------------------------------------------------------- | ------------------ | ------------ | ----------------------------------------- |
| **Spojit**  | `X-Spojit-Signature: t=<ts>,v1=<hex>`                         | `<unix-ts>.<body>` | hex          | Your own backend / scripts / tools        |
| **Shopify** | `X-Shopify-Hmac-Sha256`                                       | raw body           | base64       | Inbound from a Shopify app                |
| **GitHub**  | `X-Hub-Signature-256: sha256=<hex>`                           | raw body           | hex          | Inbound from a GitHub App or repo webhook |
| **Slack**   | `X-Slack-Signature: v0=<hex>` (+ `X-Slack-Request-Timestamp`) | `v0:<ts>:<body>`   | hex          | Inbound from a Slack app's Events API     |
| **Custom**  | configurable                                                  | configurable       | configurable | Anything we don't have a preset for       |

All schemes use HMAC-SHA256 by default. The Custom scheme additionally allows SHA-1 (weak; only for legacy providers that require it) and SHA-512.

### Provider presets

#### Spojit

We generate a `whsec_…` secret on connection create and show it once. Sign every request like this:

* Compute Unix timestamp in seconds
* HMAC-SHA256 of `<timestamp>.<raw-body>` using your secret
* Hex-encode the result
* Send as header: `X-Spojit-Signature: t=<timestamp>,v1=<hex>`
* Optional: `X-Spojit-Event-Id: <unique-id>` for deduplication.

**Timestamp tolerance:** 5 minutes either direction. Older requests are rejected as replays.

#### Shopify

In your Shopify admin: **Settings → Notifications → Webhooks**. Each webhook subscription has a signing secret you can reveal; paste it when creating the connection.

Spojit verifies `X-Shopify-Hmac-Sha256` (base64-encoded HMAC-SHA256 of the raw body). Deduplication uses Shopify's `X-Shopify-Webhook-Id` header.

Provider docs: [Shopify webhooks configuration](https://shopify.dev/docs/apps/webhooks/configuration/https).

#### GitHub

In GitHub: when creating the webhook on a repository or GitHub App, set a **Secret** value. Paste the same value when creating the Spojit connection.

Spojit verifies `X-Hub-Signature-256: sha256=<hex>`. Deduplication uses GitHub's `X-GitHub-Delivery` header.

Provider docs: [Validating webhook deliveries](https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries).

#### Slack

In your Slack app: **Basic Information → App Credentials → Signing Secret**. Paste it when creating the Spojit connection.

Spojit verifies `X-Slack-Signature: v0=<hex>` over `v0:<ts>:<body>`, with the timestamp in `X-Slack-Request-Timestamp` (5-minute skew). Deduplication uses Slack's `X-Slack-Event-Id` header.

**URL verification:** Slack sends a one-time `url_verification` event when you save the webhook URL. Spojit handles this automatically: it verifies the HMAC, then echoes the `challenge` field as `text/plain`. No workflow run is dispatched for verification events.

Provider docs: [Verifying requests from Slack](https://api.slack.com/authentication/verifying-requests-from-slack).

#### Custom

For providers without a preset, configure each axis explicitly:

| Field                       | Description                                                                                      |
| --------------------------- | ------------------------------------------------------------------------------------------------ |
| **Signature header name**   | The HTTP header carrying the HMAC (e.g. `X-Vendor-Signature`).                                   |
| **Algorithm**               | `SHA-256` (default), `SHA-1` (weak, legacy only), `SHA-512`.                                     |
| **Encoding**                | `hex` or `base64`.                                                                               |
| **Value prefix**            | Optional literal string stripped before comparison (e.g. `sha256=`).                             |
| **Signed payload**          | `Raw body` (sign body verbatim) or `Timestamp.body` (Stripe-style, requires a timestamp header). |
| **Timestamp header / skew** | Required when payload includes a timestamp.                                                      |
| **Event ID header**         | Optional; used for deduplication.                                                                |

### Example: Postman

To test your webhook from Postman:

**Request:**

* Method: `POST`
* URL: your webhook URL (from the trigger properties panel)
* Headers:
  * `Content-Type: application/json`
  * `X-Spojit-Signature`: generated by the pre-request script below
  * `X-Spojit-Event-Id`: any unique string (optional; used for deduplication)
* Body (raw JSON):

```json theme={null}
{
  "event": "order.created",
  "data": {
    "orderId": "ORD-12345",
    "amount": 99.99
  }
}
```

**Pre-request Script** (paste in Postman's Scripts → Pre-request tab):

```javascript theme={null}
const secret = "whsec_YOUR_SECRET_HERE";
const timestamp = Math.floor(Date.now() / 1000).toString();
const body = pm.request.body.raw;
const payload = timestamp + "." + body;
const signature = CryptoJS.HmacSHA256(payload, secret)
  .toString(CryptoJS.enc.Hex);

pm.request.headers.upsert({
  key: "X-Spojit-Signature",
  value: "t=" + timestamp + ",v1=" + signature
});
```

Replace `whsec_YOUR_SECRET_HERE` with your actual signing secret.

### Example: Node.js

```javascript theme={null}
import crypto from "node:crypto";

const secret = "whsec_YOUR_SECRET_HERE";
const body = JSON.stringify({ event: "order.created", data: { orderId: "ORD-12345" } });
const timestamp = Math.floor(Date.now() / 1000);
const signature = crypto
  .createHmac("sha256", secret)
  .update(`${timestamp}.${body}`)
  .digest("hex");

const response = await fetch("https://your-webhook-url", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Spojit-Signature": `t=${timestamp},v1=${signature}`,
    "X-Spojit-Event-Id": "evt_unique_123",
  },
  body,
});

console.log(response.status); // 202
```

### Example: Python

```python theme={null}
import hmac, hashlib, time, json, requests

secret = "whsec_YOUR_SECRET_HERE"
body = json.dumps({"event": "order.created", "data": {"orderId": "ORD-12345"}})
timestamp = str(int(time.time()))
signature = hmac.new(
    secret.encode(), f"{timestamp}.{body}".encode(), hashlib.sha256
).hexdigest()

response = requests.post(
    "https://your-webhook-url",
    headers={
        "Content-Type": "application/json",
        "X-Spojit-Signature": f"t={timestamp},v1={signature}",
        "X-Spojit-Event-Id": "evt_unique_456",
    },
    data=body,
)

print(response.status_code)  # 202
```

### Response codes

| Code  | Meaning                                                                                                                       |
| ----- | ----------------------------------------------------------------------------------------------------------------------------- |
| `202` | Accepted: workflow run dispatched. Response includes `executionId` and `status`.                                              |
| `401` | Signature missing, invalid, or timestamp outside the 5-minute tolerance window.                                               |
| `404` | Trigger not found or inactive.                                                                                                |
| `409` | Duplicate event: a request with the same event-id header was already processed. Response includes the original `executionId`. |
| `402` | Insufficient credits.                                                                                                         |
| `403` | Not authorized to run this workflow.                                                                                          |
| `500` | Internal error.                                                                                                               |

### Deduplication

Deduplication is **opt-in via an event-id header**. When the header is present, a second request with the same id on the same webhook returns `409` with the original `executionId` instead of running the workflow again. When it's absent, every request fires a fresh execution.

The header depends on the connection type:

| Connection type | Event-id header                                                                       |
| --------------- | ------------------------------------------------------------------------------------- |
| Spojit Webhook  | `X-Spojit-Event-Id` (optional; set per logical event if you want retries deduped)     |
| Shopify Webhook | `X-Shopify-Webhook-Id` (Shopify sends automatically)                                  |
| GitHub Webhook  | `X-GitHub-Delivery` (GitHub sends automatically)                                      |
| Slack Webhook   | `event_id` field in the request body (Slack sends automatically)                      |
| Custom Webhook  | The header name you configured under **Event ID header** when creating the connection |

### Secret rotation

Each connection can hold up to **2 active signing secrets** at a time, enabling zero-downtime rotation. Because rotation lives on the connection, all triggers using that connection rotate atomically; there's never a window where some workflows accept the old secret and others don't.

Open the connection's detail page (**Connections → My Connections → Configure** on the row), then:

1. Click **Rotate / add secret** to create a second secret.
2. **Spojit scheme:** copy the newly generated secret (shown once). **Other schemes:** rotate the secret upstream (in Shopify / GitHub / Slack admin, etc.), then paste the new value here.
3. Both secrets verify simultaneously. Check the **Last used** timestamp on each; once the new secret shows recent activity, you've fully cut over.
4. Delete the old secret.

The detail page also shows **every workflow** whose webhook trigger references this connection, which is handy for confirming what'll be affected before you rotate. Spojit verifies incoming requests against **all active secrets** on the connection; the first match wins.

### Webhook payload

The full HTTP request body is passed as the trigger output to downstream nodes. If the body is valid JSON, it's parsed automatically. Otherwise, it's available as a raw string under `{ raw: "..." }`.

### Polling execution status

When your webhook returns **202 Accepted** (either async mode, or sync mode that hit its timeout), you'll get back an `executionId`. You can poll the status endpoint to find out where the run is and retrieve the output once it's done.

**Endpoint:**

```
GET /api/executions/{executionId}/status
```

**Authentication:** same HMAC scheme as webhook ingress; reuse your webhook's signing secret (no separate credentials needed). The signed payload is `{timestamp}.{executionId}`.

```
X-Spojit-Signature: t=<unix-timestamp>,v1=<hex-hmac-sha256>
```

**Response (200 OK):**

```json theme={null}
{
  "executionId": "cc74c932-59a8-4838-aef7-744b7a53637f",
  "status": "RUNNING",
  "currentStep": {
    "id": "connector-abc123",
    "label": "AI Agent",
    "status": "RUNNING"
  },
  "stepsCompleted": 1,
  "totalSteps": 3,
  "output": null,
  "error": null
}
```

**Fields:**

| Field            | Description                                                                                                                                                                                      |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `status`         | `PENDING`, `RUNNING`, `WAITING_APPROVAL`, `COMPLETED`, `FAILED`, or `CANCELLED`                                                                                                                  |
| `currentStep`    | The step currently executing (or waiting, e.g. for approval). `null` when the run has finished.                                                                                                  |
| `stepsCompleted` | Count of steps that have reached `COMPLETED`. Grows as the run progresses.                                                                                                                       |
| `totalSteps`     | Total work-producing steps in the workflow. **Only populated for linear workflows.** Returns `null` if the graph contains any Condition, Loop, or Parallel node (actual path length is dynamic). |
| `output`         | The workflow's output, populated once `status === 'COMPLETED'`. When the workflow ends with a Response node, this mirrors its `{ statusCode, headers, body }` shape.                             |
| `error`          | Error message if `status === 'FAILED'`, otherwise `null`.                                                                                                                                        |

**Response codes:**

| Code  | Meaning                                                                                         |
| ----- | ----------------------------------------------------------------------------------------------- |
| `200` | Status retrieved                                                                                |
| `401` | Signature missing, invalid, expired (>5 min), or the originating trigger has no signing secrets |
| `404` | Execution not found                                                                             |

### Example: Node.js polling

```javascript theme={null}
import crypto from "node:crypto";

const secret = "whsec_YOUR_SECRET";
const executionId = "cc74c932-59a8-4838-aef7-744b7a53637f";

async function pollStatus() {
  const timestamp = Math.floor(Date.now() / 1000);
  const signature = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${executionId}`)
    .digest("hex");

  const response = await fetch(
    `https://your-spojit-host/api/executions/${executionId}/status`,
    {
      headers: {
        "X-Spojit-Signature": `t=${timestamp},v1=${signature}`,
      },
    },
  );

  return response.json();
}

// Poll until terminal status
while (true) {
  const status = await pollStatus();
  console.log(status.status, status.currentStep?.label ?? "-");
  if (["COMPLETED", "FAILED", "CANCELLED"].includes(status.status)) {
    console.log("Output:", status.output);
    break;
  }
  await new Promise((r) => setTimeout(r, 2000));
}
```

### Example: Python polling

```python theme={null}
import hmac, hashlib, time, requests

secret = "whsec_YOUR_SECRET"
execution_id = "cc74c932-59a8-4838-aef7-744b7a53637f"

def poll_status():
    timestamp = str(int(time.time()))
    signature = hmac.new(
        secret.encode(),
        f"{timestamp}.{execution_id}".encode(),
        hashlib.sha256,
    ).hexdigest()

    response = requests.get(
        f"https://your-spojit-host/api/executions/{execution_id}/status",
        headers={
            "X-Spojit-Signature": f"t={timestamp},v1={signature}",
        },
    )
    return response.json()

while True:
    status = poll_status()
    print(status["status"], (status.get("currentStep") or {}).get("label", "-"))
    if status["status"] in ("COMPLETED", "FAILED", "CANCELLED"):
        print("Output:", status["output"])
        break
    time.sleep(2)
```

### Example: Postman

Same pre-request script as the webhook call, but with the payload changed to sign `{timestamp}.{executionId}`:

```javascript theme={null}
const secret = "whsec_YOUR_SECRET";
const url = pm.request.url.toString();
const match = url.match(/\/executions\/([^/]+)\/status/);
const executionId = match ? match[1] : "";

const timestamp = Math.floor(Date.now() / 1000).toString();
const payload = timestamp + "." + executionId;
const signature = CryptoJS.HmacSHA256(payload, secret)
  .toString(CryptoJS.enc.Hex);

pm.request.headers.upsert({
  key: "X-Spojit-Signature",
  value: "t=" + timestamp + ",v1=" + signature
});
```

### Recommended polling cadence

* Start with 1–2 second intervals for short-running workflows
* Back off to 5–10 seconds for workflows that routinely take minutes
* Always stop polling once `status` is `COMPLETED`, `FAILED`, or `CANCELLED`; these are terminal

***

## Schedule

Run a workflow automatically on a cron schedule. Each firing produces an independent workflow execution, identical in every way to a manually triggered run.

### Setup

1. Select the trigger node in the designer
2. Set **Trigger Type** to **Schedule**
3. Click **Add Schedule**
4. Edit the **Cron** expression (5-field Unix cron, e.g., `0 9 * * 1-5` for 9 AM weekdays)
5. Pick a **Timezone** (IANA, e.g. `Australia/Sydney`, `America/New_York`)
6. Leave **Active** on to have it fire; toggle off to pause
7. Click **Save workflow**; the schedule begins firing on the next matching time

Changes only apply on **Save**. Editing the cron or timezone field does not affect the running schedule until you save. The **Next fires** preview shows the three upcoming firings based on the currently saved configuration, so you can always see what's actually going to run.

### Multiple schedules

A single trigger node can hold any number of schedules. Use this to model real-world timing needs such as:

* **Business hours vs. weekends**: one schedule for `0 9 * * 1-5` (weekdays 9 AM) and another for `0 12 * * 0,6` (weekends noon)
* **Peaks and off-peaks**: run every 15 minutes during business hours, every hour overnight
* **Multi-region timing**: one schedule in `America/New_York`, another in `Europe/London`, both hitting the same workflow

Each schedule is independent; you can pause one and leave the other active, or delete one without affecting the others.

### Cron format

Schedules use standard 5-field Unix cron:

```
┌───────────── minute (0-59)
│ ┌─────────── hour (0-23)
│ │ ┌───────── day of month (1-31)
│ │ │ ┌─────── month (1-12)
│ │ │ │ ┌───── day of week (0-6, Sun=0)
│ │ │ │ │
* * * * *
```

| Expression     | Meaning                            |
| -------------- | ---------------------------------- |
| `0 * * * *`    | Every hour on the hour             |
| `*/15 * * * *` | Every 15 minutes                   |
| `0 9 * * *`    | Every day at 9 AM                  |
| `0 9 * * 1-5`  | 9 AM on weekdays                   |
| `0 0 1 * *`    | Midnight on the 1st of every month |
| `0 9 * * 1`    | 9 AM every Monday                  |

The properties panel shows a human-readable description under the cron input (e.g. "At 09:00 AM, Monday through Friday") so you can verify your expression before saving.

### Timezones

Every schedule has an IANA timezone (e.g. `Australia/Sydney`, `Europe/London`, `UTC`). The cron expression is evaluated in that timezone, which means daylight savings transitions are handled correctly; a `0 9 * * *` schedule in `America/New_York` fires at 9 AM local time year-round, not at a fixed UTC offset.

### Pausing a schedule

Toggle the **Active** switch off and save the workflow. The schedule is retained in the workflow's configuration (so you can re-enable it later) but won't fire while paused. This is reversible without losing the cron/timezone configuration.

### Tips

* The **Next fires** preview shows the saved schedule, not your draft edits, so it's a trustworthy readout of what's actually going to run
* You can combine a **Schedule** trigger with a **Manual** or **Webhook** trigger on the same workflow. Each is an independent entrypoint that converges on the same downstream logic.
* Keep the cron on conservative intervals during development (e.g. every 10 min) to validate behavior before switching to production cadence

***

## Email

Run a workflow automatically when a new email arrives in a connected Gmail or Outlook mailbox. The workflow receives the parsed message (sender, subject, body, attachment metadata) and can act on it (extract data from a PDF order form, write to your ERP, notify a Slack channel, etc.).

### How it works

You connect a mailbox to Spojit via OAuth (the OAuth grant is read-only by default; no password sharing). Spojit polls that mailbox on a schedule you choose and fires a workflow execution for every new message that matches your filters. The mail itself never travels through any third party; Spojit reads it directly from your provider (Google or Microsoft) using the OAuth token you granted.

### Recommended pattern: dedicated mailbox

Instead of connecting your everyday inbox, create a **dedicated mailbox** in your workspace just for the workflow, for example `orders-automation@acme.com`. Then set up a forwarding rule (or share the address with senders) so only the relevant emails land there. Benefits:

* Clear scope: Spojit only ever sees the dedicated mailbox, not your personal inbox
* Easy to revoke: disconnect the mailbox in Spojit *or* delete the mailbox in your workspace; either kills access
* Easy to audit: IT can see exactly what's flowing through the dedicated address

### Setup

1. **Connect the mailbox** in the [Connections page](/connections):
   * Click **Add connection** → pick **Gmail (trigger)** or **Outlook (trigger)**
   * Choose a permission level:
     * **Read only**: Spojit can only read messages
     * **Read + modify**: Spojit can also mark messages as read or move them after processing (needed for the optional "After processing" actions below)
   * Sign in with the account that owns the mailbox
   * The connection appears in your list with the granted scopes visible
2. **Add the trigger** in the workflow designer:
   * Drop a Trigger node, set **Trigger Type** to **Email**
   * Click **Add mailbox**
   * Pick the connection from the dropdown
   * Pick the folder/label to watch (e.g., `INBOX`, or a custom label like `Spojit/Orders`)
   * Set the poll interval (1–60 minutes; default 2)
   * Optional: filter by sender domain or subject regex
   * Optional: pick what to do with the message after Spojit processes it
3. Click **Save workflow**; Spojit starts polling on the next cycle

### Filters

Two optional filters narrow what triggers a run:

* **From allowlist**: comma-separated patterns. `@acme.com` matches any sender at acme.com; `orders@vendor.com` matches the exact address. Multiple patterns are OR'd.
* **Subject regex**: a JavaScript-flavor regular expression. The message fires only if the subject matches.

If both filters are set, both must pass.

### After processing

By default Spojit doesn't touch the message; it stays in your folder, in whatever read state it was. If you want, you can ask Spojit to:

* **Mark as read**: useful for visual confirmation that Spojit picked it up
* **Move to label / folder**: pick a destination; in Gmail the label is added (other labels stay), in Outlook the message moves out of the source folder

Both options require a **Read + modify** connection; pick one when you create the connection if you plan to use these. Read-only connections will have the modify options disabled in the trigger panel.

### Multiple mailboxes per workflow

A single workflow can watch multiple mailboxes, useful for things like "watch our US orders mailbox AND our EU orders mailbox, both run the same processing logic." Add as many as you need; each fires the workflow independently.

### Multiple workflows watching the same mailbox

Different workflows can watch the same mailbox + folder. Every matching message fires every matching workflow, independently and in parallel. Common pattern: one workflow processes the order into your ERP, another notifies sales on Slack, and both fire on each incoming email.

A few important behavior notes for this pattern:

* **No "first one wins"**: each workflow gets its own copy of the email, runs its own logic
* **`After processing` actions don't hide messages from siblings**: even if Workflow A moves the message to a `Processed` folder, Workflow B still sees and processes it within the same poll cycle. You won't miss a workflow because of another workflow's cleanup action.
* **Conflicting after-processing actions apply in order**: if A says "mark read" and B says "move to folder X", both happen. In Gmail, labels accumulate; in Outlook, the move wins.

The one limit: within a *single* workflow, you can't add two EMAIL triggers with identical settings (same connection + folder + filters), which would just fire the workflow twice for every message, which is never what you want. Cross-workflow duplicates are fine and intentional.

### What Spojit can and can't see

* **Can see**: messages in the folder you select. Spojit's code is committed to only querying that folder, even though the OAuth scope technically grants whole-mailbox access (this is how Google/Microsoft OAuth scopes work; folder-level scoping isn't an option from the provider).
* **Can't do** (read-only connections): mark messages as read, move messages, delete messages
* **Can do with Read + modify** (only if you picked it at connection setup): mark as read, move/label, but never delete
* **Doesn't store**: full message bodies. Bodies and attachments are passed into the workflow execution and retained per the workflow execution's normal retention policy. They are not stored in any separate Spojit-owned mailbox.

### Disconnecting

In the Connections page, click **Remove** on the email connection. Spojit deletes the OAuth token and stops polling immediately. To revoke at the provider side as well (defense in depth):

* **Google**: [myaccount.google.com/permissions](https://myaccount.google.com/permissions) → find Spojit → Remove access
* **Microsoft**: [myaccount.microsoft.com/consent](https://myaccount.microsoft.com/consent) → find Spojit → Remove

### Tips

* Start with a **dedicated mailbox**: much easier story for IT and for limiting blast radius
* Use **Read only** by default; only upgrade to **Read + modify** if you actually want Spojit to mark or move messages
* The poll interval doesn't need to be aggressive; 2–5 minutes is fine for almost every order-processing use case; 1-minute polls aren't dramatically faster end-to-end given queue + provider latency
* Pair a dedicated mailbox with a forwarding rule from your main inbox to filter just the messages you want Spojit to act on
* For testing, send yourself an email matching your filters and watch the **Recent activity** panel to confirm it landed

***

## Mailhook

A mailhook gives the workflow its **own unique email address** (e.g. `mh-x7k2m9qf4a3vbn8c@mailhook.spojit.com`). Any message sent to that address starts a run, with no mailbox connection or OAuth involved: a webhook, but for email. Delivery is push rather than polling, so runs start seconds after the message arrives.

Use a mailhook when you control where the mail gets sent (vendor notification settings, forwarding rules, system-generated mail). Use the Email trigger when the mail already lands in a mailbox you own.

Full documentation, including address rotation, filters, and the input payload shape, is on the [Mailhook Trigger](/workflow-editor/nodes/mailhook) page.

***

## Output

The trigger node produces an output that downstream nodes can reference:

* **Manual**: The input data passed when clicking Run (if any)
* **Webhook**: The parsed HTTP request body
* **Schedule**: `{ scheduledAt }`, the ISO-8601 timestamp of the firing that started the run
* **Email**: `{ from, to, cc, subject, textBody, htmlBody, headers, receivedAt, conversationId, attachments[] }`. Each attachment is a reference (`{ provider, providerMessageId, attachmentId, filename, mimeType, size }`); the bytes are fetched on demand by downstream nodes that need them.
* **Mailhook**: `{ from, to, cc, replyTo, subject, text, html, truncated, receivedAt, emailId, messageId, attachments[] }`. Each attachment is a reference (`{ id, filename, contentType }`); the bytes are fetched on demand by downstream nodes that need them. See [Mailhook Trigger](/workflow-editor/nodes/mailhook#workflow-input) for the full shape.

## Tips

* Start with **Manual** triggers during development, then switch to **Webhook**, **Schedule**, **Email**, or **Mailhook** when you're ready for production
* A workflow can have multiple triggers (e.g., Manual + Webhook, or Schedule + Email); each is an independent entrypoint
* Use the **Active** toggle to temporarily pause any trigger without deleting it or losing the configuration
