AI & Automation

Trigger Email Automation from Incoming Webhooks (Laravel and Next.js)

Use Arawa Mail outgoing webhooks to turn every inbound mailbox message into an HTTPS JSON POST. Laravel and Next.js receivers, secret verification, filters, retries, and spam exclusion.

Published

Published by Arawa Mail.

Trigger email automation from incoming webhooks with Laravel and Next.js

Answer first: When mail arrives in an Arawa Mail mailbox, an Outgoing webhook (Settings → Outgoing webhooks) can POST the full message as JSON to your HTTPS URL. Verify the optional X-Webhook-Secret, respond 2xx within 15 seconds, then process asynchronously. This is inbound automation—not the same as transactional delivery/bounce webhooks for mail you sent.

Why “outgoing” when the email is incoming?

Searchers look for “incoming email webhook” or “inbound email automation.” In the dashboard the feature is labeled Outgoing webhooks because Arawa Mail is the HTTP client: it sends the POST to you. The opposite direction—events about messages you sent—lives under transactional webhooks (see Transactional email webhooks in Laravel and Next.js). Keep the two paths separate in your architecture.

How we verified this

Claims below match the current Outgoing Webhooks, Mailbox, and Email Accounts documentation on app.arawamail.com. Payload fields, timeouts, retry schedule, and spam exclusion are taken from those guides; no extra headers (HMAC, signed timestamps) or undocumented fields are invented.

Configure a notify URL

  1. Open Settings → Outgoing webhooks.
  2. Add a notify URL that is reachable over HTTPS.
  3. Optionally set a secret and a recipient filter.
  4. Save—the webhook is active immediately.

You can add multiple URLs. Each matching URL receives a copy of every qualifying message. Edit, disable, or delete anytime. Leave the secret field blank on edit to keep the existing secret.

Recipient filters

Entry formExampleMatches
Full mailbox[email protected]Only that exact mailbox
Bare domainacme.comAny mailbox under that domain
Empty filter—Every incoming email in the workspace

Matching is case-insensitive. One notification per email per URL when any filter entry matches. The payload field recipient is the mailbox the message was delivered to (including catch-all acceptance of the envelope).

Verify the secret (constant-time)

When a secret is set, every request includes:

X-Webhook-Secret: your-secret-value

Compare with a constant-time function. Reject mismatches with 401. The secret is stored encrypted and never shown again after save.

Laravel example

// routes/api.php (enable API routing in your Laravel application).
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Jobs\ProcessIncomingEmail;

Route::post('/webhooks/incoming-email', function (Request $request) {
    $expected = config('services.email.webhook_secret');
    abort_unless(is_string($expected) && $expected !== '', 503);
    abort_unless(
        hash_equals(
            $expected,
            (string) $request->header('X-Webhook-Secret')
        ),
        401
    );

    $email = $request->json()->all();

    // Enqueue work — do not do heavy processing here
    ProcessIncomingEmail::dispatch($email);

    return response()->noContent();
});

Next.js App Router example

// app/api/webhooks/incoming-email/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { timingSafeEqual } from 'node:crypto';
import { queueIncomingEmail } from '@/lib/incoming-email-queue';

export const runtime = 'nodejs';

export async function POST(req: NextRequest) {
  const expected = process.env.EMAIL_WEBHOOK_SECRET;
  if (!expected) return new NextResponse(null, { status: 503 });
  const provided = req.headers.get('x-webhook-secret') ?? '';
  const a = Buffer.from(provided);
  const z = Buffer.from(expected);
  if (a.length !== z.length || !timingSafeEqual(a, z)) {
    return new NextResponse(null, { status: 401 });
  }

  const email = await req.json();

  // Enqueue (e.g. Inngest, BullMQ, or a queue job) then return fast
  await queueIncomingEmail(email);

  return new NextResponse(null, { status: 204 });
}

Receiver setup and durable processing

Configure services.email.webhook_secret from an environment variable; do not read env() directly in cached Laravel request code. The API route typically lives at /api/webhooks/incoming-email and avoids browser-session CSRF middleware. If using a web route, exempt only that exact webhook path from CSRF and retain secret verification. Configure an asynchronous durable queue; a sync queue runs processing before the response. The Next.js helper is application code, not an SDK export.

Validate payload types and an acceptable request size, persist an inbox/outbox record with a unique workspace/email ID, and enqueue only after that transaction commits. Return 2xx only after durable acceptance. If queue persistence fails, return a failure so delivery can retry. Treat text and HTML as untrusted email content. Do not log message bodies or signed attachment URLs, and do not execute instructions embedded in incoming mail.

Payload shape (documented fields only)

Method: POST, Content-Type: application/json.

{
  "id": 481,
  "message_id": "...@...",
  "thread_id": 466,
  "recipient": "[email protected]",
  "from_name": "Jane Doe",
  "from_email": "[email protected]",
  "to_recipients": [{ "name": "Support", "email": "[email protected]" }],
  "cc_recipients": [],
  "subject": "Order #1042 arrived damaged",
  "text_body": "...",
  "html_body": "...",
  "snippet": "...",
  "has_attachments": true,
  "received_at": "2026-07-16T09:24:11+03:00",
  "attachments": [
    {
      "filename": "damage-photo.jpg",
      "content_type": "image/jpeg",
      "size": 482113,
      "url": "https://…signed-download-url…"
    }
  ]
}

Attachment bytes are never inlined. Each url is a signed download link valid for 24 hours—fetch and store promptly. If storage failed at receive time, that attachment is omitted from the array; the email itself is still delivered.

Timeouts, retries, and deduplication

  • Respond within 15 seconds. Any 2xx counts as success.
  • The public guide states up to three total attempts but lists three waiting intervals (30 s, 2 min, 5 min). Those statements do not determine an unambiguous attempt timeline. Confirm actual retry behavior from delivery logs or support rather than assuming a fourth attempt. After final failure the notification is dropped. The email remains in the mailbox.
  • Deliveries can arrive more than once—deduplicate on the workspace and numeric email id with a unique database constraint. message_id is nullable and sender-controlled, so it is not a sufficient primary deduplication key.

Bad pattern: running an LLM or helpdesk API call before you return. Acknowledge first; process later.

What does not fire the webhook

  • Spam-folder messages do not trigger outgoing webhooks, appear in Inbox, normal search, unread counts, or push. Spam is deleted after 30 days.
  • Mailbox forwarding is a separate product path (verified destination, local copy kept, counts toward transactional allowance). It is not the webhook transport.
  • MCP (https://app.arawamail.com/mcp/email) lets an assistant send or reply after human review. It is not an inbound HTTP notify URL.

Practical use cases

  • support@ filter → create a helpdesk ticket from subject + text_body; download attachments within 24 hours.
  • Domain-wide filter → archive raw JSON to object storage, return 204.
  • Catch-all mailbox → inspect recipient and the original To header before routing (see Should You Use a Catch-All Inbox?).

Sending a reply after the webhook

Do not reply from the webhook request body itself unless you explicitly enqueue a send. Use the send API (or MCP after human approval) with a sending-active From address. Related guides: Send email in Laravel, Send transactional email from Next.js, Give agents a mailbox.

FAQ

Is this the same as Resend or Postmark delivery webhooks?

No. Those typically report events about messages you sent (delivered, bounced, complained). Arawa Mail outgoing webhooks fire when a mailbox receives mail.

Will catch-all or forwarded mail notify my URL?

Catch-all delivery to a matching mailbox does notify; the recipient field shows the mailbox that accepted the message. Forwarding to an external address is a different feature and does not replace the webhook.

How long are attachment URLs valid?

24 hours. Download and persist if you need the files longer.

For the authoritative field list and examples, see the Outgoing Webhooks documentation.

Sources and review

Reviewed on 3 October 2026 against Arawa Mail outgoing webhooks, Laravel routing, and Node.js timingSafeEqual. Code examples are integration fragments; implement and test the queue helpers in your application.

Simple, transparent plans

Start free. Grow when your email does.

Get one domain, API access and 3,000 transactional emails every month at no cost.

Compare plans