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

> ## Agent Instructions
> AhaSend stores all message data in the EU. See /facts.
> The API base URL is https://api.ahasend.com. Send the API key in the Authorization: Bearer header. See /api-reference/authentication.
> Use send.ahasend.com for SMTP. Ports 25, 587 and 2525 use STARTTLS. Port 465 is not supported. See /smtp.
> For send-only integrations, create an API Key v2 under Credentials → Add and scope it to messages:send:{your-domain}. See /send-api/credentials and /api-reference/scopes.

# Inbound Email Routing and Parsing to Webhooks

> Receive inbound email in your app with AhaSend routing: point MX records to AhaSend, match addresses with wildcards and get parsed JSON.

AhaSend inbound routes turn email sent to your domain into JSON requests to your app; this guide covers MX setup, address matching and route configuration.

Available on Free, Pro and Max. See [plans and features](https://ahasend.com/docs/facts.md#plans-and-features).

> [!NOTE]
>
> **Inbound Email Processing:** Message routing handles emails sent TO your domains, while webhooks track emails sent FROM your domains. Both use similar HTTP POST mechanisms but serve different purposes.

## What is Message Routing?

Message routing is a powerful mechanism for efficiently managing and directing inbound email traffic within your applications. By setting up message routes, you can automatically parse and direct incoming emails to appropriate endpoints based on predefined rules or criteria.

This functionality enables you to:

> [card] Support Ticket Systems
>
> Automatically convert inbound emails into support tickets or customer inquiries

> [card] Reply Processing
>
> Handle email replies and integrate them into existing conversation threads

> [card] Email-to-Database
>
> Parse and store email content directly in your databases or CRM systems

> [card] Automated Workflows
>
> Trigger application logic based on inbound email content and metadata

### How Message Routes Work in AhaSend

**1. Email Received**

An email is sent to an address on your AhaSend domain (e.g., `support@yourdomain.com` or `ticket-*@yourdomain.com`)

**2. Route Matching**

AhaSend checks configured routes to find matching recipient patterns

**3. Email Processing**

The email is parsed, processed, and formatted according to your route settings

**4. HTTP Request Sent**

AhaSend sends a POST request to your endpoint URL with the email data

> [!NOTE]
>
> **MX Record Requirement:** Message routing requires your domain's [MX records from your domain details](https://ahasend.com/docs/domains.md) to point to AhaSend. Without proper MX configuration, AhaSend cannot receive emails sent to your domain.

## Delivery Limits

AhaSend makes 12 attempts over about 11 hours, including the first attempt. Each attempt times out after 5 seconds. After more than 100 consecutive failed attempts, the route is disabled and account users are emailed. See [route retry policy](https://ahasend.com/docs/api-reference/webhooks/retry-policy.md) and [security](https://ahasend.com/docs/api-reference/webhooks/security.md).

## Prerequisites

Before setting up message routing, ensure your domain is properly configured:

> [accordion] Domain Setup
>
> **Required Configuration:**
>
> * Domain must be added and verified in your AhaSend account
> * [MX records from your domain details](https://ahasend.com/docs/domains.md) must point to AhaSend's mail servers
> * DNS propagation should be complete (may take up to 48 hours)
>
> **Verification Steps:**
>
> 1. Check your domain status in the AhaSend dashboard
> 2. Verify [MX records from your domain details](https://ahasend.com/docs/domains.md) are correctly configured
> 3. Test email delivery to ensure routing works

> [accordion] Message Data Retention
>
> **Routing depends on stored message data:**
>
> * Inbound emails are stored as message data and delivered to your endpoint from that store
> * Message data retention must be **1 day or more** on the account; at 0 days inbound emails are discarded on arrival and nothing is routed
> * Check the setting under **Account Settings → Retention Policy** (see [Data Retention](https://ahasend.com/docs/retention.md))

> [accordion] Endpoint Requirements
>
> **Your Application Endpoint Must:**
>
> * Accept HTTP POST requests
> * Respond with 2xx status codes (200-299) for successful processing
> * Handle JSON payloads of up to about 30 MB when attachments are enabled: inbound messages are accepted up to 20 MiB (the same limit as outbound SMTP) and AhaSend does not cap the routed payload, so it can reach the message size plus base64 and JSON overhead
> * Process requests within 5 seconds to avoid timeouts
>
> **Required Handling:**
>
> * Verify webhook signatures for security
> * Implement idempotency using webhook-id headers
> * Handle duplicate or retry requests gracefully

## Creating Message Routes

Configure message routes in your AhaSend dashboard to start processing inbound emails:

![Screenshot of the form for creating a new route](https://ahasend.com/sites/default/files/inline-images/image_6.png)

**1. Access Message Routing**

1. **Log in** to your [AhaSend Dashboard](https://dash.ahasend.com)
2. **Navigate** to the **[Routes](https://dash.ahasend.com/account/-/integrations/routes)** section from the main menu
3. **Click** the **"Add Route"** button

**2. Configure Endpoint URL**

**Enter Your Endpoint URL:**

* Provide the complete URL where you want to receive routing events
* Must be a valid HTTPS URL (HTTP allowed for development)
* Should respond with 2xx status codes for successful processing

**Example URLs:**

```text theme={null}
https://api.yourapp.com/inbound-email
https://yourapp.com/api/support/tickets
```

**3. Set Recipient Pattern**

**Configure Email Address Routing:**

Choose the email addresses to route using the recipient email address field:

**Specific Address:**

* `support` - Routes only `support@yourdomain.com`

**Wildcard Patterns:**

* `*` - Routes ALL emails to the domain
* `ticket-*` - Routes `ticket-123@yourdomain.com`, `ticket-abc@yourdomain.com`, etc.
* `*-support` - Routes `help-support@yourdomain.com`, `sales-support@yourdomain.com`, etc.

> [!TIP]
>
> **Start Specific:** Begin with specific addresses like "support" or "info" before using broad wildcards like "\*" to avoid overwhelming your endpoint.

**4. Configure Processing Options**

**Customize how emails are processed:**

> [accordion] Attachments
>
> **Include Attachments:**
>
> * Enable to receive email attachments as base64-encoded data
>
> * Significantly increases payload size; there is no per-attachment limit, only the 20 MiB total message limit enforced at the SMTP server
>
> * Useful for document processing or file handling workflows
>
> > [!WARNING]
> >
> > **Payload Size:** Enabling attachments can create payloads of up to about 30 MB. Ensure your endpoint and any proxy in front of it accept requests of that size. Messages larger than 20 MiB are rejected at SMTP time, so the sender gets a bounce and nothing reaches your endpoint.

> [accordion] Headers
>
> **Include Raw Headers:**
>
> * Receive complete original email headers
> * Useful for advanced email processing or compliance requirements
> * Includes routing information, authentication results, and metadata

> [accordion] Strip Replies
>
> **Reply Extraction:**
>
> * Automatically separate new message content from quoted replies
> * Useful for conversation threading and clean content extraction
> * Provides both full body and extracted reply content

> [accordion] Deduplicate by Message ID
>
> **Handle Multiple Recipients:**
>
> * **Disabled (default):** Separate request for each recipient (To, CC, BCC)
> * **Enabled:** Single request per email regardless of recipient count
> * Use when you want to process each email only once

**5. Create and Secure Route**

1. **Click** "Create Route" to save your configuration
2. **Note the Route Secret** displayed on the details page
3. **Copy and store** the secret securely for signature verification

> [!NOTE]
>
> **Save Your Secret:** Copy the route secret from the route details page. Store it securely in your application's configuration for webhook signature verification.

## Testing Message Routes

Verify your message routing integration works correctly:

**1. Use Test Events**

1. **Go to** your message route details page in the dashboard
2. **Click** "Send Test Event"
3. **Verify** your endpoint receives the test payload

Test events contain realistic sample data and arrive within seconds.

**2. Development Tools**

**For Quick Testing:**

* Use [Beeceptor](https://beeceptor.com) to create temporary endpoint URLs
* Inspect incoming requests and verify payload structure
* Test without writing application code first

**For Development:**

* Use [ngrok](https://ngrok.com) to expose local development servers
* Test routing handling in your actual application code

**3. Send Real Emails**

**Test with Actual Email Delivery:**

1. Ensure your domain's [MX records from your domain details](https://ahasend.com/docs/domains.md) point to AhaSend
2. Send an email to your configured address pattern
3. Verify your endpoint receives the routing request
4. Check the email content and metadata are correct

> [!TIP]
>
> **Test Email Sources:** Try sending from different email providers (Gmail, Outlook, etc.) to test various email formats and edge cases.

## Security and Verification

Message routing follows the [Standard Webhooks specification](https://github.com/standard-webhooks/standard-webhooks) for secure payload delivery:

`webhook-id` is the delivery ID used for deduplication. The payload's `webhook_id` or `route_id` identifies the configured endpoint, not the delivery.

## Security Headers

Every routing request includes the same security headers as webhooks:

```http theme={null}
webhook-id: wh_1234567890abcdef
webhook-timestamp: 1683360000
webhook-signature: v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=
```

> [accordion] webhook-id
>
> **Unique routing identifier** - Use as an idempotency key to prevent processing duplicate routing requests from retries.

> [accordion] webhook-timestamp
>
> **Unix timestamp** when the routing request was sent - Use to reject old routing attempts.

> [accordion] webhook-signature
>
> **HMAC signature** of the payload using your route secret - Verify this to ensure authenticity.

### Signature Verification

Use Standard Webhooks libraries to verify routing request authenticity:

```javascript Node.js theme={null}
import { Webhook } from 'standardwebhooks';

async function readLimitedBody(request) {
  const reader = request.body?.getReader();
  if (!reader) return new Uint8Array();
  const chunks = [];
  let size = 0;
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    size += value.byteLength;
    if (size > 30_000_000) { await reader.cancel(); return null; }
    chunks.push(value);
  }
  const body = new Uint8Array(size);
  let offset = 0;
  for (const chunk of chunks) { body.set(chunk, offset); offset += chunk.byteLength; }
  return body;
}

const webhook = new Webhook(process.env.ROUTE_SECRET, { format: 'raw' });

// In your routing handler
export async function handleInboundEmail(request, processInboundEmail) {
  const payload = await readLimitedBody(request);
  if (payload === null) return new Response(null, { status: 413 });
  const headers = {
    'webhook-id': request.headers.get('webhook-id'),
    'webhook-timestamp': request.headers.get('webhook-timestamp'),
    'webhook-signature': request.headers.get('webhook-signature')
  };

  try {
    const routingEvent = webhook.verify(new TextDecoder('utf-8', { fatal: true }).decode(payload), headers);
    // Process only verified inbound email; keep addresses and content out of logs.

    if (routingEvent.type !== "message.routing") return new Response(null, { status: 204 });
    // Commit durable, deduplicated work before acknowledging the request.
    await processInboundEmail(routingEvent.data, headers["webhook-id"]);

    return new Response('OK', { status: 200 });
  } catch (error) {
    return new Response('Invalid signature', { status: 400 });
  }
}
```

The Python example expects a framework request with a raw `body` attribute. Configure a 30,000,000-byte request limit in that framework or its front proxy before it buffers the body. Supply the application processing callback used below.

```python Python theme={null}
import base64
import binascii

def supported_signatures(header):
    # Skip unsupported or malformed entries before the library decodes them.
    supported = []
    for entry in (header or "").split():
        version, separator, encoded = entry.partition(",")
        if version != "v1" or not separator:
            continue
        try:
            signature = base64.b64decode(encoded, validate=True)
        except (binascii.Error, ValueError):
            continue
        if len(signature) == 32:
            supported.append(entry)
    return " ".join(supported)

from standardwebhooks.webhooks import Webhook
import os

webhook = Webhook(os.environ['ROUTE_SECRET'].encode('utf-8'))

def handle_inbound_email(request, process_inbound_email):
    headers = {
        'webhook-id': request.headers.get('webhook-id'),
        'webhook-timestamp': request.headers.get('webhook-timestamp'),
        'webhook-signature': supported_signatures(request.headers.get('webhook-signature'))
    }

    try:
        payload = request.body
        if len(payload) > 30_000_000:
            return {'error': 'Request too large'}, 413
        routing_event = webhook.verify(payload, headers)

        # Process verified inbound email
        if routing_event.get('type') != 'message.routing':
            return {'status': 'ignored'}, 204
        email_data = routing_event['data']
        # Keep addresses and message content out of logs.

        # Create support ticket, process reply, etc.
        process_inbound_email(email_data)

        return {'status': 'success'}, 200
    except Exception as e:
        return {'error': 'Invalid signature'}, 400
```

```go Go theme={null}
package main

import (
    "io"
    "log"
    "net/http"
    "os"
    standardwebhooks "github.com/standard-webhooks/standard-webhooks/libraries/go"
)

func handleInboundEmail(w http.ResponseWriter, r *http.Request) {
    webhook, err := standardwebhooks.NewWebhookRaw([]byte(os.Getenv("ROUTE_SECRET")))
    if err != nil {
        http.Error(w, "Invalid route secret", 500)
        return
    }

    r.Body = http.MaxBytesReader(w, r.Body, 30_000_000)
    payload, err := io.ReadAll(r.Body)
    if err != nil {
        http.Error(w, "Cannot read webhook body", 413)
        return
    }
    headers := r.Header

    if err := webhook.Verify(payload, headers); err != nil {
        http.Error(w, "Invalid signature", 400)
        return
    }

    // Process verified inbound email
    log.Printf("Verified routing request received")

    // Parse payload and process email
    // Create support ticket, process reply, etc.

    w.WriteHeader(200)
}
```

```php PHP theme={null}
<?php
// PHP handler: read the exact body, with a limit, before parsing JSON.
$body = file_get_contents('php://input', false, null, 0, 30_000_001);
$secret = getenv('ROUTE_SECRET');
$id = $_SERVER['HTTP_WEBHOOK_ID'] ?? '';
$timestamp = $_SERVER['HTTP_WEBHOOK_TIMESTAMP'] ?? '';
$signatures = $_SERVER['HTTP_WEBHOOK_SIGNATURE'] ?? '';
if ($body === false || strlen($body) > 30_000_000) { http_response_code(413); exit; }
if (!$secret) { http_response_code(500); exit; }
if (!$id || !ctype_digit($timestamp) || abs(time() - (int)$timestamp) > 300) {
    http_response_code(400); exit;
}
$expected = base64_encode(hash_hmac('sha256', "$id.$timestamp.$body", $secret, true));
$valid = false;
foreach (explode(' ', $signatures) as $candidate) {
    $parts = explode(',', $candidate, 2);
    if (count($parts) === 2 && $parts[0] === 'v1' && hash_equals($expected, $parts[1])) $valid = true;
}
if (!$valid) { http_response_code(400); exit; }
try { $event = json_decode($body, true, 512, JSON_THROW_ON_ERROR); }
catch (JsonException $e) { http_response_code(400); exit; }
// Verification-only example: add an atomic durable handoff before business side effects.
// Unknown event types are valid signed deliveries too.
http_response_code(204);
```

> [!NOTE]
>
> **Standard Webhooks Compatibility:** Message routing uses the same [Standard Webhooks libraries](https://github.com/standard-webhooks/standard-webhooks) as regular webhooks, so you can use the same verification code for both.

## Message Route Payload Structure

All message routing requests follow the Standard Webhooks payload format:

```json theme={null}
{
  "type": "message.routing",
  "route_id": "11111111-1111-4111-8111-111111111111",
  "timestamp": "2024-05-07T10:41:45.026792973Z",
  "data": {
    // Inbound email data
  }
}
```

### Complete Payload Example

```json theme={null}
{
  "type": "message.routing",
  "route_id": "11111111-1111-4111-8111-111111111111",
  "timestamp": "2024-05-07T10:41:45.026792973Z",
  "data": {
    "id": "route-msg-12345",
    "from": "customer@gmail.com",
    "reply_to": "customer@gmail.com",
    "to": "support@yourdomain.com",
    "subject": "Help with my account",
    "message_id": "<unique-message-id@gmail.com>",
    "size": 2048,
    "spam_score": 0.1,
    "bounce": false,
    "cc": "",
    "date": "Mon, 06 May 2024 13:15:46 +0000",
    "in_reply_to": "",
    "references": "",
    "auto_submitted": "",
    "html_body": "<p>I need help with my account settings.</p>",
    "plain_body": "I need help with my account settings.",
    "reply_from_plain_body": "I need help with my account settings.",
    "attachments": [
      {
        "filename": "screenshot.png",
        "disposition": "attachment",
        "content_type": "image/png",
        "content_id": "attachment_001",
        "data": "base64-encoded-image-data"
      }
    ],
    "headers": {
      "From": "customer@gmail.com",
      "To": "support@yourdomain.com",
      "Subject": "Help with my account",
      "Date": "Mon, 06 May 2024 13:15:46 +0000",
      "Message-ID": "<unique-message-id@gmail.com>",
      "Content-Type": "text/html; charset=UTF-8"
    }
  }
}
```

### Key Fields Explained

> [accordion] Email Metadata
>
> **Core Message Information:**
>
> * `from` - Sender's email address and name
> * `to` - Recipient address that matched your route
> * `subject` - Email subject line
> * `message_id` - Unique identifier from the email headers
> * `date` - When the email was sent
> * `size` - Email size in bytes

> [accordion] Message Content
>
> **Email Body Content:**
>
> * `html_body` - HTML version of the email (if available)
> * `plain_body` - Plain text version of the email
> * `reply_from_plain_body` - Extracted reply content (if strip replies enabled)

> [accordion] Threading Information
>
> **Conversation Threading:**
>
> * `in_reply_to` - Message ID this email is replying to
> * `references` - Complete thread reference chain
> * `reply_to` - Address to reply to (may differ from sender)

> [accordion] Spam and Security
>
> **Content Analysis:**
>
> * `spam_score` - Spam score; higher values indicate more likely spam
>
> * `bounce` - Whether this is a bounce/delivery failure message
>
> * `auto_submitted` - Indicates automated emails (out-of-office, etc.)
>
> > [!WARNING]
> >
> > Choose a spam-score threshold for your application and review it against real messages. The example threshold is not a delivery guarantee.

> [accordion] Optional Data
>
> **Additional Information (if enabled):**
>
> * `attachments` - Array of file attachments with base64 data
> * `headers` - Complete raw email headers
> * `cc` - CC recipients (comma-separated)

The workflow functions below use application-owned callbacks such as `createSupportTicket`, `findConversationByMessageId`, and `enqueueOnce`. Provide those functions from your ticketing, database, or job-queue code. Only invoke them after signature verification.

## Common Use Cases

> [accordion] Support Ticket System
>
> **Automatically create support tickets from inbound emails:**
>
> ```javascript theme={null}
> async function processInboundEmail(emailData) {
>   const ticket = {
>     subject: emailData.subject,
>     description: emailData.plain_body,
>     customerEmail: emailData.from,
>     priority: emailData.spam_score > 5 ? 'low' : 'normal',
>     attachments: emailData.attachments || []
>   };
>
>   await createSupportTicket(ticket);
> }
> ```
>
> **Route Pattern:** `support` or `help`

> [accordion] Reply Processing
>
> **Handle email replies to existing conversations:**
>
> ```javascript theme={null}
> async function processInboundEmail(emailData) {
>   if (emailData.in_reply_to) {
>     // Find existing conversation
>     const conversation = await findConversationByMessageId(emailData.in_reply_to);
>
>     if (conversation) {
>       await addReplyToConversation(conversation.id, {
>         content: emailData.reply_from_plain_body || emailData.plain_body,
>         sender: emailData.from,
>         timestamp: emailData.date
>       });
>     }
>   }
> }
> ```
>
> **Route Pattern:** `*` (catch all replies)

> [accordion] Department-Specific Routing
>
> **Route emails to different departments:**
>
> ```javascript theme={null}
> async function processInboundEmail(emailData) {
>   const department = emailData.to.split('@')[0]; // Extract local part
>
>   switch (department) {
>     case 'sales':
>       await createSalesLead(emailData);
>       break;
>     case 'support':
>       await createSupportTicket(emailData);
>       break;
>     case 'billing':
>       await createBillingInquiry(emailData);
>       break;
>   }
> }
> ```
>
> **Route Patterns:** Multiple routes (`sales`, `support`, `billing`)

> [accordion] Document Processing
>
> **Process email attachments automatically:**
>
> ```javascript theme={null}
> async function processInboundEmail(emailData) {
>   if (emailData.attachments && emailData.attachments.length > 0) {
>     for (const attachment of emailData.attachments) {
>       if (attachment.content_type === 'application/pdf') {
>         const pdfData = Buffer.from(attachment.data, 'base64');
>         await processPdfDocument(pdfData, attachment.filename);
>       }
>     }
>   }
> }
> ```
>
> **Route Pattern:** `documents` or `upload`
> **Settings:** Enable attachments

## Best Practices

> [accordion] Error Handling
>
> **Robust Error Handling:**
>
> * Always return 2xx status codes for successfully processed emails
> * Log processing errors for debugging and monitoring
> * Handle malformed or unexpected email formats gracefully
> * Implement retry logic for external service failures
>
> **Example Error Handling:**
>
> ```javascript theme={null}
> export async function acceptEmail(emailData, enqueueDurableWork) {
>   try {
>     await enqueueDurableWork(emailData);
>     return { status: 202 };
>   } catch {
>     return { status: 503 }; // Let AhaSend retry a failed durable handoff.
>   }
> }
> ```

> [accordion] Security Considerations
>
> **Securing Your Routes:**
>
> * Always verify webhook signatures in production
> * Sanitize email content before processing or storage
> * Be cautious with attachments - scan for malware
> * Validate sender addresses for sensitive operations
> * Consider spam scores when processing emails
>
> **Spam Filtering Example:**
>
> ```javascript theme={null}
> export function shouldProcessEmail(emailData, threshold = 10) {
>   // This threshold is your application's policy, not an AhaSend limit.
>   return emailData.spam_score <= threshold;
> }
> ```

> [accordion] Performance Optimization
>
> **Optimizing Route Performance:**
>
> * Process emails asynchronously when possible
> * Commit a durable job before returning 2xx; process the job separately
> * Monitor endpoint response times and success rates
> * Implement proper logging and metrics
> * Scale endpoints based on email volume
>
> **Async Processing Pattern:**
>
> ```javascript theme={null}
> export async function queueEmail(emailData, response, enqueueOnce, webhookId) {
>   await enqueueOnce(webhookId, emailData);
>   response.status(202).send('Accepted');
> }
> ```

## Troubleshooting

> [accordion] Emails Not Being Routed
>
> **Check Route Configuration:**
>
> * Verify [MX records from your domain details](https://ahasend.com/docs/domains.md) point to AhaSend servers
> * Confirm recipient pattern matches the email address
> * Check route is enabled and not disabled due to failures
> * Test with "Send Test Event" feature
>
> **DNS Verification:**
>
> ```bash theme={null}
> dig MX yourdomain.com
> nslookup -type=MX yourdomain.com
> ```
>
> **Common Issues:**
>
> * [MX records from your domain details](https://ahasend.com/docs/domains.md) not updated or propagated
> * Typos in recipient patterns
> * Route disabled due to endpoint failures
> * Message data retention set to 0 days (see [Data Retention](https://ahasend.com/docs/retention.md))

> [accordion] Endpoint Not Receiving Requests
>
> **Verify Endpoint Configuration:**
>
> * Ensure endpoint URL is accessible from internet
> * Check firewall settings and security groups
> * Verify endpoint responds with 2xx status codes
> * Test endpoint with curl or Postman
>
> **Testing Your Endpoint:**
>
> ```bash theme={null}
> curl -X POST https://your-endpoint.com/inbound \
>   -H "Content-Type: application/json" \
>   -d '{"test": "data"}'
> ```
>
> **Monitoring:**
>
> * Check AhaSend dashboard for delivery attempts
> * Review server logs for incoming requests
> * Monitor endpoint response times and error rates

> [accordion] Signature Verification Failures
>
> **Debugging Signature Issues:**
>
> * Ensure route secret is correctly configured
> * Use raw request body for signature calculation
> * Check Standard Webhooks library implementation
> * Verify header extraction is working correctly
>
> **Debug Steps:**
>
> ```javascript theme={null}
> console.log('Webhook ID:', headers['webhook-id']);
> console.log('Timestamp:', headers['webhook-timestamp']);
> console.log('Payload Length:', payload.length);
> ```

> [accordion] What Messages Does AhaSend Reject?
>
> AhaSend checks every message that arrives at `mx.ahasend.com` before it is queued for your route. A message that fails a check is refused during the SMTP transaction with a permanent error, so the sender's server returns it as a bounce and nothing reaches your endpoint. The bounce text quotes our reply:
>
> | Check | SMTP reply | What it means |
> | - | - | - |
> | Neither domain is ours | `556` | No AhaSend account owns the recipient's domain or the sender's domain. Usually a typo in the address, or a domain whose MX points to AhaSend without being added to an account. |
> | Not exactly one `From` address | `550` | The message has no `From` header, or more than one address in it. |
> | Fails the sender's DMARC policy | `550` | The `From` domain publishes `p=reject` or `p=quarantine`, and neither SPF nor DKIM aligns with that domain. |
> | No SPF or DKIM pass | `550` | The `From` domain has no DMARC record, or one with `p=none`, and the message has neither a passing SPF result nor a valid DKIM signature for any domain. |
> | Bare line endings | `552` | Lines end in LF instead of CRLF, which the SMTP standard requires. Typical of a script that writes the message by hand. |
> | Larger than 20 MiB | `552` | The whole message, attachments included after base64 encoding. See [SMTP limits](https://ahasend.com/docs/smtp.md). |
>
> The authentication checks keep forged mail out of your application: a message that passes was sent by a server the `From` domain authorizes. A sender whose domain has no SPF, no DKIM and no DMARC is refused; they need at least SPF or DKIM, which Gmail and Microsoft require of them anyway.
>
> A rejected message is not stored and does not appear in your message log; the sender's bounce is the only record.

> [accordion] Route Disabled
>
> **Automatic Route Disabling:**
>
> * AhaSend disables routes after more than 100 consecutive failed attempts
> * You'll receive an email notification when this happens
> * Fix underlying endpoint issues before re-enabling
>
> **Re-enabling Process:**
>
> 1. Identify and resolve the root cause of failures
> 2. Test your endpoint is working correctly
> 3. Re-enable the route in your dashboard
> 4. Monitor for successful deliveries
>
> **Prevention:**
>
> * Monitor route success rates regularly
> * Set up alerts for route failures
> * Implement proper error handling and logging

## Related Guides

* [Domain setup](https://ahasend.com/docs/domains.md), [API authentication](https://ahasend.com/docs/api-reference/authentication.md), [Route payload](https://ahasend.com/docs/api-reference/routes/route-message.md).
* [Route CLI](https://ahasend.com/docs/cli/commands/routes.md), [Local route testing](https://ahasend.com/docs/cli/route-testing.md), [Signature verification](https://ahasend.com/docs/api-reference/webhooks/security.md).
* [Retry policy](https://ahasend.com/docs/api-reference/webhooks/retry-policy.md), [Node.js SDK](https://ahasend.com/docs/guides/nodejs-sdk.md), [Go SDK](https://ahasend.com/docs/guides/go-sdk.md).
* [Plans and limits](https://ahasend.com/docs/facts.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.