# Get a list of your subscriptions to MailChannels Inbound Source: https://docs.mailchannels.com/api-reference/account/get-a-list-of-your-subscriptions-to-mailchannels-inbound /inbound/api-reference/openapi.yaml get /subscriptions # Retrieve the condition of the service Source: https://docs.mailchannels.com/api-reference/account/retrieve-the-condition-of-the-service /inbound/api-reference/openapi.yaml get /status # Delete Custom Tracking Domain Source: https://docs.mailchannels.com/api-reference/custom-tracking/delete-custom-tracking-domain /email-api/api-reference/openapi.yaml delete /custom-tracking-domains/{hostname}/{scope} Permanently delete an existing custom tracking domain for the given hostname and scope. The domain can be re-registered if needed. WARNING: Any tracking links or unsubscribe URLs in previously sent emails using this domain will stop working immediately. # Register Custom Tracking Domain Source: https://docs.mailchannels.com/api-reference/custom-tracking/register-custom-tracking-domain /email-api/api-reference/openapi.yaml post /custom-tracking-domains Register a custom branded domain for click tracking, open tracking, or unsubscribe handling. By default, MailChannels uses shared domains for these links. Using a custom domain improves brand consistency by replacing shared domains with your own (e.g., click.example.com). Once registered, select the domain at send time using its `name`. Before registration completes, two DNS records must be in place: 1. A TXT record at `_mailchannels-verify.` containing the verification token (returned in the 202 response). 2. A CNAME record at `` pointing to `links.mailchannels.net`. # Retrieve Custom Tracking Domains Source: https://docs.mailchannels.com/api-reference/custom-tracking/retrieve-custom-tracking-domains /email-api/api-reference/openapi.yaml get /custom-tracking-domains Retrieve all custom tracking domains registered under your account. Optional filters include domain name, status, scope, limit and offset. # Update Custom Tracking Domain Source: https://docs.mailchannels.com/api-reference/custom-tracking/update-custom-tracking-domain /email-api/api-reference/openapi.yaml patch /custom-tracking-domains/{hostname}/{scope} Update an existing custom tracking domain by its hostname and scope. Supports updating the custom tracking domain's name or toggling its active status. # Create DKIM Key Pair Source: https://docs.mailchannels.com/api-reference/dkim/create-dkim-key-pair /email-api/api-reference/openapi.yaml post /domains/{domain}/dkim-keys Create a DKIM key pair for a specified domain and selector using the specified algorithm and key length, for the current customer. # DKIM, SPF & Domain Lockdown Check Source: https://docs.mailchannels.com/api-reference/dkim/dkim-spf-&-domain-lockdown-check /email-api/api-reference/openapi.yaml post /check-domain Validates a domain's email authentication setup by retrieving its DKIM, SPF, and Domain Lockdown status. This endpoint checks whether the domain is properly configured for secure email delivery. # Retrieve DKIM Keys Source: https://docs.mailchannels.com/api-reference/dkim/retrieve-dkim-keys /email-api/api-reference/openapi.yaml get /domains/{domain}/dkim-keys Search for DKIM keys by domain, with optional filters. If selector is provided, at most one key will be returned. # Rotate DKIM Key Pair Source: https://docs.mailchannels.com/api-reference/dkim/rotate-dkim-key-pair /email-api/api-reference/openapi.yaml post /domains/{domain}/dkim-keys/{selector}/rotate Rotate an active DKIM key pair. Mark the original key as 'rotated', and create a new key pair with the required new key selector, reusing the same algorithm and key length. The rotated key remains valid for signing for a 3-day grace period, and is automatically changed to 'retired' 2 weeks after rotation. Publish the new key to its DNS TXT record before rotated key expires for signing as emails sent with an unpublished key will fail DKIM validation by receiving providers. After the grace period, only the new key is valid for signing if published. # Update DKIM Key Status Source: https://docs.mailchannels.com/api-reference/dkim/update-dkim-key-status /email-api/api-reference/openapi.yaml patch /domains/{domain}/dkim-keys/{selector} Update fields of an existing DKIM key pair for the specified domain and selector, for the current customer. Currently, only the status field can be updated. revoked: Indicates that the key is compromised and should not be used. retired: Indicates that the key has been rotated and is no longer in use. rotated: Indicates that the key is going through the rotation process. Only active key pairs can be updated to this status, and no new key pair is created. The rotated key can be used to sign emails for 3 days after the status update, and will automatically change to 'retired' 2 weeks after update. For a smooth key transition, it is recommended to create and publish a new key pair before signing is disabled for the rotated key. # Bulk provision domains Source: https://docs.mailchannels.com/api-reference/domains/bulk-provision-domains /inbound/api-reference/openapi.yaml post /domains/batch Provision up to 1000 domains to use MailChannels Inbound. # Create domain alias Source: https://docs.mailchannels.com/api-reference/domains/create-domain-alias /inbound/api-reference/openapi.yaml post /domains/{domain}/alias/{alias} Create an alias for the domain # Fetch downstream addresses Source: https://docs.mailchannels.com/api-reference/domains/fetch-downstream-addresses /inbound/api-reference/openapi.yaml get /domains/{domain}/downstream-address Retrieve stored downstream addresses for the domain. # List domains Source: https://docs.mailchannels.com/api-reference/domains/list-domains /inbound/api-reference/openapi.yaml get /domains Fetch a list of all domains associated with this API key. # Provision domain Source: https://docs.mailchannels.com/api-reference/domains/provision-domain /inbound/api-reference/openapi.yaml post /domains Provision a single domain to use MailChannels Inbound. # Remove domain Source: https://docs.mailchannels.com/api-reference/domains/remove-domain /inbound/api-reference/openapi.yaml delete /domains/{domain} De-provision a domain to cease protecting it with MailChannels Inbound. # Remove domain alias Source: https://docs.mailchannels.com/api-reference/domains/remove-domain-alias /inbound/api-reference/openapi.yaml delete /domains/{domain}/alias/{alias} Remove an alias for the domain # Set downstream address Source: https://docs.mailchannels.com/api-reference/domains/set-downstream-address /inbound/api-reference/openapi.yaml put /domains/{domain}/downstream-address Sets the list of downstream addreses for the domain. This action deletes any existing downstream address for the domain before creating new ones. If the 'records' parameter is an empty array, all downstream address records will be deleted. # Update API key Source: https://docs.mailchannels.com/api-reference/domains/update-api-key /inbound/api-reference/openapi.yaml put /domains/{domain}/api-key Update the API key that is associated with a domain. # Update domain settings Source: https://docs.mailchannels.com/api-reference/domains/update-domain-settings /inbound/api-reference/openapi.yaml put /domains/{domain}/settings Update the abuse policy settings (these settings determine how spam messages are handled), downstream addresses, admins and aliases for a domain. # Get alert counts Source: https://docs.mailchannels.com/api-reference/get-alert-counts /outbound/api-reference/openapi.yaml post /alertcount Returns alert counts grouped by hour, day, or week. # Get alerts Source: https://docs.mailchannels.com/api-reference/get-alerts /outbound/api-reference/openapi.yaml post /alerts Returns alerts that match the specified filters and date range. # Get sender profile Source: https://docs.mailchannels.com/api-reference/get-sender-profile /outbound/api-reference/openapi.yaml post /senderprofile Returns message counts grouped by time interval and the policy disposition for the specified sender. # Add domain list entry Source: https://docs.mailchannels.com/api-reference/lists/add-domain-list-entry /inbound/api-reference/openapi.yaml post /domains/{domain}/lists/{listname} Add an entry to a domain blocklist or safelist. # Add item to customer list Source: https://docs.mailchannels.com/api-reference/lists/add-item-to-customer-list /inbound/api-reference/openapi.yaml post /lists/{listname} # Add item to recipient list Source: https://docs.mailchannels.com/api-reference/lists/add-item-to-recipient-list /inbound/api-reference/openapi.yaml post /users/{email}/lists/{listname} # Delete domain list entry Source: https://docs.mailchannels.com/api-reference/lists/delete-domain-list-entry /inbound/api-reference/openapi.yaml delete /domains/{domain}/lists/{listname} Delete an entry from the blocklist or safelist for a domain. # Delete item from customer list Source: https://docs.mailchannels.com/api-reference/lists/delete-item-from-customer-list /inbound/api-reference/openapi.yaml delete /lists/{listname} # Delete item from recipient list Source: https://docs.mailchannels.com/api-reference/lists/delete-item-from-recipient-list /inbound/api-reference/openapi.yaml delete /users/{email}/lists/{listname} # Get customer list entries Source: https://docs.mailchannels.com/api-reference/lists/get-customer-list-entries /inbound/api-reference/openapi.yaml get /lists/{listname} # Get domain list entries Source: https://docs.mailchannels.com/api-reference/lists/get-domain-list-entries /inbound/api-reference/openapi.yaml get /domains/{domain}/lists/{listname} Get all of the blocklist or safelist entries for a domain. # Get recipient list entries Source: https://docs.mailchannels.com/api-reference/lists/get-recipient-list-entries /inbound/api-reference/openapi.yaml get /users/{email}/lists/{listname} # Bulk create login links Source: https://docs.mailchannels.com/api-reference/login-links/bulk-create-login-links /inbound/api-reference/openapi.yaml post /domains/batch/login-link Generate a batch of links that allow a user to log in as a domain administrator to their different domains. Maximum of 1000 links per request. # Create login link Source: https://docs.mailchannels.com/api-reference/login-links/create-login-link /inbound/api-reference/openapi.yaml get /domains/{domain}/login-link Generate a link that allows a user to log in as a domain administrator. # Retrieve Engagement Metrics Source: https://docs.mailchannels.com/api-reference/metrics/retrieve-engagement-metrics /email-api/api-reference/openapi.yaml get /metrics/engagement Retrieve engagement metrics for messages sent from your account, including counts of open and click events. Supports optional filters for time range, and campaign ID. # Retrieve Performance Metrics Source: https://docs.mailchannels.com/api-reference/metrics/retrieve-performance-metrics /email-api/api-reference/openapi.yaml get /metrics/performance Retrieve performance metrics for messages sent from your account, including counts of processed, delivered, hard-bounced, and complained events. Supports optional filters for time range, and campaign ID. # Retrieve Recipient Behaviour Metrics Source: https://docs.mailchannels.com/api-reference/metrics/retrieve-recipient-behaviour-metrics /email-api/api-reference/openapi.yaml get /metrics/recipient-behaviour Retrieve recipient behaviour metrics for messages sent from your account, including counts of unsubscribed events. Supports optional filters for time range, and campaign ID. # Retrieve Sender Metrics Source: https://docs.mailchannels.com/api-reference/metrics/retrieve-sender-metrics /email-api/api-reference/openapi.yaml get /metrics/senders/{sender_type} Retrieves a list of senders, either sub-accounts or campaigns, with their associated message metrics. Sorted by total # of sent messages (processed + dropped) Supports optional filter for time range, and optional settings for limit, offset, and sort order. Note: senders without any messages in the given time range will not be included in the results. The default time range is from one month ago to now, and the default sort order is descending. # Retrieve Volume Metrics Source: https://docs.mailchannels.com/api-reference/metrics/retrieve-volume-metrics /email-api/api-reference/openapi.yaml get /metrics/volume Retrieve volume metrics for messages sent from your account, including counts of processed, delivered and dropped events. Supports optional filters for time range and campaign ID. # Create a recipient Source: https://docs.mailchannels.com/api-reference/recipients/create-a-recipient /inbound/api-reference/openapi.yaml put /users # Submit a false negative or false positive report Source: https://docs.mailchannels.com/api-reference/reporting/submit-a-false-negative-or-false-positive-report /inbound/api-reference/openapi.yaml post /report # Request sender delisting Source: https://docs.mailchannels.com/api-reference/request-sender-delisting /outbound/api-reference/openapi.yaml post /remediation Request delisting for a sender identified by a message ID or envelope sender address. Provide at least one of `message_id` or `sender`. If you provide both, `message_id` is used to find the message. This endpoint supports sender delisting only. It does not accept false positive or false negative reports or other content remediation requests. # Search traffic logs Source: https://docs.mailchannels.com/api-reference/search-traffic-logs /outbound/api-reference/openapi.yaml post /search Search email delivery logs for your account. By default, searches cover the last 3 days of data. To search a different date range, include both start_date and end_date in the request body. # Send an Email Source: https://docs.mailchannels.com/api-reference/send/send-an-email /email-api/api-reference/openapi.yaml post /send Sends an email message to one or more recipients. **Click Tracking Notes:** Only links (`` tags) meeting all of the following conditions are processed for click tracking: - The URL is non-empty. - The URL starts with "http" or "https". - The link does not have a clicktracking attribute set to 'off'. # Send an Email Asynchronously Source: https://docs.mailchannels.com/api-reference/send/send-an-email-asynchronously /email-api/api-reference/openapi.yaml post /send-async Queues an email message for asynchronous processing and returns immediately with a request ID. The email will be processed in the background, and you'll receive webhook events for all delivery status updates (e.g. dropped, processed, delivered, hard-bounced). These webhook events are identical to those sent for the synchronous /send endpoint. Use this endpoint when you need to send emails without waiting for processing to complete. This can improve your application's response time, especially when sending to multiple recipients. # Activate Sub-account Source: https://docs.mailchannels.com/api-reference/sub-accounts/activate-sub-account /email-api/api-reference/openapi.yaml post /sub-account/{handle}/activate Activates a suspended sub-account identified by its handle, restoring its ability to send emails. # Create Sub-account Source: https://docs.mailchannels.com/api-reference/sub-accounts/create-sub-account /email-api/api-reference/openapi.yaml post /sub-account Creates a new sub-account under the parent account. Each sub-account must have a unique handle composed solely of lowercase alphanumeric characters. If no handle is provided, a random handle will be generated. # Create Sub-account API Key Source: https://docs.mailchannels.com/api-reference/sub-accounts/create-sub-account-api-key /email-api/api-reference/openapi.yaml post /sub-account/{handle}/api-key Creates a new API key for the specified sub-account. # Create Sub-account SMTP Password Source: https://docs.mailchannels.com/api-reference/sub-accounts/create-sub-account-smtp-password /email-api/api-reference/openapi.yaml post /sub-account/{handle}/smtp-password Creates a new SMTP password for the specified sub-account. # Delete Sub-account Source: https://docs.mailchannels.com/api-reference/sub-accounts/delete-sub-account /email-api/api-reference/openapi.yaml delete /sub-account/{handle} Deletes the sub-account identified by its handle. # Delete Sub-account API Key Source: https://docs.mailchannels.com/api-reference/sub-accounts/delete-sub-account-api-key /email-api/api-reference/openapi.yaml delete /sub-account/{handle}/api-key/{id} Deletes the API key identified by its ID for the specified sub-account. # Delete Sub-account Limit Source: https://docs.mailchannels.com/api-reference/sub-accounts/delete-sub-account-limit /email-api/api-reference/openapi.yaml delete /sub-account/{handle}/limit Deletes the limit for the specified sub-account. After a successful deletion, the specified sub-account will be limited to the parent account's limit. # Delete Sub-account SMTP Password Source: https://docs.mailchannels.com/api-reference/sub-accounts/delete-sub-account-smtp-password /email-api/api-reference/openapi.yaml delete /sub-account/{handle}/smtp-password/{id} Deletes the SMTP password identified by its ID for the specified sub-account. # Retrieve Sub-account API Keys Source: https://docs.mailchannels.com/api-reference/sub-accounts/retrieve-sub-account-api-keys /email-api/api-reference/openapi.yaml get /sub-account/{handle}/api-key Retrieves details of all API keys associated with the specified sub-account. For security reasons, the full API key is **not** returned; only the key ID and a partially redacted version are provided. # Retrieve Sub-account Limit Source: https://docs.mailchannels.com/api-reference/sub-accounts/retrieve-sub-account-limit /email-api/api-reference/openapi.yaml get /sub-account/{handle}/limit Retrieves the limit of a specified sub-account. A value of -1 indicates that the sub-account inherits the parent account's limit, allowing the sub-account to utilize any remaining capacity within the parent account's allocation. # Retrieve Sub-account SMTP Passwords Source: https://docs.mailchannels.com/api-reference/sub-accounts/retrieve-sub-account-smtp-passwords /email-api/api-reference/openapi.yaml get /sub-account/{handle}/smtp-password Retrieves details of all SMTP passwords associated with the specified sub-account. For security, the full SMTP password is **not** returned; only the password ID and a partially redacted version are provided. # Retrieve Sub-account Usage Stats Source: https://docs.mailchannels.com/api-reference/sub-accounts/retrieve-sub-account-usage-stats /email-api/api-reference/openapi.yaml get /sub-account/{handle}/usage Retrieves usage statistics for the specified sub-account during the current billing period. # Retrieve Sub-accounts Source: https://docs.mailchannels.com/api-reference/sub-accounts/retrieve-sub-accounts /email-api/api-reference/openapi.yaml get /sub-account Retrieves all sub-accounts associated with the parent account. The response is paginated with a default limit of 1000 sub-accounts per page and an offset of 0. # Set Sub-account Limit Source: https://docs.mailchannels.com/api-reference/sub-accounts/set-sub-account-limit /email-api/api-reference/openapi.yaml put /sub-account/{handle}/limit Sets the limit for the specified sub-account. The minimum allowed sends is 0. # Suspend Sub-account Source: https://docs.mailchannels.com/api-reference/sub-accounts/suspend-sub-account /email-api/api-reference/openapi.yaml post /sub-account/{handle}/suspend Suspends the sub-account identified by its handle. This action disables the account, preventing it from sending any emails until it is reactivated. # Create Suppression Entries Source: https://docs.mailchannels.com/api-reference/suppression/create-suppression-entries /email-api/api-reference/openapi.yaml post /suppression-list Creates suppression entries for the specified account. Parent accounts can create suppression entries for all associated sub-accounts. If suppression_type is not provided, it defaults to 'non-transactional'. The operation is atomic, meaning all entries are successfully added or none are added if an error occurs. # Delete Suppression Entry Source: https://docs.mailchannels.com/api-reference/suppression/delete-suppression-entry /email-api/api-reference/openapi.yaml delete /suppression-list/recipients/{recipient} Deletes suppression entry associated with the account based on the specified recipient and source. If source is not provided, it defaults to 'api'. If source is set to 'all', all suppression entries related to the specified recipient will be deleted. # Retrieve Suppression List Source: https://docs.mailchannels.com/api-reference/suppression/retrieve-suppression-list /email-api/api-reference/openapi.yaml get /suppression-list Retrieve suppression entries associated with the specified account. Supports filtering by recipient, source and creation date range. The response is paginated, with a default limit of 1000 entries per page and an offset of 0. # Retrieve Usage Stats Source: https://docs.mailchannels.com/api-reference/usage/retrieve-usage-stats /email-api/api-reference/openapi.yaml get /usage Retrieves usage statistics during the current billing period. # Delete Customer Webhooks Source: https://docs.mailchannels.com/api-reference/webhooks/delete-customer-webhooks /email-api/api-reference/openapi.yaml delete /webhook Deletes all registered webhook endpoints for the customer. # Enroll for Webhook Notifications Source: https://docs.mailchannels.com/api-reference/webhooks/enroll-for-webhook-notifications /email-api/api-reference/openapi.yaml post /webhook Enrolls the customer to receive event notifications via webhooks. # Resend Events Source: https://docs.mailchannels.com/api-reference/webhooks/resend-events /email-api/api-reference/openapi.yaml post /webhook-batch/{batch_id}/resend Synchronously resend the webhook batch with the provided batch_id for the customer. The result is returned in the response. # Retrieve Customer Webhooks Source: https://docs.mailchannels.com/api-reference/webhooks/retrieve-customer-webhooks /email-api/api-reference/openapi.yaml get /webhook Retrieves all registered webhook endpoints associated with the customer. # Retrieve Webhook Batches Source: https://docs.mailchannels.com/api-reference/webhooks/retrieve-webhook-batches /email-api/api-reference/openapi.yaml get /webhook-batch Retrieves paged webhook batches associated with the customer. The time range specified by created_after and created_before filters must not exceed 31 days. If neither is specified, the default time range is the last 3 days. Optional filters include status categories, webhook, limit and offset. # Retrieve Webhook Signing Key Source: https://docs.mailchannels.com/api-reference/webhooks/retrieve-webhook-signing-key /email-api/api-reference/openapi.yaml get /webhook/public-key Retrieves the public key used to verify signatures on incoming webhook payloads. # Validate Enrolled Webhook Source: https://docs.mailchannels.com/api-reference/webhooks/validate-enrolled-webhook /email-api/api-reference/openapi.yaml post /webhook/validate Validates whether your enrolled webhook(s) respond with an HTTP 2xx status code. Sends a test request to each webhook containing your customer handle, a hardcoded event type(test), a hardcoded sender email(test@mailchannels.com),a timestamp, a request ID (provided or generated), and an SMTP ID. The response includes the HTTP status code and body returned by each webhook. # Introduction Source: https://docs.mailchannels.com/email-api/api-reference-introduction View the HTTP-specific API reference for the Email API. This section provides in-depth information about the Email API. If you're new to the API, consider starting with the [Get Started](/email-api/curl/quickstart) guide, or [send vs send async](/email-api/send-vs-send-async). ## Base URL The base URL for API requests is `https://api.mailchannels.net/tx/v1`. All requests are made to this URL, and must use HTTPS. ## Authentication All requests to the Email API must contain the `X-Api-Key` header with your API key as the value. See the [Authentication](/email-api/authentication) section for more information about API keys and how to manage them. ## Troubleshooting For HTTP status codes and error responses, see the [Errors](/email-api/errors) section of the API reference. # Attachments Source: https://docs.mailchannels.com/email-api/attachments Learn how to attach files to your emails. ## Adding attachments The `attachments` array attaches files to your email. Each entry is an object with the MIME `type` of the file, the `filename` shown to the recipient, and `content` containing the file's bytes as a base64-encoded string. See [Embedding inline images](#embedding-inline-images) below for the optional `content_id` field. ```bash cURL theme={null} #!/usr/bin/env bash set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}" : "${FROM_EMAIL:?Set FROM_EMAIL (must be on a Domain-Lockdown-authorized domain)}" : "${TO_EMAIL:?Set TO_EMAIL}" : "${ATTACHMENT_PATH:?Set ATTACHMENT_PATH to the file you want to attach, e.g. ./logo.png}" # Base64-encode the file. The -w0 flag (GNU base64) emits a single line with no # wrapping; on macOS, drop -w0 — `base64` there doesn't wrap by default. ATTACHMENT_BASE64=$(base64 -w0 "$ATTACHMENT_PATH") ATTACHMENT_FILENAME=$(basename "$ATTACHMENT_PATH") curl -X POST https://api.mailchannels.net/tx/v1/send \ -H "Content-Type: application/json" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" \ -d @- <emails->send([ 'from' => ['email' => $from_email, 'name' => 'Your Name'], 'to' => ['email' => $to_email, 'name' => 'Recipient'], 'subject' => 'Hello from MailChannels', 'text' => 'Hello! See the attached file.', 'attachments' => [ [ 'content' => base64_encode($content), 'filename' => basename($attachment_path), 'type' => $mime, ], ], ]); print_r($response->toArray()); ``` ### Limits * Up to 1000 attachments per email. * 30 MB combined limit on attachments and email content. Messages that exceed this are rejected. ### Forbidden attachments The following MIME types and file extensions are blocked because they are commonly associated with malware. Sending an attachment with a forbidden type or extension returns an error. **MIME types** * `application/x-msdownload` * `application/vnd.ms-htmlhelp` * `application/java-archive` * `application/x-sh` * `application/x-shellscript` **File extensions** `.ade`, `.adp`, `.bat`, `.chm`, `.cmd`, `.com`, `.cpl`, `.exe`, `.hta`, `.ins`, `.isp`, `.jar`, `.jse`, `.lib`, `.lnk`, `.mde`, `.msc`, `.msp`, `.mst`, `.pif`, `.scr`, `.sct`, `.sh`, `.shb`, `.sys`, `.vb`, `.vbe`, `.vbs`, `.vxd`, `.wsc`, `.wsf`, `.wsh` ## Embedding inline images Setting `content_id` on an attachment embeds it inline in the message body instead of showing it as a downloadable attachment. Reference the image from HTML content with a `cid:` URI that matches the `content_id`, for example `` for an attachment with `content_id: "logo"`. ```bash cURL theme={null} #!/usr/bin/env bash set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}" : "${FROM_EMAIL:?Set FROM_EMAIL (must be on a Domain-Lockdown-authorized domain)}" : "${TO_EMAIL:?Set TO_EMAIL}" : "${LOGO_PATH:?Set LOGO_PATH to the image you want to embed, e.g. ./logo.png}" # Base64-encode the image. The -w0 flag (GNU base64) emits a single line with no # wrapping; on macOS, drop -w0 — `base64` there doesn't wrap by default. LOGO_BASE64=$(base64 -w0 "$LOGO_PATH") curl -X POST https://api.mailchannels.net/tx/v1/send \ -H "Content-Type: application/json" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" \ -d @- <Hello! Here's our logo:

" } ], "attachments": [ { "type": "image/png", "filename": "logo.png", "content": "$LOGO_BASE64", "content_id": "logo" } ] } JSON ``` ```javascript Node.js theme={null} import { readFile } from "node:fs/promises"; import { MailChannels } from "mailchannels-sdk"; const { MAILCHANNELS_API_KEY, FROM_EMAIL, TO_EMAIL, LOGO_PATH } = process.env; if (!MAILCHANNELS_API_KEY) throw new Error("Set MAILCHANNELS_API_KEY before running"); if (!FROM_EMAIL) throw new Error("Set FROM_EMAIL (must be on a Domain-Lockdown-authorized domain)"); if (!TO_EMAIL) throw new Error("Set TO_EMAIL"); if (!LOGO_PATH) throw new Error("Set LOGO_PATH to the image you want to embed, e.g. ./logo.png"); const logoBytes = await readFile(LOGO_PATH); const mailchannels = new MailChannels(MAILCHANNELS_API_KEY); // Setting content_id is what embeds the attachment inline: it's referenced from the HTML // body via the matching cid: URI, instead of showing up as a downloadable attachment. const { data, error } = await mailchannels.emails.send({ from: { email: FROM_EMAIL, name: "Your Name" }, to: { email: TO_EMAIL, name: "Recipient" }, subject: "Hello from MailChannels", html: `

Hello! Here's our logo:

`, attachments: [ { type: "image/png", filename: "logo.png", content: logoBytes.toString("base64"), contentId: "logo", }, ], }); if (error) { console.error("Send failed:", error); process.exit(1); } console.log(data); ``` ```python Python theme={null} import base64 import os import sys import mailchannels if not os.environ.get("MAILCHANNELS_API_KEY"): sys.exit("Set MAILCHANNELS_API_KEY before running") from_email = os.environ.get("FROM_EMAIL") to_email = os.environ.get("TO_EMAIL") logo_path = os.environ.get("LOGO_PATH") if not from_email: sys.exit("Set FROM_EMAIL (must be on a Domain-Lockdown-authorized domain)") if not to_email: sys.exit("Set TO_EMAIL") if not logo_path: sys.exit("Set LOGO_PATH to the image you want to embed, e.g. ./logo.png") with open(logo_path, "rb") as f: logo_base64 = base64.b64encode(f.read()).decode("ascii") # Setting content_id is what embeds the attachment inline: it's referenced from the HTML # body via the matching cid: URI, instead of showing up as a downloadable attachment. response = mailchannels.Emails.send( { "from": {"email": from_email, "name": "Your Name"}, "to": [{"email": to_email, "name": "Recipient"}], "subject": "Hello from MailChannels", "html": '

Hello! Here\'s our logo:

', "attachments": [ { "type": "image/png", "filename": "logo.png", "content": logo_base64, "content_id": "logo", } ], } ) print(response) ``` ```php PHP theme={null} emails->send([ 'from' => ['email' => $from_email, 'name' => 'Your Name'], 'to' => ['email' => $to_email, 'name' => 'Recipient'], 'subject' => 'Hello from MailChannels', 'html' => '

Hello! Here\'s our logo:

', 'attachments' => [ [ 'content' => base64_encode($logo), 'filename' => basename($logo_path), 'type' => mime_content_type($logo_path) ?: 'application/octet-stream', 'content_id' => 'logo', ], ], ]); print_r($response->toArray()); ```
`content_id` must be unique across all attachments in the request. Never send an attachment that you received from a user without first scanning it for viruses and malware. # Authentication Source: https://docs.mailchannels.com/email-api/authentication Learn how to create and manage API keys for your account and sub-accounts. ## What is an API key? An API key is a unique token that authenticates your requests to the MailChannels Email API. It serves two purposes: proving who you are, and determining what you are allowed to do. ## Authenticating a request Every request to the Email API must include your API key. The examples below send a minimal email and demonstrate how the key is set. ```bash cURL theme={null} #!/usr/bin/env bash set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}" : "${FROM_EMAIL:?Set FROM_EMAIL (must be on a Domain-Lockdown-authorized domain)}" : "${TO_EMAIL:?Set TO_EMAIL}" curl -X POST https://api.mailchannels.net/tx/v1/send \ -H "Content-Type: application/json" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" \ -d @- <emails->send([ 'from' => ['email' => $from_email, 'name' => 'Your Name'], 'to' => ['email' => $to_email, 'name' => 'Recipient'], 'subject' => 'Hello from MailChannels', 'text' => 'Hello! This is a plain-text email.', ]); print_r($response->toArray()); ``` Similar to a password, an API key grants access to your account. Store it securely and never commit it to source control or expose it in client-side code. ## API key management ### Creating an API key Visit the [API Keys page](https://dash.mailchannels.com/account/api-keys) in the Console to create a key for your account. 1. Click **Create API Key**. 2. Add a descriptive label. 3. Set the scope to `Sending Email`. 4. Click **Create API Key** and copy the key. You won't be able to see it again. The full key is shown exactly once at the moment it is created. The redacted version is available via the Console and API, but never the original key. There is no way to recover a key. ### Scope Scope determines what an API key is allowed to do. Scope is assigned when the key is created in the Console and cannot be changed afterwards. The API currently supports the following scopes: `Sending Email` (api) and `Managing Inbound Filtering` (inbound). If you create a key with `Sending Email`, you can access every endpoint specified in the [Email API reference](/email-api/api-reference-introduction). If you create a key with `Managing Inbound Filtering`, you have access to MailChannels Inbound Filtering. Inbound filtering blocks unwanted mail before it reaches your infrastructure. If you would like to learn more, view the [Inbound Filtering documentation](https://www.mailchannels.com/inbound/). ### Deleting an API key Delete an API key in the Console by clicking the **Revoke** button next to it. In the dialog that appears, click **Revoke API Key** to confirm. All subsequent requests that use the key will fail. ## Sub-account key management API keys for sub-accounts can be managed via the API. The parent account can create, list, and delete keys for each sub-account it owns. ### Creating a key ```bash cURL theme={null} #!/usr/bin/env bash set -u : "${PARENT_API_KEY:?Set PARENT_API_KEY before running}" : "${SUB_ACCOUNT_HANDLE:?Set SUB_ACCOUNT_HANDLE (the sub-account to create a key for)}" curl -X POST \ "https://api.mailchannels.net/tx/v1/sub-account/$SUB_ACCOUNT_HANDLE/api-key" \ -H "X-Api-Key: $PARENT_API_KEY" ``` ```javascript Node.js theme={null} import { MailChannels } from "mailchannels-sdk"; const { PARENT_API_KEY, SUB_ACCOUNT_HANDLE } = process.env; if (!PARENT_API_KEY) throw new Error("Set PARENT_API_KEY (a parent-account API key) before running"); if (!SUB_ACCOUNT_HANDLE) throw new Error("Set SUB_ACCOUNT_HANDLE (the sub-account to create a key for)"); const mailchannels = new MailChannels(PARENT_API_KEY); const { data, error } = await mailchannels.subAccounts.createApiKey(SUB_ACCOUNT_HANDLE); if (error) { console.error("Create sub-account API key failed:", error); process.exit(1); } // data.key is shown only once. Store it securely. console.log(data); ``` ```python Python theme={null} import os import sys import mailchannels parent_api_key = os.environ.get("PARENT_API_KEY") handle = os.environ.get("SUB_ACCOUNT_HANDLE") if not parent_api_key: sys.exit("Set PARENT_API_KEY (a parent-account API key) before running") if not handle: sys.exit("Set SUB_ACCOUNT_HANDLE (the sub-account to create a key for)") mailchannels.api_key = parent_api_key response = mailchannels.SubAccounts.ApiKeys.create(handle) print(response) ``` ```php PHP theme={null} subAccounts->apiKeys->create($sub_account_handle); print_r($response->toArray()); ``` ### Listing keys ```bash cURL theme={null} #!/usr/bin/env bash set -u : "${PARENT_API_KEY:?Set PARENT_API_KEY before running}" : "${SUB_ACCOUNT_HANDLE:?Set SUB_ACCOUNT_HANDLE (the sub-account whose keys you want to list)}" curl "https://api.mailchannels.net/tx/v1/sub-account/$SUB_ACCOUNT_HANDLE/api-key" \ -H "X-Api-Key: $PARENT_API_KEY" ``` ```javascript Node.js theme={null} import { MailChannels } from "mailchannels-sdk"; const { PARENT_API_KEY, SUB_ACCOUNT_HANDLE } = process.env; if (!PARENT_API_KEY) throw new Error("Set PARENT_API_KEY (a parent-account API key) before running"); if (!SUB_ACCOUNT_HANDLE) throw new Error("Set SUB_ACCOUNT_HANDLE (the sub-account whose keys you want to list)"); const mailchannels = new MailChannels(PARENT_API_KEY); const { data, error } = await mailchannels.subAccounts.listApiKeys(SUB_ACCOUNT_HANDLE); if (error) { console.error("List sub-account API keys failed:", error); process.exit(1); } console.log(data); ``` ```python Python theme={null} import os import sys import mailchannels parent_api_key = os.environ.get("PARENT_API_KEY") handle = os.environ.get("SUB_ACCOUNT_HANDLE") if not parent_api_key: sys.exit("Set PARENT_API_KEY (a parent-account API key) before running") if not handle: sys.exit("Set SUB_ACCOUNT_HANDLE (the sub-account whose keys you want to list)") mailchannels.api_key = parent_api_key response = mailchannels.SubAccounts.ApiKeys.list(handle) print(response) ``` ```php PHP theme={null} subAccounts->apiKeys->list($sub_account_handle); print_r($response->toArray()); ``` ### Deleting a key ```bash cURL theme={null} #!/usr/bin/env bash set -u : "${PARENT_API_KEY:?Set PARENT_API_KEY before running}" : "${SUB_ACCOUNT_HANDLE:?Set SUB_ACCOUNT_HANDLE}" : "${API_KEY_ID:?Set API_KEY_ID (the id of the key to delete)}" curl -X DELETE \ "https://api.mailchannels.net/tx/v1/sub-account/$SUB_ACCOUNT_HANDLE/api-key/$API_KEY_ID" \ -H "X-Api-Key: $PARENT_API_KEY" ``` ```javascript Node.js theme={null} import { MailChannels } from "mailchannels-sdk"; const { PARENT_API_KEY, SUB_ACCOUNT_HANDLE, API_KEY_ID } = process.env; if (!PARENT_API_KEY) throw new Error("Set PARENT_API_KEY (a parent-account API key) before running"); if (!SUB_ACCOUNT_HANDLE) throw new Error("Set SUB_ACCOUNT_HANDLE"); if (!API_KEY_ID) throw new Error("Set API_KEY_ID (the ID of the key to delete)"); const mailchannels = new MailChannels(PARENT_API_KEY); const { success, error } = await mailchannels.subAccounts.deleteApiKey( SUB_ACCOUNT_HANDLE, Number(API_KEY_ID), ); if (!success) { console.error("Delete sub-account API key failed:", error); process.exit(1); } console.log(`Deleted API key ${API_KEY_ID} for sub-account ${SUB_ACCOUNT_HANDLE}`); ``` ```python Python theme={null} import os import sys import mailchannels parent_api_key = os.environ.get("PARENT_API_KEY") handle = os.environ.get("SUB_ACCOUNT_HANDLE") key_id = os.environ.get("API_KEY_ID") if not parent_api_key: sys.exit("Set PARENT_API_KEY (a parent-account API key) before running") if not handle: sys.exit("Set SUB_ACCOUNT_HANDLE") if not key_id: sys.exit("Set API_KEY_ID (the id of the key to delete)") mailchannels.api_key = parent_api_key response = mailchannels.SubAccounts.ApiKeys.delete(handle, key_id) print(response) ``` ```php PHP theme={null} subAccounts->apiKeys->delete($sub_account_handle, $api_key_id); print_r($response->toArray()); ``` ## Key limits Each account can have 100 API keys. Each sub-account can have 100 API keys. Sub-account keys do not count towards the parent account's limit. Attempting to create another key past that limit returns an error. Delete an unused key before creating a new one. ## Best practices ### Storage Use a secrets manager or environment variable to store your API key, rather than hard-coding it in source code. This reduces the risk of accidental exposure. ### Rotation Regularly rotate your API keys to minimize the impact of a potential compromise. To rotate a key, create a new one, update your applications to use it, and then revoke the old key. ### One key per application Use a unique API key for each application or service that integrates with the Email API. Then, if a key is compromised, you can revoke it without affecting other applications. # MailChannels Email API: pricing, plans, and limits Source: https://docs.mailchannels.com/email-api/billing/pricing Understand Email API plans, volume tiers, send counting, usage limits, and billing for governed platform email. MailChannels Email API uses a straightforward volume-based pricing model for governed platform email for governed platform email. You start on a free developer plan with no credit card charge, and move to a paid tier when your sending volume grows. This page explains how emails are counted, what happens when you reach your limit, and how billing is handled so there are no surprises. ## Free developer plan The free plan lets you send up to **100 emails per day** at no cost. It is designed for testing, development, and low-volume application emailapplication email. The free plan is fully functional for API sending but does not include features like open and click tracking. Until you add a payment method, sending is restricted to addresses belonging to verified [users](https://dash.mailchannels.net/account/users) on your account. Add a user, click the link in the verification email, then use that address as the recipient. The 100-sends-per-day limit **resets at 00:00 UTC** each day, regardless of your time zone. ## Paid plans Paid plans use a **tier-based pricing model** based on the number of emails you send per month. You choose a plan that matches your expected monthly volume. * The **entry-level paid plan** covers up to **10,000 emails per month**. * Higher tiers are available for larger volumes, each with a fixed monthly price. * Annual billing discounts may apply. * All plans include the full set of core Email API features. There is currently no official paid tier between the free plan (\~100 emails/day) and the 10,000 emails/month plan. If you need more than the free tier allows, the next step up is the 10k/month plan. Visit the [MailChannels pricing page](https://www.mailchannels.com/pricing/#for_devs) to see current volume tiers and costs. ## How emails are counted MailChannels counts the number of **sends**, not the number of API calls. If you use the `personalizations` field to send one message to 100 different recipients in a single API call, that counts as **100 sends**,, not 1. Plan your volume estimates around the total number of recipient deliveries, not the number of requests you make to the API. ## What happens when you exceed your limit If you reach your daily limit (free plan) or monthly limit (paid plan), MailChannels **pauses further email delivery**. Subsequent API calls to send messages will fail. MailChannels never silently holds or drops messages that were already successfully submitted via the API. If a send call returned a success response before you hit the limit, that message will be delivered. When you hit your limit, you have two options: 1. **Wait** for your quota to reset at the start of the next billing cycle. 2. **Upgrade** to a higher volume plan to immediately increase your send limit. If you upgrade mid-cycle, MailChannels applies a **pro-rated charge** for the increased volume cap for the remainder of the month. Monitor your [usage](https://dash.mailchannels.net/overview). Upgrading before you hit the limit avoids any interruption to your critical emails. ## Unused quota does not roll over Unused emails from one billing cycle do not carry over to the next. Each month you receive a fresh allotment based on your plan. For example: if your plan allows 50,000 emails/month and you send only 30,000 this month, you still have 50,000 available next month,, not 70,000. ## Billing * Your card is charged at the **start of each billing cycle** (monthly by default, unless you are on annual billing). * If you upgrade mid-cycle, the additional charge is **pro-rated** for the remaining days in the month. * Payments are processed by a **PCI-DSS certified payment partner**.. MailChannels does not store your card number directly. * If a payment fails, MailChannels will notify you to update your payment details to avoid service interruption. * You can manage billing and view invoices on the [billing page](https://dash.mailchannels.net/billing). ## Rate limits and burst sending There is no fixed messages-per-second cap on the Email API. You can send at high throughput as long as you stay within your monthly quota and your traffic remains inside normal sender-risk patternsEmail API. You can send at high throughput as long as you stay within your monthly quota and your traffic remains inside normal sender-risk patterns. However, if your account suddenly sends an unusually large burst, especially on a small or free plan, the anti-abuse system may temporarily rate-limit your sending speed to protect shared reputation and, especially on a small or free plan, the anti-abuse system may temporarily rate-limit your sending speed to protect shared reputation and system health. This is only triggered by abnormal patterns, such as a compromised system attempting to send millions of messages in a short window. For normal use cases, your monthly volume quota is the primary constraint. If you have a use case requiring very high burst rates, contact MailChannels in advance or spread the send out over time. ## Click tracking URLs When you enable click tracking for outbound emails, MailChannels automatically replaces HTTP and HTTPS links in your message body with custom tracking URLs. These URLs let you monitor recipient engagement and track click events in real time. MailChannels uses a stateless encoding system for tracking links. The tracking URL has this structure: ```text theme={null} https://[BASE_DOMAIN]/[CUSTOMER_HASH]/[SID_HASH]/[ENCODED_TOKEN]?v=2 ``` * **BASE\_DOMAIN** -- the MailChannels click-tracking service domain, e.g. `click.mailchannels.net`. * **CUSTOMER\_HASH** and **SID\_HASH** -- SHA-256 hashes of your customer handle and Sender ID, used for routing without exposing your account details. * **ENCODED\_TOKEN** -- a URL-safe Base64-encoded package containing the destination URL, the Message ID, and an HMAC-SHA256 digital signature. * **?v=2** -- tells MailChannels servers to decode the token inline. Because the destination URL and Message ID are embedded directly in the token and signed with HMAC-SHA256, there is no database lookup on click. R. Redirects are fast and reliable. Tampered URLs are automatically rejected: if even a single character of the destination is changed, the signature check fails and the server returns a `404 Not Found`. The system also supports key rotation via the Key ID field, so MailChannels can update signing keys without breaking links already sent to recipients. Click tracking is not available on the free developer plan. Upgrade to a paid plan to enable it. # Handling complained events Source: https://docs.mailchannels.com/email-api/complained-events Learn about the best practices for responding to complained webhook events. ## What is a complained event? A complained event is generated when a recipient files a spam complaint about an email message. This indicates that the recipient considers the email to be unwanted or unsolicited. The recipient's email address is automatically added to your suppression list so you won't accidentally send mail to them again, but you should remove the address from your mailing list as well. For more information, see [suppressions and unsubscribe handling](/email-api/suppressions-unsubscribes). ## What to do if you receive a complained event If you receive a complained event, you should remove the recipient's email address from your mailing list to avoid sending to them again in the future. If you receive a large number of complaints, you should investigate the cause and take action to reduce your complaint rate. This could involve improving the quality of your email content, ensuring that you have proper permission to email your recipients, or making it easier for recipients to unsubscribe from your emails. If you receive a large number of complaints, your sending may be restricted or your account may be suspended. It's important to monitor your complaint rate and take action to reduce it if necessary. ## Fields for complained events * `email`: Sender email address. * `customer_handle`: Your account ID. * `timestamp`: Unix timestamp of the event. * `event`: The type of event, `complained` in this case. * `recipients`: List of recipient email addresses that filed complaints. * `campaign_id` *(optional)*: The campaign ID associated with the message, if applicable. * `request_id` *(optional)*: A unique identifier generated to track the original HTTP request. This will be blank if the message is sent via SMTP instead of the API. * `smtp_id`: A unique identifier used to track the message, matching the `Message-Id` header. ## Example complained event ```json theme={null} { "email": "sender@example.com", "customer_handle": "examplecorp", "timestamp": 1700000000, "event": "complained", "recipients": [ "recipient@example.net" ], "campaign_id": "spring-sale-2026", "request_id": "4b06083a23724f279c8d11a370e641f8", "smtp_id": "" } ``` # DKIM signing and key management Source: https://docs.mailchannels.com/email-api/configuring-dkim MailChannels manages DKIM signing and key rotation for you. ## DKIM You can choose to manage your own keys, or let MailChannels manage your keys for you. If you choose to manage your own keys, you are responsible for creating the key pair, publishing the DNS record, and rotating keys when necessary. If you choose to let MailChannels manage your keys, we manage the keys, and all you have to do is update the DNS. **If you want to manage your own keys:** include `dkim_domain`, `dkim_selector` and `dkim_private_key` in your API request to send a message. **If you want MailChannels to manage your keys:** MailChannels generates and stores the private key, and handles the signing process. ### Create a key pair Generate a key pair for your domain. `selector` is a string that identifies the key and is used in the DNS record and email headers. `algorithm` defaults to `rsa` and `key_length` defaults to `2048`. Choose a short, descriptive selector that helps you identify the key, for example "marketing2026" or "transactional20260101". ```bash cURL theme={null} #!/usr/bin/env bash # Create a DKIM key pair managed by MailChannels. The response includes the # public key and the DNS TXT record to publish. set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}" : "${DOMAIN:?Set DOMAIN (the sending domain, e.g. example.com)}" : "${SELECTOR:?Set SELECTOR (e.g. mc1)}" curl -X POST "https://api.mailchannels.net/tx/v1/domains/$DOMAIN/dkim-keys" \ -H "Content-Type: application/json" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" \ -d @- <dkim->create( domain: $domain, selector: $selector, algorithm: 'rsa', keyLength: 2048, ); print_r($response->toArray()); ``` The response includes the suggested DNS record under `dkim_dns_records`: ```json Response theme={null} { "domain": "example.com", "selector": "mc1", "public_key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...", "status": "active", "algorithm": "rsa", "key_length": 2048, "created_at": "2026-05-13T18:30:00Z", "dkim_dns_records": [ { "name": "mc1._domainkey.example.com", "type": "TXT", "value": "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A..." } ] } ``` ### Publish the DNS record Add the returned TXT record to your domain's DNS. Receivers fetch this record to verify the DKIM signatures on your mail. Here is an example record: DNS Name: `mc1._domainkey.example.com` TXT Record value: `v=DKIM1; k=rsa; p=public_key_value` The format of the record is specified in [RFC 6376, section 3.6.1](https://datatracker.ietf.org/doc/html/rfc6376#section-3.6.1). ### Send a message using the managed key Once you have created a key and published the DNS record, your mail will automatically be signed using that key. If you have multiple keys and want to specify which one to use, include `dkim_selector` in your send request. See the [API reference](/api-reference/send/send-an-email) for more details. ```bash cURL theme={null} #!/usr/bin/env bash # Send a message signed with a MailChannels-managed DKIM key. Optionally set # dkim_selector; the private key stays with MailChannels. set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}" : "${FROM_EMAIL:?Set FROM_EMAIL (must be on a Domain-Lockdown-authorized domain)}" : "${TO_EMAIL:?Set TO_EMAIL}" : "${DKIM_SELECTOR:?Set DKIM_SELECTOR (the selector of an active key)}" curl -X POST https://api.mailchannels.net/tx/v1/send \ -H "Content-Type: application/json" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" \ -d @- <emails->send([ 'from' => ['email' => $from_email], 'to' => ['email' => $to_email], 'subject' => 'Hello from MailChannels', 'text' => 'Signed with a MailChannels-managed DKIM key.', 'dkim_selector' => $dkim_selector, ]); print_r($response->toArray()); ``` ### Rotate a key Rotating a key refers to the process of creating a new key, updating your DNS records, and retiring the old key. This is a security best practice that limits the potential damage if a private key is compromised. MailChannels provides a tool to rotate your DKIM keys. When you rotate a key, a new key pair is generated, and the old key is marked as `rotated`. ```bash cURL theme={null} #!/usr/bin/env bash # Rotate an active DKIM key. set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}" : "${DOMAIN:?Set DOMAIN (the sending domain, e.g. example.com)}" : "${CURRENT_SELECTOR:?Set CURRENT_SELECTOR (the active key to rotate out)}" : "${NEW_SELECTOR:?Set NEW_SELECTOR (the selector for the new active key)}" curl -X POST "https://api.mailchannels.net/tx/v1/domains/$DOMAIN/dkim-keys/$CURRENT_SELECTOR/rotate" \ -H "Content-Type: application/json" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" \ -d @- <dkim->rotate($domain, $current_selector, $new_selector); print_r($response->toArray()); ``` The response contains both keys, each with its suggested DNS record: ```json Response theme={null} { "new_key": { "domain": "example.com", "selector": "mc2", "public_key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...new...", "status": "active", "algorithm": "rsa", "key_length": 2048, "created_at": "2026-05-13T18:30:00Z", "dkim_dns_records": [ { "name": "mc2._domainkey.example.com", "type": "TXT", "value": "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...new..." } ] }, "rotated_key": { "domain": "example.com", "selector": "mc1", "public_key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...", "status": "rotated", "algorithm": "rsa", "key_length": 2048, "status_modified_at": "2026-05-13T18:30:00Z", "gracePeriodExpiresAt": "2026-05-16T18:30:00Z", "retiresAt": "2026-05-27T18:30:00Z", "dkim_dns_records": [ { "name": "mc1._domainkey.example.com", "type": "TXT", "value": "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A..." } ] } } ``` The rotated key remains valid for signing for a **3-day grace period** (`gracePeriodExpiresAt`) and is automatically marked `retired` 2 weeks after rotation (`retiresAt`). To avoid signature-verification failures during the cutover: 1. Publish the new key's DNS record **before** updating your send requests to use the new selector. 2. Leave the rotated key's DNS record in place until the grace period ends — in-flight mail signed with it still needs the record for verification. 3. Once the grace period ends, update your send requests to use the new selector. Signing with the old selector will fail after it transitions to `retired`. # Content types Source: https://docs.mailchannels.com/email-api/content-types Learn about the content types you can use in your email body and how to use them effectively. ## Creating the email body The `content` array contains the body of your email. Each entry is a `{type, value}` object, where `type` is a [MIME](https://datatracker.ietf.org/doc/html/rfc2045) content type and `value` is the body for that type. When you supply more than one entry, MailChannels encodes the message as `multipart/alternative`. ### Plain text and HTML Every message that includes a `text/html` part should include a `text/plain` part as well, with the plain text part first. ```bash cURL theme={null} #!/usr/bin/env bash set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}" : "${FROM_EMAIL:?Set FROM_EMAIL (must be on a Domain-Lockdown-authorized domain)}" : "${TO_EMAIL:?Set TO_EMAIL}" curl -X POST https://api.mailchannels.net/tx/v1/send \ -H "Content-Type: application/json" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" \ -d @- <Hello from MailChannels.

" } ] } JSON ``` ```javascript Node.js theme={null} import { MailChannels } from "mailchannels-sdk"; const { MAILCHANNELS_API_KEY, FROM_EMAIL, TO_EMAIL } = process.env; if (!MAILCHANNELS_API_KEY) throw new Error("Set MAILCHANNELS_API_KEY before running"); if (!FROM_EMAIL) throw new Error("Set FROM_EMAIL (must be on a Domain-Lockdown-authorized domain)"); if (!TO_EMAIL) throw new Error("Set TO_EMAIL"); const mailchannels = new MailChannels(MAILCHANNELS_API_KEY); const { data, error } = await mailchannels.emails.send({ from: { email: FROM_EMAIL, name: "Your Name" }, to: { email: TO_EMAIL, name: "Recipient" }, subject: "Hello from MailChannels", text: "Hello from MailChannels.", html: "

Hello from MailChannels.

", }); if (error) { console.error("Send failed:", error); process.exit(1); } console.log(data); ``` ```python Python theme={null} import os import sys import mailchannels if not os.environ.get("MAILCHANNELS_API_KEY"): sys.exit("Set MAILCHANNELS_API_KEY before running") from_email = os.environ.get("FROM_EMAIL") to_email = os.environ.get("TO_EMAIL") if not from_email: sys.exit("Set FROM_EMAIL (must be on a Domain-Lockdown-authorized domain)") if not to_email: sys.exit("Set TO_EMAIL") # Passing both text and html sends a multipart/alternative message; clients # pick the best part they can render. response = mailchannels.Emails.send( { "from": {"email": from_email, "name": "Your Name"}, "to": [{"email": to_email, "name": "Recipient"}], "subject": "Hello from MailChannels", "text": "Hello from MailChannels.", "html": "

Hello from MailChannels.

", } ) print(response) ``` ```php PHP theme={null} emails->send([ 'from' => ['email' => $from_email, 'name' => 'Your Name'], 'to' => ['email' => $to_email, 'name' => 'Recipient'], 'subject' => 'Hello from MailChannels', 'text' => 'Hello from MailChannels.', 'html' => '

Hello from MailChannels.

', ]); print_r($response->toArray()); ```
Significant differences between the plain text and HTML versions hurt deliverability. Make sure the plain text version is the same message as the HTML version, just without formatting. It should contain all the same text, links, and images. ### Supported MIME types The following types are generally readable in most email clients: * `text/plain` * `text/html` * `text/richtext` * `text/css` When multiple parts are present, the rendered body orders them as `text/plain`, then `text/html`, then any other types. ### Prohibited MIME types The following types are blocked because they are frequently associated with malware delivery. Including them in `content` returns an error: * `application/x-msdownload` * `application/vnd.ms-htmlhelp` * `application/java-archive` * `application/x-sh` * `application/x-shellscript` Other MIME types may be blocked from time to time when our abuse-prevention systems detect ongoing abuse involving that type. # cURL quickstart Source: https://docs.mailchannels.com/email-api/curl/quickstart Send an email with the MailChannels Email API using cURL. Send an email in a few minutes. This guide walks through sending your first request to the MailChannels Email API. ## Prerequisites Before your first request, complete the following steps. [Sign up](https://dash.mailchannels.com) if you don't already have an account. Open [Account > API Keys](https://dash.mailchannels.com/account/api-keys) and click **Create API Key**. Add a descriptive label and set the scope to `api`. Copy the key; you won't be able to see it again. Treat your API key like a password. Don't commit it to source control or expose it in client-side code. For each domain you'll send from, add a `TXT` record on the `_mailchannels` subdomain authorizing your account to send. Replace `examplecorp` with your MailChannels account ID (visible in the panel footer when you log in): ``` _mailchannels.example.com TXT "v=mc1 auth=examplecorp" ``` See the [Domain Lockdown guide](/email-api/domain-lockdown) for details. Add MailChannels to the SPF record on each sending domain so recipient servers accept your mail: ``` example.com TXT "v=spf1 include:relay.mailchannels.net ~all" ``` If you already have an SPF record, merge in `include:relay.mailchannels.net` rather than creating a second record. A domain must have only one SPF record. Your sending domain must resolve. It needs a valid `A` or `MX` record. Without one, mail from the domain is rejected with `550 5.1.2 [SDNF] Sender Domain Not Found`. Most registered domains already have these records. Confirm yours with the [check-domain tool](https://dash.mailchannels.com/domain-health). See [Troubleshooting](/email-api/troubleshooting) for details. ## Install the cURL SDK ```bash cURL theme={null} # cURL ships with macOS and most Linux distributions. If it's missing, # install it with your platform's package manager: # macOS (Homebrew) brew install curl # Debian / Ubuntu sudo apt-get install curl # Windows (winget) — note: curl ships with Windows 10 (1803+) and Windows 11 winget install cURL.cURL # Verify the install curl --version ``` ## Send your first email To send an email, provide the recipients, sender, subject, and message content. The example below sends an HTML email to a single recipient. Until you add a payment method, sending is restricted to addresses belonging to verified [Users](https://dash.mailchannels.com/account/users) **on your account**. Add a user, click the link in the verification email, then use that address as the `to` email below. ```bash cURL theme={null} #!/usr/bin/env bash # Check that the required variables are set. This is just a sanity check for # this example script set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}" : "${FROM_EMAIL:?Set FROM_EMAIL (must be on a Domain-Lockdown-authorized domain)}" : "${TO_EMAIL:?Set TO_EMAIL}" curl -X POST https://api.mailchannels.net/tx/v1/send \ -H "Content-Type: application/json" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" \ -d @- <Hello,

This is my first HTML email sent via the MailChannels Email API.

" } ] } JSON ``` Set the following before running: * `MAILCHANNELS_API_KEY` — the API key generated above. * `FROM_EMAIL` — an address on the domain you configured with Domain Lockdown and SPF above (e.g. `hello@example.com` if you used `example.com` in the Domain Lockdown and SPF DNS records). * `TO_EMAIL` — a recipient inbox you can check, e.g. the address you use to sign up for MailChannels. ## Next steps * Read the [webhooks guide](/email-api/webhooks) to learn how to get real-time notifications about email delivery, bounces, and complaints. * Explore the [API reference](/email-api/api-reference-introduction) to see what else you can do with the API. # Custom headers Source: https://docs.mailchannels.com/email-api/custom-headers Learn how to add custom headers to your emails. ## What are custom headers? Custom headers let you attach RFC 5322 compliant headers to your messages. This is useful for message classification, tracking, and per-recipient identifiers. Some headers are restricted to prevent abuse, and MailChannels may limit how many times the same header can be repeated. See [Restricted headers](#restricted-headers) for a non-exhaustive list. ### Adding custom headers Include custom headers in the top-level `headers` object to apply them to every recipient of the message: ```bash cURL theme={null} #!/usr/bin/env bash set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}" : "${FROM_EMAIL:?Set FROM_EMAIL (must be on a Domain-Lockdown-authorized domain)}" : "${TO_EMAIL:?Set TO_EMAIL}" curl -X POST https://api.mailchannels.net/tx/v1/send \ -H "Content-Type: application/json" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" \ -d @- <emails->send([ 'from' => ['email' => $from_email, 'name' => 'Your Name'], 'to' => ['email' => $to_email, 'name' => 'Recipient'], 'subject' => 'Hello from MailChannels', 'text' => 'Hello from MailChannels.', 'headers' => [ 'X-Campaign-Id' => 'welcome-2026', ], ]); print_r($response->toArray()); ``` Using custom headers incorrectly could significantly harm your email deliverability. ### Custom headers within personalizations Custom headers can also be added to individual personalizations, allowing for recipient-specific headers. This is useful for personalized unsubscribe links, tracking, or identification headers that vary per recipient. ```bash cURL theme={null} #!/usr/bin/env bash set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}" : "${FROM_EMAIL:?Set FROM_EMAIL (must be on a Domain-Lockdown-authorized domain)}" : "${TO_EMAIL_1:?Set TO_EMAIL_1}" : "${TO_EMAIL_2:?Set TO_EMAIL_2}" curl -X POST https://api.mailchannels.net/tx/v1/send \ -H "Content-Type: application/json" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" \ -d @- <emails->send([ 'from' => ['email' => $from_email, 'name' => 'Your Name'], 'personalizations' => [ [ 'to' => [['email' => $to_email_1]], 'headers' => ['X-Campaign-Id' => 'welcome-2026-cohort-a'], ], [ 'to' => [['email' => $to_email_2]], 'headers' => ['X-Campaign-Id' => 'welcome-2026-cohort-b'], ], ], 'subject' => 'Hello from MailChannels', 'text' => 'Hello from MailChannels.', ]); print_r($response->toArray()); ``` When a header is defined both globally and in a personalization object, the value in the personalization object is used. ## Restricted headers The following names cannot be used as keys in the `headers` JSON object. This list is non-exhaustive. * Authentication-Results * BCC * CC * Content-Transfer-Encoding * Content-Type * DKIM-Signature * From * Message-ID * Received * Reply-To * Subject * To # Custom tracking Source: https://docs.mailchannels.com/email-api/custom-tracking Route click tracking, open tracking, and unsubscribe links through your own custom domain to reinforce your brand and improve email deliverability. ## What is custom tracking? By default, MailChannels uses shared domains for click-tracking links, open-tracking pixels, and unsubscribe URLs embedded in your emails. A custom tracking domain replaces those shared domains with one you own (e.g., `click.example.com`), improving brand consistency and deliverability. Each custom tracking domain is defined by three user-configurable fields: * **`hostname`** — the subdomain you control, e.g., `click.example.com`. * **`scope`** — the event type it handles: `click`, `open`, or `unsubscribe`. * **`name`** — a unique short label used to select this domain at send time (e.g., `clickdemo`). Custom tracking domains are not available on the free developer plan. Upgrade to a paid plan to enable it. ## Registering a custom tracking domain Provide the `hostname`, `scope`, and a unique `name` for the domain. ```bash cURL theme={null} #!/usr/bin/env bash # Register a custom click-tracking domain. set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}" curl -X POST "https://api.mailchannels.net/tx/v1/custom-tracking-domains" \ -H "Content-Type: application/json" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" \ -d @- < If the domain has not been verified yet, the API returns the DNS records you need to add. The domain is not active at this stage. ```json theme={null} { "token": "550e8400-e29b-41d4-a716-446655440000", "txt_record_name": "_mailchannels-verify.click.example.com", "txt_record_value": "550e8400-e29b-41d4-a716-446655440000", "instructions": "Add a DNS TXT record: _mailchannels-verify.click.example.com → \"550e8400-e29b-41d4-a716-446655440000\" and a CNAME record: click.example.com → links.mailchannels.net, then retry this request." } ``` Add both records to your domain's DNS before retrying: ```plaintext theme={null} _mailchannels-verify.click.example.com TXT 550e8400-e29b-41d4-a716-446655440000 click.example.com CNAME links.mailchannels.net ``` The TXT record proves you own the hostname. The CNAME record routes tracking traffic to MailChannels infrastructure. Once both records have propagated, call the registration endpoint again with the same request body. If verification succeeds, the API returns the domain in the `active` status. If either record is not yet visible to MailChannels, the response includes an `instructions` field describing which record is still missing. Wait for DNS propagation and retry. ## Sending with custom tracking Reference the custom tracking domain by its `name` in `tracking_settings` or `unsubscribe_settings` when sending a message. The domain must have `active` status. A full send example: ```bash cURL theme={null} #!/usr/bin/env bash # Send an email with the custom click-tracking, open-tracking, and unsubscribe-handling domains. set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}" : "${FROM_EMAIL:?Set FROM_EMAIL (must be on a Domain-Lockdown-authorized domain)}" : "${TO_EMAIL:?Set TO_EMAIL}" curl -X POST "https://api.mailchannels.net/tx/v1/send" \ -H "Content-Type: application/json" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" \ -d @- <Hello! Visit our sale page.

" } ], "tracking_settings": { "click_tracking": { "enable": true, "custom_domain_name": "clickdemo" }, "open_tracking": { "enable": true, "custom_domain_name": "opendemo" }, }, "unsubscribe_settings": { "custom_domain_name": "unsubscribedemo" } } JSON ```
## Managing custom tracking domains ### Listing domains Retrieve all registered domains for your account. Optional filters include `name`, `status`, and `scope`. ```bash cURL theme={null} #!/usr/bin/env bash # Retrieve custom tracking domains for the account. set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY (parent or sub-account) before running}" curl -X GET "https://api.mailchannels.net/tx/v1/custom-tracking-domains" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" ``` ### Updating a domain Rename a domain or toggle its status between `active` and `disabled`. Reactivating a disabled domain triggers DNS re-verification. Both the TXT ownership record and the CNAME record must be in place. If either is missing, the response includes an `instructions` field describing what to add. ```bash cURL theme={null} #!/usr/bin/env bash # Update a custom tracking domain. set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}" : "${TRACKING_DOMAIN_HOSTNAME:?Set TRACKING_DOMAIN_HOSTNAME to the hostname of the custom tracking domain to be updated}" : "${SCOPE:?Set SCOPE to the scope of the custom tracking domain to be updated}" curl -X PATCH "https://api.mailchannels.net/tx/v1/custom-tracking-domains/$TRACKING_DOMAIN_HOSTNAME/$SCOPE" \ -H "Content-Type: application/json" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" \ -d @- < ### Deleting a domain Delete a domain by its hostname and scope. The same hostname and scope can be re-registered if needed. Any tracking links or unsubscribe URLs in previously sent emails that used a deleted domain will stop working immediately. ```bash cURL theme={null} #!/usr/bin/env bash # Delete a custom tracking domain permanently. set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}" : "${TRACKING_DOMAIN_HOSTNAME:?Set TRACKING_DOMAIN_HOSTNAME to the hostname of the custom tracking domain to be removed}" : "${SCOPE:?Set SCOPE to the scope of the custom tracking domain to be removed}" curl -X DELETE "https://api.mailchannels.net/tx/v1/custom-tracking-domains/$TRACKING_DOMAIN_HOSTNAME/$SCOPE" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" \ ``` # Debug a sent message Source: https://docs.mailchannels.com/email-api/debug-sent-message Use API responses, webhook events, and application logs to answer what happened to a MailChannels message. Use this workflow when a recipient reports a missing message, a webhook event is unexpected, or your application needs to reconcile delivery state. ## Capture the identifiers Store these values when you send a message: | Identifier | Where it comes from | Why it matters | | - | - | - | | `request_id` | API response and webhook payloads | Connects the send request to later delivery events. | | `smtp_id` | Webhook payloads when present | Helps correlate SMTP and API delivery records. | | Sender and recipient | Request payload | Narrows the investigation to a domain, mailbox, or user. | ## Follow the event sequence 1. Confirm the API returned a successful response for `send` or `send-async`. 2. Search your stored webhook events by `request_id`. 3. Confirm a `processed` event exists. 4. Look for an event such as `delivered`, `hard-bounced`, `soft-bounced`, `dropped`, or `complained`. 5. If the recipient says the message is missing after a `delivered` event, ask them to check spam, quarantine, forwarding rules, and mailbox-level filters. `delivered` means the receiving mail system accepted the message. It does not guarantee inbox placement or that the recipient saw the message. ## Missing webhook events If the API returned a successful response for `send` or `send-async`, but your system did not record a webhook event: * Confirm the webhook endpoint is still enrolled. * Run webhook validation from the Console or the API. * Check whether your endpoint returned a non-2xx response, timed out, or rejected the request before storing the raw payload. * Query webhook batches for recent failed delivery attempts. * Use [batch resend](/email-api/webhook-retries) after you fix the receiver. ## Recipient says the message was not received Collect this information before escalating: * Sender address and recipient address. * Approximate send time with time zone. * API `request_id`. * Last webhook event and timestamp. * Whether the recipient checked spam, junk, quarantine, forwarding, and mailbox rules. * Any bounce or complaint event. Review [errors](/email-api/errors) for more request troubleshooting. # Domain Lockdown Source: https://docs.mailchannels.com/email-api/domain-lockdown Learn how to set up your DNS record for Domain Lockdown. ## What is Domain Lockdown? Domain Lockdown is a DNS-based security feature that tells MailChannels which accounts or sender identities may send email from your domain. MailChannels blocks mail when the authenticated account or sender identity is not listed in your DNS record. For each domain you send from, create a `TXT` record at the `_mailchannels` subdomain: ```text theme={null} _mailchannels.example.com TXT v=mc1 auth=examplecorp ``` This record allows the MailChannels account `examplecorp` to send mail from `example.com`. ### Record format A Domain Lockdown record is a space-separated list of fields: ```text theme={null} _mailchannels.example.com TXT v=mc1 auth=examplecorp senderid=examplecorp|x-authsender|billing@example.com sidw=examplecorp|x-authsender|billing*@example.com ``` * `v=mc1` identifies the Domain Lockdown record version. Always include this field. * `auth=` allows a MailChannels account or sub-account ID to send from the domain. * `senderid=` allows one exact sender ID. * `sidw=` allows one sender ID pattern with a limited wildcard `*`. You must include at least one `auth`, `senderid`, or `sidw` field to specify the authorized account or sender. You may include more than one `auth`, `senderid`, or `sidw` field in the same record. A message is allowed if any field matches. Domain Lockdown by Cloudflare Worker ID (cfid) is no longer supported as of August 31, 2024. ### Finding your account ID and sender ID Your MailChannels account ID is shown in the [panel footer](https://dash.mailchannels.com) when you log in. Sub-account IDs are listed on the [sub-accounts page](https://dash.mailchannels.com/sub-accounts). To find a sender ID, check the headers of a message sent through MailChannels: ```text theme={null} X-MailChannels-SenderId: examplecorp|x-authsender|billing@example.com X-MailChannels-Auth-Id: examplecorp Content-Type: text/plain; charset="utf-8" Date: Mon, 22 Aug 2022 14:15:57 -0500 From: billing@example.com To: customer@example.net Subject: Your invoice is ready ``` * Use the `X-MailChannels-Auth-Id` value in an `auth=` field. Note that messages sent by a sub-account will show the sub-account ID here, not the parent. * Use the `X-MailChannels-SenderId` value in a `senderid=` field, or as a reference when writing a `sidw=` pattern. ### Choosing a lockdown method Use `auth` when every sender under a MailChannels account should be allowed to send from the domain. ```text theme={null} _mailchannels.example.com TXT v=mc1 auth=examplecorp ``` To authorize a sub-account to also send from the domain: ```text theme={null} _mailchannels.example.com TXT v=mc1 auth=examplecorp auth=examplecorp_sub ``` This is the simplest setup and works well when a small number of accounts control all authorized sending for the domain. Use `senderid` when only specific sender identities should be allowed. ```text theme={null} _mailchannels.example.com TXT v=mc1 senderid=examplecorp|x-authsender|billing@example.com senderid=examplecorp|x-authsender|alerts@example.com ``` Messages from the same MailChannels account with any other sender ID are blocked by this record. Use `sidw` to allow a controlled group of sender IDs without listing every address individually. ```text theme={null} _mailchannels.example.com TXT v=mc1 sidw=examplecorp|x-authsender|*.head@example.com ``` This is useful for hosting providers, agencies, or platforms where multiple customers send through the same MailChannels account and `auth` would be too broad. ### Sender ID wildcard The `sidw` field supports one `*` wildcard in the sender part of the sender ID. The wildcard matches any number of characters. ```text theme={null} _mailchannels.example.com TXT v=mc1 sidw=examplecorp|x-authsender|billing*@example.com ``` This allows sender IDs such as: ```text theme={null} examplecorp|x-authsender|billing.no-reply@example.com examplecorp|x-authsender|billing.alerts@example.com examplecorp|x-authsender|billing@example.com ``` `sidw` patterns must follow these rules: * **Format**: exactly three pipe-separated parts: `||`. * **Pipe character**: `|` is always a separator. Do not include a literal `|` in the sender part; use `*` to match across that portion instead. * **Constraint**: edge cases such as `|mailer-daemon||NOTNDR` are not supported. * **Wildcard**: one wildcard is permitted only in the sender part. A standalone wildcard, such as `examplecorp|x-authsender|*`, is prohibited. Invalid `sidw` patterns are skipped. Use [Testing Your Setup](#testing-your-setup) to confirm that your record matches the sender IDs you expect. #### Escaping in `sidw` patterns In the sender part of a `sidw` pattern, `*` is a wildcard. To match a literal asterisk, write `\*`. To match a literal backslash, write `\\`. For example, this record matches `examplecorp|x-authsender|*depart\billing@example.com` and `examplecorp|x-authsender|*depart\alerts@example.com`: ```text theme={null} _mailchannels.example.com TXT v=mc1 sidw=examplecorp|x-authsender|\*depart\\*@example.com ``` Escaping any other character, such as `\a`, makes the `sidw` pattern invalid. Some DNS providers process backslashes before publishing the TXT record. After creating the record, verify the value DNS returns: ```bash theme={null} resolvectl query --type=TXT _mailchannels.example.com ``` MailChannels evaluates the published TXT value, so adjust your input until DNS returns the intended record. ### Testing your setup After publishing the DNS record, confirm the expected TXT value is visible: Use the [check-domain tool](https://dash.mailchannels.com/domain-health), or use the check-domain API to verify: ```bash cURL theme={null} #!/usr/bin/env bash # Check that a domain's DKIM, SPF, and Domain Lockdown records are configured # correctly and have propagated. set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}" : "${DOMAIN:?Set DOMAIN (the sending domain, e.g. example.com)}" curl -X POST "https://api.mailchannels.net/tx/v1/check-domain" \ -H "Content-Type: application/json" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" \ -d @- <checkDomain->check($domain); print_r($response->toArray()); ``` #### Troubleshooting invalid `sidw` patterns Use this table for reference when a `sidw` rule is present in DNS but does not match the sender ID you expect. Invalid `sidw` patterns are skipped while MailChannels continues evaluating any other fields in the record. | Invalid pattern | Why it is skipped | | - | - | | sidw=\*\|x-authsender\|sender\*@example.com | Wildcard `*` is allowed only in the sender part. | | sidw=examplecorp\|x-authsender\|\* | The wildcard cannot be the entire sender part. | | sidw=examplecorp\|x-authsender\|\*sender\*@example.com | More than one wildcard is present. | | sidw=examplecorp\|x-authsender\|sender\|\*@example.com | The extra \| creates a fourth part. `sidw` must have exactly three pipe-separated parts. | | sidw=examplecorp\|x-authsender\|se\nder\*@example.com | `\n` is not a valid escape sequence. In the sender part, only `\*` and `\\` are valid escapes. | ### Best practices * Start with the narrowest rule that matches your sending model: `auth` for one trusted account, `senderid` or `sidw` for shared sending environments. * Keep records simple and prefer a few clear fields over broad wildcard patterns. * Review Domain Lockdown records when you add new sending systems, customer domains, or sender ID formats. * Remove old `auth`, `senderid`, and `sidw` fields when they are no longer needed. # Email API vs SMTP Source: https://docs.mailchannels.com/email-api/email-api-vs-smtp Learn the differences between the Email API and SMTP, and when to choose each. ## Two ways to send MailChannels gives you two ways to hand off your mail: the HTTP Email API and SMTP. Both deliver through the same infrastructure and reach the same inboxes. The difference is how your application connects. The Email API is an HTTPS interface: your application sends a single request with a JSON body describing the message. SMTP is the long-established mail protocol: you point an existing mail server or SMTP client at MailChannels and it relays your mail. ## How they compare | | Email API | SMTP | | - | - | - | | How you integrate | Send an HTTPS request with a JSON body | Point an existing mail server or SMTP client at MailChannels | | Connection | HTTPS request to the Email API at `api.mailchannels.net` | Relay through `smtp.mailchannels.net` using SMTP | | Authentication | API key sent in the `X-Api-Key` header | Account ID and SMTP password using SMTP AUTH | | DKIM signing | Managed DKIM signing available — MailChannels signs on your behalf once configured | MailChannels does not sign; your own DKIM signatures are preserved | | Delivery events | Delivery, bounce, and engagement webhooks | abuse notifications webhooks | Both methods require the same DNS setup on every sending domain: a [Domain Lockdown](/email-api/domain-lockdown) record and an [SPF](/email-api/spf-dkim-dmarc) record. ## When to choose the Email API Choose the Email API if you are: * Building a new application, or sending from a serverless or edge environment where holding an SMTP connection is awkward. * Relying on delivery, bounce, and engagement [webhooks](/email-api/webhooks) to track what happens to your mail. Start with the [Email API quickstart](/email-api/curl/quickstart). ## When to choose SMTP Choose SMTP if you: * Already run a mail server, or use software that sends over SMTP, and want to move to MailChannels with little or no code change. See [Sending via SMTP](/email-api/smtp-sending) to get started. # Errors Source: https://docs.mailchannels.com/email-api/errors Learn about common Email API errors, what they mean, and how to handle them. The handling pattern depends on the language you use: * **curl** — inspect the HTTP status returned by `curl -w "%{http_code}"` and parse the response body with `jq`. * **JavaScript SDK** — SDK methods return `{ data, error }` (or `{ success, error }`). The `error` object has `message` and `statusCode` fields; branch on `error?.statusCode`. * **Python SDK** — `mailchannels.Emails.send` (and other resource methods) raise typed exceptions that subclass `mailchannels.exceptions.MailChannelsError`. Catch the specific subclass to react to each status. * **PHP SDK** — SDK methods throw typed exceptions that subclass `MailChannels\Exception\MailChannelsException`. Catch the specific subclass to react to each status; use `getStatusCode()` and `getMessage()` on the caught exception. The examples below show how each endpoint's errors might be handled together in production code: surface transient 5xx failures so the caller (or a retry layer) can react, and fail fast on 4xx so the underlying request can be corrected. ## Send API errors | Status | Meaning | What to do | | - | - | - | | `400 Bad Request` | The JSON body is malformed, a required field is missing, an email address is invalid, or non-transactional sending rules are not met. | Compare the payload with the [Email Structure](/email-api/how-emails-are-structured) guide and the API reference. | | `403 Forbidden` | The API key is missing, invalid, scoped incorrectly, not attached to the expected account, or the sending domain is not authorized. | Confirm the `X-Api-Key` header, check the key scope, and verify [Domain Lockdown](/email-api/domain-lockdown). | | `413 Payload Too Large` | The encoded message, headers, and attachments exceed 30 MB. | Reduce attachment size or count. See [Attachments](/email-api/attachments). | | `500 Internal Server Error` | A transient MailChannels service error. | Retry with exponential backoff. Check [status.mailchannels.net](https://status.mailchannels.net/) if the error persists. | | `502 Bad Gateway` | A transient infrastructure or network error. | Retry with backoff and alert if repeated attempts fail. | ```bash cURL theme={null} status=$(curl -s -o /tmp/mc-resp.json -w "%{http_code}" \ -X POST https://api.mailchannels.net/tx/v1/send \ -H "Content-Type: application/json" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" \ -d @payload.json) case "$status" in 2*) ;; 400|403|413) echo "Send rejected ($status):" jq -r '.errors // .message // empty' /tmp/mc-resp.json exit 1 ;; 500|502) echo "Send failed transiently ($status). Retry later." exit 1 ;; *) echo "Unexpected status $status" exit 1 ;; esac ``` ```javascript Node.js theme={null} const { data, error } = await mailchannels.emails.send(payload); if (error) { switch (error.statusCode) { case 400: // Invalid payload — compare with the Email Structure guide. case 403: // Bad API key, wrong scope, or missing Domain Lockdown TXT record. case 413: // Message + attachments exceed 30 MB. console.error(`Send rejected (${error.statusCode}):`, error.message); throw error; case 500: // Transient MailChannels failure — surface for a retry layer. case 502: // Transient infrastructure / network failure — surface for a retry layer. console.error(`Send failed transiently (${error.statusCode}):`, error.message); throw error; default: throw error; } } ``` ```python Python theme={null} import logging import mailchannels from mailchannels.exceptions import ( ForbiddenError, InvalidRequestError, PayloadTooLargeError, ServerError, ) log = logging.getLogger(__name__) try: response = mailchannels.Emails.send(payload) except (InvalidRequestError, ForbiddenError, PayloadTooLargeError) as e: # 400 / 403 / 413 — caller error. Fix the request before retrying. log.error("Send rejected (%s): %s", e.status_code, e.message) raise except ServerError as e: # 500 / 502 — transient. Surface so a retry layer can react. log.error( "Send failed transiently (%s, request_id=%s): %s", e.status_code, e.request_id, e.message, ) raise ``` ```php PHP theme={null} emails->send($payload); } catch (InvalidRequestException | ForbiddenException | PayloadTooLargeException $e) { // 400 / 403 / 413 — caller error. Fix the request before retrying. error_log(sprintf('Send rejected (%d): %s', $e->getStatusCode(), $e->getMessage())); throw $e; } catch (ServerException $e) { // 500 / 502 — transient. Surface so a retry layer can react. error_log(sprintf( 'Send failed transiently (%d, request_id=%s): %s', $e->getStatusCode(), $e->getRequestId(), $e->getMessage(), )); throw $e; } ``` ## Suppression API errors | Status | Meaning | What to do | | - | - | - | | `400 Bad Request` | A recipient, source, date filter, or request body is invalid. | Validate recipient formatting and use a supported source: `api`, `unsubscribe_link`, `list_unsubscribe`, `hard_bounce`, or `spam_complaint`. | | `409 Conflict` | One or more suppression entries already exist. | Treat existing entries as success during idempotent imports, or query the suppression list before creating entries. | | `413 Payload Too Large` | The create request exceeds the 1,000-entry limit. | Split imports into batches of 1,000 entries or fewer. | | `500 Internal Server Error` | A transient MailChannels service error. | Retry with exponential backoff. Check [status.mailchannels.net](https://status.mailchannels.net/) if the error persists. | | `503 Temporarily Unavailable` | Suppression service maintenance or temporary unavailability. | Retry later with backoff. | ```bash cURL theme={null} # Assumes entries.json is already chunked to <= 1,000 entries per request. status=$(curl -s -o /tmp/mc-resp.json -w "%{http_code}" \ -X POST https://api.mailchannels.net/tx/v1/suppressions \ -H "Content-Type: application/json" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" \ -d @entries.json) case "$status" in 2*) ;; 409) ;; # Entries already exist — idempotent success. 400|413) echo "Suppression import rejected ($status):" jq -r '.message // empty' /tmp/mc-resp.json exit 1 ;; 500|503) echo "Suppression import failed transiently ($status). Retry later." exit 1 ;; *) echo "Unexpected status $status" exit 1 ;; esac ``` ```javascript Node.js theme={null} const BATCH_SIZE = 1000; function chunked(items, size) { const out = []; for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size)); return out; } for (const batch of chunked(entries, BATCH_SIZE)) { const { success, error } = await mailchannels.suppressions.create({ entries: batch }); if (success) continue; switch (error.statusCode) { case 409: // Entries already exist — idempotent, treat as success. continue; case 400: // Invalid recipient, source, or filter. case 413: // Batch exceeds 1,000 entries. console.error(`Suppression import rejected (${error.statusCode}):`, error.message); throw error; case 500: // Transient failure — surface for a retry layer. case 503: // Suppression service maintenance — surface for a retry layer. console.error(`Suppression import failed transiently (${error.statusCode}):`, error.message); throw error; default: throw error; } } ``` ```python Python theme={null} import logging import mailchannels from mailchannels.exceptions import ( ConflictError, InvalidRequestError, PayloadTooLargeError, ServerError, ) log = logging.getLogger(__name__) BATCH_SIZE = 1000 def chunked(items, size): for i in range(0, len(items), size): yield items[i : i + size] for batch in chunked(entries, BATCH_SIZE): try: mailchannels.Suppressions.create(entries=batch) except ConflictError: # 409 — one or more entries already exist. Idempotent: treat as success. continue except (InvalidRequestError, PayloadTooLargeError) as e: # 400 / 413 — caller error. Fix the batch before retrying. log.error("Suppression import rejected (%s): %s", e.status_code, e.message) raise except ServerError as e: # 500 / 503 — transient. Surface so a retry layer can react. log.error( "Suppression import failed transiently (%s, request_id=%s): %s", e.status_code, e.request_id, e.message, ) raise ``` ```php PHP theme={null} suppressions->create($batch); } catch (ConflictException) { // 409 — one or more entries already exist. Idempotent: treat as success. continue; } catch (InvalidRequestException | PayloadTooLargeException $e) { // 400 / 413 — caller error. Fix the batch before retrying. error_log(sprintf('Suppression import rejected (%d): %s', $e->getStatusCode(), $e->getMessage())); throw $e; } catch (ServerException $e) { // 500 / 503 — transient. Surface so a retry layer can react. error_log(sprintf( 'Suppression import failed transiently (%d, request_id=%s): %s', $e->getStatusCode(), $e->getRequestId(), $e->getMessage(), )); throw $e; } } ``` ## Webhook API errors | Status | Meaning | What to do | | - | - | - | | `400 Bad Request` | A webhook URL, batch ID, filter, or validation request is invalid. | Check the URL, filter values, and batch ID before retrying. | | `404 Not Found` | The requested webhook batch or signing key does not exist. Applies to `POST /webhook-batch/{batch_id}/resend`, `GET /webhook/public-key`, and `POST /webhook/validate`. | Confirm the resource ID exists before retrying. | | `409 Conflict` | A webhook endpoint is already enrolled for this customer (`POST /webhook` only). | Treat as success for idempotent enrollment, or query existing webhooks with `GET /webhook` before enrolling. | | `500 Internal Server Error` | A transient MailChannels service error. | Retry and check [status.mailchannels.net](https://status.mailchannels.net/) if failures continue. | ```bash cURL theme={null} status=$(curl -s -o /tmp/mc-resp.json -w "%{http_code}" \ -X POST "https://api.mailchannels.net/tx/v1/webhook?endpoint=$WEBHOOK_URL" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY") case "$status" in 2*) ;; 409) ;; # Endpoint already enrolled — idempotent success. 400|404) echo "Webhook request rejected ($status):" jq -r '.message // empty' /tmp/mc-resp.json exit 1 ;; 500) echo "Webhook request failed transiently. Retry later." exit 1 ;; *) echo "Unexpected status $status" exit 1 ;; esac ``` ```javascript Node.js theme={null} const { success, error } = await mailchannels.webhooks.enroll(webhookUrl); if (error) { switch (error.statusCode) { case 409: // Endpoint already enrolled — idempotent, treat as success. break; case 400: // Invalid URL, filter, or batch ID. case 404: // Missing batch / signing key (resendBatch, publicKey, validate). console.error(`Webhook request rejected (${error.statusCode}):`, error.message); throw error; case 500: // Transient failure — surface for a retry layer. console.error("Webhook request failed transiently:", error.message); throw error; default: throw error; } } ``` ```python Python theme={null} import logging import mailchannels from mailchannels.exceptions import ConflictError, InvalidRequestError, ServerError log = logging.getLogger(__name__) try: mailchannels.Webhooks.create(endpoint=webhook_url) except ConflictError: # 409 — endpoint already enrolled. Idempotent: treat as success. pass except InvalidRequestError as e: # 400 — invalid URL, filter, or batch ID. # 404 — missing batch or signing key (Webhooks.resend_batch / # public_key / validate). Both surface as InvalidRequestError; # inspect e.status_code to distinguish. log.error("Webhook request rejected (%s): %s", e.status_code, e.message) raise except ServerError as e: # 5xx — transient. Surface so a retry layer can react. log.error( "Webhook request failed transiently (%s, request_id=%s): %s", e.status_code, e.request_id, e.message, ) raise ``` ```php PHP theme={null} webhooks->create($webhook_url); } catch (ConflictException) { // 409 — endpoint already enrolled. Idempotent: treat as success. } catch (InvalidRequestException | NotFoundException $e) { // 400 — invalid URL, filter, or batch ID. // 404 — missing batch or signing key (resendBatch, validate). // Both surface as different exceptions; inspect getStatusCode() to distinguish. error_log(sprintf('Webhook request rejected (%d): %s', $e->getStatusCode(), $e->getMessage())); throw $e; } catch (ServerException $e) { // 5xx — transient. Surface so a retry layer can react. error_log(sprintf( 'Webhook request failed transiently (%d, request_id=%s): %s', $e->getStatusCode(), $e->getRequestId(), $e->getMessage(), )); throw $e; } ``` # Handling hard-bounced events Source: https://docs.mailchannels.com/email-api/hard-bounced-events Learn about the best practices for responding to hard-bounced webhook events. ## What is a hard-bounced event? A hard-bounced event is generated when a recipient's server permanently rejects an email (returning a 5xx SMTP status code). You should not attempt to resend it. The event data includes the specific reason for the bounce, indicating whether the cause was a problem with your request (e.g., an invalid recipient address) or with the recipient's server (e.g., mailbox unavailable). In certain cases, such as an invalid recipient address, the rejected recipient is automatically added to your suppression list. For more information, see [suppressions and unsubscribe handling](/email-api/suppressions-unsubscribes). ## What to do if you receive a hard-bounced event If you receive a hard-bounced event, you should investigate the reason for the bounce and take appropriate action based on the `status` code and `reason` fields. For example, if the bounce is due to an invalid recipient address, you should remove that address from your mailing list to avoid sending to it again in the future. Other causes of hard-bounced events include issues with the recipient's server, rejections due to failed authentication (DKIM, SPF), or the server flagging your mail as spam. For more in-depth information on SMTP response codes and their meanings, see our [SMTP response code documentation](https://blog.mailchannels.com/understanding-smtp-response-codes/). ## Fields for hard-bounced events * `email`: Sender email address. * `customer_handle`: Your account ID. * `timestamp`: Unix timestamp of the event. * `event`: The type of event, `hard-bounced` in this case. * `recipients`: List of recipient email addresses that couldn't receive the message. * `status`: The SMTP status code received for the bounce. * `reason`: A human-readable explanation of why the message bounced. * `campaign_id` *(optional)*: The campaign ID associated with the message, if applicable. * `request_id` *(optional)*: A unique identifier generated to track the original HTTP request. This will be blank if the message is sent via SMTP instead of the API. * `smtp_id`: A unique identifier used to track the message, matching the `Message-Id` header. ## Example hard-bounced event ```json theme={null} { "email": "sender@example.com", "customer_handle": "examplecorp", "timestamp": 1700000000, "event": "hard-bounced", "recipients": [ "recipient@example.net", "recipient2@example.net" ], "status": "521", "reason": "Server does not accept mail", "campaign_id": "spring-sale-2026", "request_id": "4b06083a23724f279c8d11a370e641f8", "smtp_id": "" } ``` # How emails are structured Source: https://docs.mailchannels.com/email-api/how-emails-are-structured Learn how to structure your email payload for sending, including personalizations and content types. ## The request payload The structure of the request JSON payload represents the email you want to send. Among other things, it defines the sender, recipients, subject, and body of an email. The `personalizations` field allows you to specify unique content for each recipient of an email. You can create highly targeted and individualized email campaigns within a single API call. ### Basic structure The `personalizations` array applies per-recipient overrides on top of the root-level fields. This structure allows for sending to multiple recipients, customizing subject lines per recipient, and applying different headers or DKIM settings per email. The example below sends a fruit sale to three recipients. The first two entries override the sender, subject, and cc/bcc recipients; the third entry only overrides the subject and falls back to the root-level sender: ```json JSON theme={null} { "personalizations": [ { "to": [ { "name": "Banana Lover", "email": "banana-lover123@example.com" } ], "cc": [ { "email": "banana-fans@example.com" } ], "from": { "name": "Banana Department", "email": "bananas@fruitstand.example.com" }, "subject": "🍌 BANANAS ARE ON SALE 🍌" }, { "to": [ { "name": "Apple Lover", "email": "apple-lover123@example.com" } ], "cc": [ { "email": "apple-fans@example.com" } ], "bcc": [ { "email": "archive@fruitstand.example.com" } ], "from": { "name": "Apple Department", "email": "apples@fruitstand.example.com" }, "subject": "🍎 APPLES ARE ON SALE 🍎" }, { "to": [ { "name": "苹果爱好者", "email": "pingguo-fan@example.com" } ] } ], "from": { "name": "Fruit Stand", "email": "sales@fruitstand.example.com" }, "subject": "Big sale this week", "content": [ { "type": "text/plain", "value": "Don't miss our fruit sale this week!" }, { "type": "text/html", "value": "

Don't miss our fruit sale this week!

" } ] } ```
When a property is defined both globally and in a personalization object, the value in the personalization object is used. ### Field types Detailed definitions of each field can be found in the [API reference](/api-reference/send/send-an-email#body-personalizations). The `to` field is the only required property within each personalization object. * `personalizations`: Array of objects (recipient-specific data) * `from`: Object (sender information) * `subject`: String * `content`: Array of objects (email body parts) Each `content` object contains: * `type`: String (e.g., "text/plain", "text/html") * `value`: String (actual content) Each `EmailAddress` object (sender or recipient information) contains: * `name`: String, optional. Display name as raw, unencoded text (e.g., `John Doe`, `张三`). * `email`: String (e.g., `foo@example.com`). Always provide the `name` field as raw, unencoded text to prevent double-encoding issues. Special characters and non-ASCII text will be automatically handled for email compatibility. # Inbound email and replies Source: https://docs.mailchannels.com/email-api/inbound-email-replies Understand how to handle replies and inbound email when sending outbound messages with MailChannels Email API. MailChannels Email API is focused on outbound email delivery. It sends messages, reports delivery events through webhooks, and helps protect your sending domains. It does not provide an inbound message content API for receiving, parsing, or storing replies. ## Supported reply patterns Use one of these patterns when recipients need to reply: | Pattern | Use it when | | - | - | | Real mailbox | A person or team should read and answer replies in an inbox. | | Shared support address | Replies should create or update support tickets through your help desk. | | Dedicated inbound provider | Your application needs to parse inbound message content, attachments, or reply threads. | | No-reply sender plus `reply_to` | The visible sender should be product-owned, but replies should route elsewhere. | ## Configure `reply_to` Set `reply_to` in your send request when replies should go to a different mailbox than the `from` address. ```json theme={null} { "from": { "email": "notifications@example.com", "name": "Example App" }, "reply_to": { "email": "support@example.com", "name": "Example Support" } } ``` ## What webhooks do not include MailChannels webhooks report delivery events such as processing, delivery, bounces, drops, complaints, and unsubscribe activity. They are not inbound message webhooks and do not include reply content. If your product needs reply parsing, store the outbound `request_id` and message metadata in your application, then connect replies through your mailbox or inbound provider. # Nuxt community integration Source: https://docs.mailchannels.com/email-api/integrations/nuxt Find the community-maintained Nuxt module for the MailChannels Email API and the official Node.js SDK guides. Use [nuxt-mailchannels in the Nuxt Modules directory](https://nuxt.com/modules/mailchannels) to integrate the MailChannels Email API into a Nuxt application. The module is maintained by [Yizack Rangel](https://github.com/Yizack/nuxt-mailchannels) and uses the official MailChannels Node.js SDK. ## Before you start Complete the account, API-key, and sending-domain setup in the [Node.js quickstart](/email-api/javascript/quickstart#prerequisites). The module requires Nuxt 3 or later and a running Nuxt server. Its server utilities are not available in a purely static deployment produced by `nuxt generate`. Keep your API key in server-side runtime configuration. ## Install and configure the module Follow the module maintainer's [installation and configuration guide](https://github.com/Yizack/nuxt-mailchannels#quick-setup) for the current setup instructions, server utilities, and examples. The package is published as [nuxt-mailchannels on npm](https://www.npmjs.com/package/nuxt-mailchannels). This is a community-maintained module. Report module-specific problems in its [issue tracker](https://github.com/Yizack/nuxt-mailchannels/issues). ## Use the SDK directly If you prefer to call the official SDK from your own server routes, use the [Nuxt guide in the Node.js SDK repository](https://bitbucket.org/mailchannels/mailchannels-email-api-sdk-js/src/main/docs/guides/frameworks/nuxt.md). See the [Node.js quickstart](/email-api/javascript/quickstart) for the SDK's account setup and first-send example. # Send governed emails automatically with MailChannels + Zapier Source: https://docs.mailchannels.com/email-api/integrations/zapier Connect MailChannels to Zapier so trusted app events can trigger governed, domain-authorized email sends. The MailChannels Zapier integration lets you send domain-authorized email through the MailChannels Email API whenever a trusted event fires in another app, such as a form submission, new CRM contact, order update, or support workflow. You build the workflow once in Zapier's visual editor, and MailChannels handles governed delivery every time the trigger fires. The MailChannels app on Zapier is currently in **Beta**. It works reliably, but you may see the Beta label when searching for it in the Zapier app directory. ## What you need before you start * A **MailChannels account** - sign up at [mailchannels.com](https://www.mailchannels.com/pricing/#for_devs). * A **Zapier account** - free or paid, available at [zapier.com](https://zapier.com). * A **domain you own** - MailChannels sends email on behalf of your domain and requires a one-time DNS setup described below. ## Setting up the integration Zapier uses your API key to authenticate with MailChannels on your behalf. 1. Log in to the [MailChannels Console](https://console.mailchannels.net). 2. Go to **Account Settings**. 3. Under **API Keys**, click **Create API Key**. 4. Give it a description (e.g., `Zapier`) and set the **Scope** to `api`. 5. Copy the key and store it somewhere safe. You will not be able to view it again. While you are in the console, note your **Account ID** shown in the upper-right corner. You may need it during domain setup. MailChannels uses **Domain Lockdown** to authorize the domain your emails will come from. You configure this once in your domain's DNS settings so your Zapier workflow cannot send from an unmanaged domain. Add a TXT record at your domain registrar with the following values: | Field | Value | | - | - | | **Host** | `_mailchannels.` | | **Value** | `v=mc1; auid=` | DNS changes typically take a few minutes to a few hours to propagate. Your domain must also have a valid **SPF record** that includes MailChannels, and at least one **A** or **MX** record. Without these, MailChannels may not deliver mail for that domain. Set up **DKIM** for your domain in the MailChannels Console under **Settings > DKIM**. DKIM signing helps protect sender reputation and reduces the chance your emails land in spam. 1. In Zapier, click **+ Create** > **Zaps**. 2. Give your Zap a name, such as `Send Welcome Email via MailChannels`. 3. Navigate to the **Action** step and search for **MailChannels**. 4. Select **MailChannels** from the results. 5. For **Action Event**, select **Send Email**. 6. In the **Account** section, click **Sign in**. 7. Enter your MailChannels API key when prompted, then confirm. MailChannels is now connected to your Zapier account. This connection is reusable, so you only need to authenticate once. After authenticating, click **Continue** to reach the email configuration screen. Fill in the following fields: | Field | Description | | - | - | | **From (Email Address)** | The address you are sending from. It must use the domain you authorized in step 2, e.g. `hello@acme.com` | | **From (Display Name)** | *(Optional)* The name recipients see, e.g. `Acme Support` | | **To (Email Address)** | The recipient's email. Click the field to insert a dynamic value from your trigger | | **To (Display Name)** | *(Optional)* The recipient's name, also available as a dynamic field | | **Subject** | Your subject line. This supports dynamic values | | **Body (HTML)** | The email body. This supports HTML and dynamic values for personalization | In any field, click inside it to browse and insert data from your trigger step, such as the submitter's name, email address, or order number. This is what makes each email feel personally addressed without extra work. **Example HTML body:** ```html theme={null}

Hi {{first_name}},

Thanks for signing up! We're excited to have you on board.

- The Acme Team

``` Once all fields are filled in, click **Continue**, then **Test Step**. Zapier sends a real test email using your sample trigger data. Check the inbox to confirm it arrived.
The trigger is the event in another app that starts the automation. Go to the **Trigger** step at the top of your Zap and configure it. 1. Search for your app and select it. 2. Choose the event that should start the email, e.g. `New Submission`. 3. Connect your account and follow the prompts. 4. Click **Test Trigger** to pull in sample data. This data will be available to personalize your email. Common trigger apps and events: * **Form submissions** - Typeform, Google Forms, Gravity Forms * **New contacts** - HubSpot, Salesforce, Pipedrive * **New orders** - Shopify, WooCommerce * **Spreadsheet rows** - Google Sheets, Airtable * **Scheduled time** - Schedule by Zapier (great for recurring emails) Once the test is successful: 1. Click **Publish** to turn the Zap on. 2. Your automation is now live. Every time the trigger fires, MailChannels sends the email automatically.
## Common use cases **Welcome email on form signup** - Trigger: new Typeform or Google Forms submission. Action: MailChannels sends a personalized welcome email to the submitter. **Order confirmation** - Trigger: new order in Shopify or WooCommerce. Action: MailChannels sends a confirmation with the customer's order details. **CRM follow-up** - Trigger: new contact added in HubSpot or Pipedrive. Action: MailChannels sends a personalized intro email from your sales team. **Scheduled digest** - Trigger: Schedule by Zapier (e.g., every Monday at 9 am). Action: MailChannels sends a weekly summary to your team or list. **Internal team alert** - Trigger: new row added to a Google Sheet. Action: MailChannels emails your team with the new row's details. ## Troubleshooting **I can't find MailChannels in Zapier's app search.** The integration is currently in Beta. Search for `MailChannels` directly, or use the direct link: [zapier.com/apps/mailchannels/integrations](https://zapier.com/apps/mailchannels/integrations). **My test email wasn't delivered.** * Make sure the From address uses the same domain you authorized in step 2. * Confirm your Domain Lockdown TXT record has propagated. * Verify your domain has a valid SPF record and at least one A or MX record. **Email went to spam.** * Set up DKIM in the MailChannels Console under **Settings > DKIM**. * Confirm your SPF record is valid. * Consider adding a DMARC record. See the [MailChannels DMARC guide](https://docs.mailchannels.net/email-api/advanced/dmarc). **Zapier shows an authentication error.** Your API key may have been deleted or rotated. Generate a new one in the MailChannels Console, then reconnect in Zapier under **My Apps > MailChannels > Reconnect**. # Node.js quickstart Source: https://docs.mailchannels.com/email-api/javascript/quickstart Send an email with the MailChannels Email API using Node.js. Send an email in a few minutes. This guide walks through sending your first request to the MailChannels Email API. The Node.js SDK is open source. Browse the [source code](https://bitbucket.org/mailchannels/mailchannels-email-api-sdk-js/). ## Prerequisites Before your first request, complete the following steps. [Sign up](https://dash.mailchannels.com) if you don't already have an account. Open [Account > API Keys](https://dash.mailchannels.com/account/api-keys) and click **Create API Key**. Add a descriptive label and set the scope to `api`. Copy the key; you won't be able to see it again. Treat your API key like a password. Don't commit it to source control or expose it in client-side code. For each domain you'll send from, add a `TXT` record on the `_mailchannels` subdomain authorizing your account to send. Replace `examplecorp` with your MailChannels account ID (visible in the panel footer when you log in): ``` _mailchannels.example.com TXT "v=mc1 auth=examplecorp" ``` See the [Domain Lockdown guide](/email-api/domain-lockdown) for details. Add MailChannels to the SPF record on each sending domain so recipient servers accept your mail: ``` example.com TXT "v=spf1 include:relay.mailchannels.net ~all" ``` If you already have an SPF record, merge in `include:relay.mailchannels.net` rather than creating a second record. A domain must have only one SPF record. Your sending domain must resolve. It needs a valid `A` or `MX` record. Without one, mail from the domain is rejected with `550 5.1.2 [SDNF] Sender Domain Not Found`. Most registered domains already have these records. Confirm yours with the [check-domain tool](https://dash.mailchannels.com/domain-health). See [Troubleshooting](/email-api/troubleshooting) for details. ## Install the Node.js SDK ```javascript Node.js theme={null} # npm npm i mailchannels-sdk # yarn yarn add mailchannels-sdk # pnpm pnpm add mailchannels-sdk ``` ## Send your first email To send an email, provide the recipients, sender, subject, and message content. The example below sends an HTML email to a single recipient. Until you add a payment method, sending is restricted to addresses belonging to verified [Users](https://dash.mailchannels.com/account/users) **on your account**. Add a user, click the link in the verification email, then use that address as the `to` email below. ```javascript Node.js theme={null} import { MailChannels } from "mailchannels-sdk"; const { MAILCHANNELS_API_KEY, FROM_EMAIL, TO_EMAIL } = process.env; if (!MAILCHANNELS_API_KEY) throw new Error("Set MAILCHANNELS_API_KEY before running"); if (!FROM_EMAIL) throw new Error("Set FROM_EMAIL (must be on a Domain-Lockdown-authorized domain)"); if (!TO_EMAIL) throw new Error("Set TO_EMAIL"); const mailchannels = new MailChannels(MAILCHANNELS_API_KEY); const { data, error } = await mailchannels.emails.send({ from: { email: FROM_EMAIL, name: "Your Name" }, to: { email: TO_EMAIL, name: "Recipient" }, subject: "Hello from MailChannels", text: "Hello! This is a plain-text fallback for clients that don't render HTML.", html: '

Hello,

This is my first HTML email sent via the MailChannels Email API.

', }); if (error) { console.error("Send failed:", error); process.exit(1); } console.log(data); ```
Set the following before running: * `MAILCHANNELS_API_KEY` — the API key generated above. * `FROM_EMAIL` — an address on the domain you configured with Domain Lockdown and SPF above (e.g. `hello@example.com` if you used `example.com` in the Domain Lockdown and SPF DNS records). * `TO_EMAIL` — a recipient inbox you can check, e.g. the address you use to sign up for MailChannels. ## Next steps * Read the [webhooks guide](/email-api/webhooks) to learn how to get real-time notifications about email delivery, bounces, and complaints. * Explore the [API reference](/email-api/api-reference-introduction) to see what else you can do with the API. # Laravel Source: https://docs.mailchannels.com/email-api/laravel Send email through Laravel Mail using the MailChannels driver. The `mailchannels/mailchannels-php` package includes a Laravel service provider that registers `mailchannels` as a [Mail](https://laravel.com/docs/13.x/mail) driver. Laravel's `Mail` facade is built on Symfony Mailer under the hood, and uses the exact same transport as the [Symfony integration](/email-api/symfony). This plugin is optional and lives alongside the rest of the PHP SDK. The plugin itself can only send mail. It doesn't implement the full API surface (sub-accounts, keys, webhooks, etc.). For those features, use the SDK's `Client` directly. ## Prerequisites Before you send your first message, follow the [PHP quickstart](/email-api/php/quickstart#prerequisites) prerequisite section. This will walk you through creating an account, generating an API key, and adding the required Domain Lockdown and SPF DNS records. ## Installation ```bash theme={null} composer require mailchannels/mailchannels-php ``` The SDK also needs a PSR-18 HTTP client and PSR-17 factories. Symfony HTTP Client + Nyholm PSR-7 is a good fit here, since Laravel Mail is built on Symfony Mailer (Guzzle works too, but install one or the other, not both): ```bash theme={null} composer require symfony/http-client nyholm/psr7 ``` ### Configuring Laravel Add a `mailchannels` mailer in `config/mail.php`: ```php config/mail.php theme={null} return [ // ... 'mailers' => [ // ... 'mailchannels' => [ 'transport' => 'mailchannels', ], ], ]; ``` Add it to `config/services.php`: ```php config/services.php theme={null} return [ // ... 'mailchannels' => [ 'api_key' => env('MAILCHANNELS_API_KEY'), 'base_url' => env('MAILCHANNELS_API_URL'), // optional; omit for the default ], ]; ``` Set in your environment or `.env` file: ```bash .env theme={null} MAIL_MAILER=mailchannels MAILCHANNELS_API_KEY=your-api-key ``` ## Sending mail Once configured, send mail through the standard `Mail` facade. No MailChannels-specific code is required: ```php theme={null} use App\Mail\WelcomeEmail; use Illuminate\Support\Facades\Mail; Mail::to('recipient@example.com')->send(new WelcomeEmail()); ``` See Laravel's docs on [generating mailables](https://laravel.com/docs/13.x/mail#generating-mailables) for everything else `Email` supports (multiple recipients, reply-to, attachments and so on). Anything built with a Laravel [Mailable](https://laravel.com/docs/13.x/mail#generating-mailables) (`from`, `to`/`cc`/`bcc`, `subject`, `text`/`html` bodies, attachments) can be sent through this transport. Only one personalization is ever built per message. If you need to send multiple individualized emails in one request, call the SDK's `Client` directly instead of going through `Mail`. See [How emails are structured](/email-api/how-emails-are-structured) for more information on personalizations. ### Control headers A Laravel `Mailable` has no equivalent for some MailChannels-specific features. Reach those instead through custom headers prefixed with `x-mailchannels-`. The `x-mailchannels-*` headers are stripped before the message is sent, so recipients never see them. Set these (and any other custom headers) from a `Mailable`'s `headers()` method: ```php theme={null} use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailables\Headers; class WelcomeEmail extends Mailable { public function headers(): Headers { return new Headers( text: [ 'X-Misc-Header' => 'welcome-2026', 'x-mailchannels-transactional' => 'true', ], ); } } ``` If the same custom header is added twice, only the **last** value is used. Boolean headers accept only the literal strings `"true"` / `"false"`; anything else throws `Symfony\Component\Mailer\Exception\TransportException`. An empty value is treated as unset (falls back to the default). | Header | Description | | - | - | | `x-mailchannels-send-async` | Queue the message (`true`, the default) instead of sending it synchronously (`false`). Equivalent to calling the SDK's `queue()` vs. `send()`. Queued sends report delivery status via [webhooks](/email-api/webhooks) rather than in the response. | | `x-mailchannels-click-tracking-enable` | Enable click tracking. | | `x-mailchannels-click-tracking-domain` | Custom domain for click-tracking links. See [Custom tracking](/email-api/custom-tracking). | | `x-mailchannels-open-tracking-enable` | Enable open tracking. | | `x-mailchannels-open-tracking-domain` | Custom domain for open-tracking links. See [Custom tracking](/email-api/custom-tracking). | | `x-mailchannels-unsubscribe-domain` | Custom domain for unsubscribe links. See [Custom tracking](/email-api/custom-tracking). | | `x-mailchannels-transactional` | Whether the mail is transactional or non-transactional (marketing, etc.). Affects List-Unsubscribe handling, see [Unsubscribe](/email-api/unsubscribe). | | `x-mailchannels-dkim-domain` | Domain to use for DKIM signing. | | `x-mailchannels-dkim-selector` | Selector to use for DKIM signing. | | `x-mailchannels-dkim-private-key` | Private key to use for DKIM signing. | | `x-mailchannels-campaign-id` | Campaign that this message is part of. | Each of these corresponds directly to a field on the [send endpoint](/api-reference/send/send-an-email). See that reference for full semantics. This transport only supports DKIM via the `x-mailchannels-dkim-*` headers above. If a `DKIM-Signature` header is already present on the message, the message is rejected, since the provided signature won't match the message the API constructs. See [DKIM signing and key management](/email-api/configuring-dkim) for information on how MailChannels handles signing and keys. Laravel's own mail queueing (`ShouldQueue` mailables, `Mail::queue()`) is unrelated to the `x-mailchannels-send-async` control header: The former controls *when your application* hands the message to the transport; the latter controls whether *MailChannels* processes it synchronously or asynchronously once it arrives. You can use them independently or together. ## Error handling Any SDK exception raised while sending (authentication, validation, rate limiting, server errors) is caught and re-thrown as `Symfony\Component\Mailer\Exception\TransportException`. Access the original exception via `$e->getPrevious()` to inspect it: ```php theme={null} use Symfony\Component\Mailer\Exception\TransportException; try { Mail::to($recipient)->send(new WelcomeEmail()); } catch (TransportException $e) { $previous = $e->getPrevious(); if ($previous instanceof \MailChannels\Exception\RateLimitException) { // handle rate limit } elseif ($previous instanceof \MailChannels\Exception\ValidationException) { // handle validation error } else { // handle other transport errors } } ``` ## Limitations * **One personalization per message.** No per-recipient template data or per-recipient header overrides through this transport. * **No Mustache templating.** This transport has no way to use mailchannels' mustache template rendering. If you need to do so, use Laravel's [Blade Template Support](https://laravel.com/docs/13.x/blade), or use the SDK's `Client` directly; see [Templates](/email-api/templates) for how. * **DKIM only via the MailChannels API**, as described above. ## Next steps * Read the [webhooks guide](/email-api/webhooks) to learn how to get real-time notifications about email delivery, bounces, and complaints. * Explore the [API reference](/email-api/api-reference-introduction) to see what else you can do with the API. # Local development Source: https://docs.mailchannels.com/email-api/local-development Set up a safe local workflow for testing MailChannels Email API sends, webhooks, and secrets. Use a local workflow that exercises the real API without exposing secrets or surprising real recipients. ## Environment variables Store credentials outside source control: ```bash theme={null} MAILCHANNELS_API_KEY=xxx ``` Add `.env` to `.gitignore` and commit a `.env.example` with placeholder values only. ## Test sending Start with one recipient you control. Use a subject prefix such as `[dev]` or `[staging]` so test mail is easy to identify. Before sending a new template, use the `dry-run` option to render the message without sending it. ## Local webhook testing During development: 1. Run your webhook receiver locally. 2. Expose it through a temporary HTTPS tunnel. 3. Register the tunnel URL as your webhook endpoint. 4. Run webhook validation. 5. Store the raw test event and verify signature validation before adding business logic. ## Avoid common mistakes * Do not put API keys in browser JavaScript. * Do not commit `.env` files, webhook signing keys, or private DKIM keys. * Do not test with large recipient lists. * Do not use production customer addresses for template experiments. * Do not assume a successful API response means inbox placement. Use webhooks and mailbox checks. # Metrics Source: https://docs.mailchannels.com/email-api/metrics Learn how to query engagement, delivery, and sending volume metrics. ## Introduction The Metrics API provides analytics and visibility into your email sending performance. By leveraging these endpoints, you can monitor performance indicators, track user engagement, analyze delivery outcomes, and gain valuable insights into recipient behavior patterns. MailChannels groups metrics into five categories: * **[Engagement](/email-api/metrics-engagement)**: opens and clicks. * **[Performance](/email-api/metrics-performance)**: processed, delivered, and bounced counts. * **[Volume](/email-api/metrics-volume)**: processed, delivered, and dropped counts. * **[Recipient Behavior](/email-api/metrics-recipient-behavior)**: unsubscribe activity. * **[Sender Breakdown](/email-api/metrics-senders)**: aggregate delivery performance grouped by campaign or sub-account. ## Common parameters All metric queries accept the same time filter: * `start_time` and `end_time`: ISO 8601 timestamps that restrict the query to a time range. If omitted, `start_time` defaults to one month ago and `end_time` defaults to the current time. Engagement, performance, volume, and recipient behavior additionally accept: * `campaign_id`: restrict results to a single campaign. * `interval`: controls bucket granularity for the time series. Supported values: `hour`, `day` (default), `week`, `month`. Sender breakdown is an aggregate, not a time series, and accepts a different set of parameters. See [sender breakdown](/email-api/metrics-senders) for details. Engagement metrics require open and click tracking to be enabled, which is not available in the free tier. # Engagement metrics Source: https://docs.mailchannels.com/email-api/metrics-engagement Learn how to track email opens and link clicks across your campaigns. ## What engagement metrics measure Engagement metrics report how recipients interact with your mail. They cover two tracked actions: * `open`: the number of times the email was opened. * `click`: the number of clicks on tracked links in HTML messages. Each event count is paired with the number of recipients who could have performed the action, so you can compute open and click rates: * `open_tracking_delivered`: the number of recipients of delivered HTML messages where open tracking was enabled in the send request. * `click_tracking_delivered`: the number of recipients of delivered HTML messages containing tracked links, where click tracking was enabled in the send request. Messages sent without the corresponding tracking enabled do not contribute to either count. Use these metrics to measure campaign reach, identify content that resonates, and compare engagement across send times or audience segments. ## Example query The example below requests daily engagement totals for a single campaign in January 2026. ```bash cURL theme={null} #!/usr/bin/env bash # Retrieve daily open and click counts for a single campaign. set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}" curl -G "https://api.mailchannels.net/tx/v1/metrics/engagement" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" \ --data-urlencode "start_time=2026-01-01T00:00:00Z" \ --data-urlencode "end_time=2026-01-31T23:59:59Z" \ --data-urlencode "campaign_id=newsletter_jan_2026" \ --data-urlencode "interval=day" ``` ```javascript Node.js theme={null} import { MailChannels } from "mailchannels-sdk"; const { MAILCHANNELS_API_KEY } = process.env; if (!MAILCHANNELS_API_KEY) throw new Error("Set MAILCHANNELS_API_KEY before running"); const mailchannels = new MailChannels(MAILCHANNELS_API_KEY); const { data, error } = await mailchannels.metrics.engagement({ startTime: "2026-01-01T00:00:00Z", endTime: "2026-01-31T23:59:59Z", }); if (error) { console.error("Get engagement metrics failed:", error); process.exit(1); } console.log(data); ``` ```python Python theme={null} import os import sys import mailchannels if not os.environ.get("MAILCHANNELS_API_KEY"): sys.exit("Set MAILCHANNELS_API_KEY before running") # Retrieve daily open and click counts for a single campaign. response = mailchannels.Metrics.engagement( start_time="2026-01-01T00:00:00Z", end_time="2026-01-31T23:59:59Z", campaign_id="newsletter_jan_2026", interval="day", ) print(response) ``` ```php PHP theme={null} metrics->engagement( startTime: '2026-01-01T00:00:00Z', endTime: '2026-01-31T23:59:59Z', campaignId: 'newsletter_jan_2026', interval: 'day', ); print_r($response->toArray()); ``` Open and click tracking is not available in our free tier. # Performance metrics Source: https://docs.mailchannels.com/email-api/metrics-performance Learn how to monitor delivery success and bounce rates. ## What performance metrics measure Performance metrics focus on the outcome of each attempt to deliver a message. They report three counts: * `processed`: messages accepted by MailChannels for delivery. * `delivered`: messages successfully handed off to the recipient's mail server. * `bounced`: messages the recipient's mail server permanently rejected (hard bounces). Use these metrics to track delivery success rates, surface bounce patterns, and watch for changes that may indicate deliverability or reputation issues. ## Example query The example below requests delivery counts from a chosen start time onward, with the default `interval` (`day`). ```bash cURL theme={null} #!/usr/bin/env bash # Retrieve processed, delivered, and bounced counts from a chosen start time. set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}" curl -G "https://api.mailchannels.net/tx/v1/metrics/performance" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" \ --data-urlencode "start_time=2026-01-01T00:00:00Z" ``` ```javascript Node.js theme={null} import { MailChannels } from "mailchannels-sdk"; const { MAILCHANNELS_API_KEY } = process.env; if (!MAILCHANNELS_API_KEY) throw new Error("Set MAILCHANNELS_API_KEY before running"); const mailchannels = new MailChannels(MAILCHANNELS_API_KEY); const { data, error } = await mailchannels.metrics.performance({ startTime: "2026-01-01T00:00:00Z", }); if (error) { console.error("Get performance metrics failed:", error); process.exit(1); } console.log(data); ``` ```python Python theme={null} import os import sys import mailchannels if not os.environ.get("MAILCHANNELS_API_KEY"): sys.exit("Set MAILCHANNELS_API_KEY before running") # Retrieve processed, delivered, and bounced counts from a chosen start time. response = mailchannels.Metrics.performance( start_time="2026-01-01T00:00:00Z", ) print(response) ``` ```php PHP theme={null} metrics->performance( startTime: '2026-01-01T00:00:00Z', ); print_r($response->toArray()); ``` For a similar view that reports messages dropped before delivery instead of bounces, see [volume metrics](/email-api/metrics-volume). # Recipient behavior metrics Source: https://docs.mailchannels.com/email-api/metrics-recipient-behavior Learn how to track unsubscribe activity across your MailChannels Email API campaigns. ## What recipient behavior metrics measure Recipient behavior metrics report actions that signal recipient sentiment toward your mail. They currently cover unsubscribe activity: * `unsubscribed`: recipients who unsubscribed. * `unsubscribe_delivered`: recipients of delivered messages that include an unsubscribe link or unsubscribe header. Because the unsubscribe feature requires exactly one recipient per message, this also equals the total number of delivered messages with unsubscribe enabled. You can use the paired values to compute an unsubscribe rate. A rising rate can indicate content fatigue, frequency issues, or an audience mismatch. ## Example query The example below requests weekly unsubscribe activity for a single campaign. ```bash cURL theme={null} #!/usr/bin/env bash # Retrieve weekly unsubscribe activity for a single campaign. set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}" curl -G "https://api.mailchannels.net/tx/v1/metrics/recipient-behaviour" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" \ --data-urlencode "campaign_id=weekly_newsletter" \ --data-urlencode "interval=week" ``` ```javascript Node.js theme={null} import { MailChannels } from "mailchannels-sdk"; const { MAILCHANNELS_API_KEY } = process.env; if (!MAILCHANNELS_API_KEY) throw new Error("Set MAILCHANNELS_API_KEY before running"); const mailchannels = new MailChannels(MAILCHANNELS_API_KEY); const { data, error } = await mailchannels.metrics.recipientBehaviour({ startTime: "2026-01-01T00:00:00Z", endTime: "2026-01-31T23:59:59Z", }); if (error) { console.error("Get recipient behaviour metrics failed:", error); process.exit(1); } console.log(data); ``` ```python Python theme={null} import os import sys import mailchannels if not os.environ.get("MAILCHANNELS_API_KEY"): sys.exit("Set MAILCHANNELS_API_KEY before running") # Retrieve weekly unsubscribe activity for a single campaign. response = mailchannels.Metrics.recipient_behaviour( campaign_id="weekly_newsletter", interval="week", ) print(response) ``` ```php PHP theme={null} metrics->recipientBehaviour( campaignId: 'weekly_newsletter', interval: 'week', ); print_r($response->toArray()); ``` # Sender breakdown metrics Source: https://docs.mailchannels.com/email-api/metrics-senders Learn how to compare delivery performance across your largest campaigns and sub-accounts. ## What sender breakdown metrics measure Sender breakdown metrics return aggregate delivery counts grouped by sender. Each entry includes the sender `name` along with `processed`, `delivered`, `dropped`, and `bounced` totals over the queried window. Choose how to group results with the `sender_type` path parameter: * `campaigns`: group results by campaign. * `sub-accounts`: group results by sub-account. Use these metrics to identify your largest senders, flag campaigns or sub-accounts with deliverability issues, and spot sub-accounts that are approaching their sending limits. Senders with no processed messages in the queried window are excluded from the response. ## Parameters Unlike the other metric categories, sender breakdown returns an aggregate (not a time series) and does not accept `campaign_id` or `interval`. The supported parameters are: * `start_time` and `end_time`: ISO 8601 timestamps that restrict the query to a time range. If omitted, `start_time` defaults to one month ago and `end_time` defaults to the current time. * `limit`: maximum number of senders to return per page (default `10`, max `1000`). * `offset`: number of senders to skip from the start of the result set (default `0`). * `sort_order`: `asc` or `desc`. Sorting is applied to the total number of messages (`processed` + `dropped`). Defaults to `desc`. ## Example query The example below requests the top 50 campaigns by total messages (`processed` + `dropped`) for January 2026, sorted in descending order. ```bash cURL theme={null} #!/usr/bin/env bash # Retrieve the top 50 campaigns by total messages (processed + dropped) for January 2026. set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}" curl -G "https://api.mailchannels.net/tx/v1/metrics/senders/campaigns" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" \ --data-urlencode "start_time=2026-01-01T00:00:00Z" \ --data-urlencode "end_time=2026-01-31T23:59:59Z" \ --data-urlencode "limit=50" \ --data-urlencode "offset=0" \ --data-urlencode "sort_order=desc" ``` ```javascript Node.js theme={null} import { MailChannels } from "mailchannels-sdk"; const { MAILCHANNELS_API_KEY } = process.env; if (!MAILCHANNELS_API_KEY) throw new Error("Set MAILCHANNELS_API_KEY before running"); const mailchannels = new MailChannels(MAILCHANNELS_API_KEY); const { data, error } = await mailchannels.metrics.senders("campaigns", { startTime: "2026-01-01T00:00:00Z", endTime: "2026-01-31T23:59:59Z", }); if (error) { console.error("Get senders metrics failed:", error); process.exit(1); } console.log(data); ``` ```python Python theme={null} import os import sys import mailchannels if not os.environ.get("MAILCHANNELS_API_KEY"): sys.exit("Set MAILCHANNELS_API_KEY before running") # Retrieve the top 50 campaigns by total messages (processed + dropped) for January 2026. response = mailchannels.Metrics.senders( "campaigns", start_time="2026-01-01T00:00:00Z", end_time="2026-01-31T23:59:59Z", limit=50, offset=0, sort_order="desc", ) print(response) ``` ```php PHP theme={null} metrics->senders( senderType: 'campaigns', startTime: '2026-01-01T00:00:00Z', endTime: '2026-01-31T23:59:59Z', limit: 50, offset: 0, sortOrder: 'desc', ); print_r($response->toArray()); ``` # Volume metrics Source: https://docs.mailchannels.com/email-api/metrics-volume Learn how to track overall sending volume and dropped messages for your MailChannels Email API account. ## What volume metrics measure Volume metrics give you a high-level view of throughput. They report three counts: * `processed`: messages accepted by MailChannels for delivery. * `delivered`: messages successfully handed off to the recipient's mail server. * `dropped`: messages that MailChannels could not process for delivery. Use these metrics to track sending volume over time, detect spikes or drop-offs, and surface configuration issues that are causing messages to be dropped before they reach the recipient. ## Example query The example below requests daily processing and delivery counts for the first week of January 2026. ```bash cURL theme={null} #!/usr/bin/env bash # Retrieve processed, delivered, and dropped counts for the first week of January 2026. set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}" curl -G "https://api.mailchannels.net/tx/v1/metrics/volume" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" \ --data-urlencode "start_time=2026-01-01T00:00:00Z" \ --data-urlencode "end_time=2026-01-07T23:59:59Z" ``` ```javascript Node.js theme={null} import { MailChannels } from "mailchannels-sdk"; const { MAILCHANNELS_API_KEY } = process.env; if (!MAILCHANNELS_API_KEY) throw new Error("Set MAILCHANNELS_API_KEY before running"); const mailchannels = new MailChannels(MAILCHANNELS_API_KEY); const { data, error } = await mailchannels.metrics.volume({ startTime: "2026-01-01T00:00:00Z", endTime: "2026-01-07T23:59:59Z", }); if (error) { console.error("Get volume metrics failed:", error); process.exit(1); } console.log(data); ``` ```python Python theme={null} import os import sys import mailchannels if not os.environ.get("MAILCHANNELS_API_KEY"): sys.exit("Set MAILCHANNELS_API_KEY before running") # Retrieve processed, delivered, and dropped counts for the first week of January 2026. response = mailchannels.Metrics.volume( start_time="2026-01-01T00:00:00Z", end_time="2026-01-07T23:59:59Z", ) print(response) ``` ```php PHP theme={null} metrics->volume( startTime: '2026-01-01T00:00:00Z', endTime: '2026-01-07T23:59:59Z', ); print_r($response->toArray()); ``` For a similar view that reports messages bounced by the recipient's mail server instead, see [performance metrics](/email-api/metrics-performance). # Migrate from Amazon SES to MailChannels Email API Source: https://docs.mailchannels.com/email-api/migration-center/migrate-from-amazon-ses Learn how to migrate an Amazon SES sending integration to MailChannels sender-risk infrastructure. ## Core difference Amazon SES is an AWS-native set of regional email primitives. It gives you credentials, identities, configuration sets, event destinations, templates, account suppressions, and optional receipt rules, but sender governance remains something you design around those primitives. MailChannels is a transactional email delivery API for platforms with many downstream senders. It adds API credentials, JSON payloads, DKIM, Domain Lockdown, HTTPS webhooks, explicit suppressions, and controls that help detect risky senders, contain abuse, and keep good mail flowing. ## Feature mapping | Amazon SES concept | MailChannels concept | Migration approach | | - | - | - | | `SendEmail` or `SendRawEmail` | `/send` payload | Replace AWS SDK calls with MailChannels API calls. | | Email identities | `from` identities | Preserve sender addresses and align them with MailChannels domain authentication. | | Domain identities | DKIM and Domain Lockdown | Recreate domain authentication and publish MailChannels DNS records. | | Configuration sets | Runtime settings or account boundaries | Translate event, tracking, and routing intent into application settings or sub-account choices. | | Event destinations | MailChannels webhooks | Replace SNS, EventBridge, Firehose, or CloudWatch flows with HTTPS webhook consumers where appropriate. | | Email templates | Application-managed Mustache content | Export templates and move rendering ownership outside SES. | | Account suppression list | MailChannels suppression list | Recreate suppressions with explicit transactional and non-transactional policy. | | Receipt rule sets | No MailChannels Email API equivalent | Replace inbound receiving and routing with another architecture. | ## Send payload notes The migration is usually a code-level integration rewrite because SES is often called through an AWS SDK rather than a provider-neutral HTTP client. | Concern | Amazon SES behavior | MailChannels behavior | | - | - | - | | Authentication | AWS credential chain and IAM policy | MailChannels API key authentication | | Regionality | SES resources are regional | MailChannels account or sub-account configuration is not an SES region | | Send API | AWS SDK operations or raw MIME | JSON `/send` or `/send-async` payloads | | Events | AWS-native destinations | MailChannels HTTPS webhooks | | Inbound | SES receipt rules | External inbound service | ## Template migration SES templates should be exported and moved into application-owned storage. If the existing application calls SES with template names and template data, replace those calls with application-side template selection and MailChannels `dynamic_template_data`. ## Suppression policy Recommended defaults: | Amazon SES source | MailChannels target | | - | - | | Account-level bounces and complaints | Suppress as both `transactional` and `non-transactional`. | | Region-specific suppression exports | Merge carefully and deduplicate before importing or recreating in MailChannels. | | Business preference unsubscribes stored outside SES | Preserve in the source preference system and map to non-transactional suppression when appropriate. | ## Event migration SES event destinations frequently feed AWS-native systems. A MailChannels migration changes both transport and event schema. Plan this as a downstream integration rewrite, not just a webhook URL swap. ## Inbound migration SES receipt rules do not map to MailChannels Email API resources. Replace inbound flows with a dedicated receiving provider, an application-managed mailbox workflow, or another AWS-native receiving path that remains separate from MailChannels sending. ## Cutover checklist * Inventory every SES region that sends or receives mail. * Replace AWS SDK send calls with MailChannels API calls. * Recreate DKIM and domain authentication through MailChannels. * Move SES templates into application-owned storage. * Replace event destinations with MailChannels webhook consumers where needed. * Rebuild inbound receipt-rule workflows outside MailChannels. * Merge regional suppression lists intentionally. # Migrate from Brevo to MailChannels Email API Source: https://docs.mailchannels.com/email-api/migration-center/migrate-from-brevo Learn how to migrate a Brevo sending integration to MailChannels sender-risk infrastructure. ## Core difference Brevo combines transactional sending, SMTP templates, senders, domains, webhooks, contact lists, campaigns, and blacklist concepts. MailChannels is built for platforms where many downstream senders share infrastructure and reputation. The migration should move send calls to MailChannels, but it should also map sender identity, domain authorization, sub-account boundaries, suppressions, and containment paths so one sender's behavior does not become everyone's delivery problem. ## Feature mapping | Brevo concept | MailChannels concept | Migration approach | | - | - | - | | Transactional email API | `/send` payload | Rewrite sends to MailChannels JSON payloads and authentication. | | SMTP templates | Application-managed Mustache content | Export templates and move rendering ownership outside Brevo. | | Senders | `from` identities | Preserve sender addresses and align them with MailChannels-authenticated domains. | | Sending domains | DKIM and Domain Lockdown | Recreate domain authentication in MailChannels and publish DNS. | | Transactional webhooks | MailChannels webhooks | Recreate delivery-event consumers for the MailChannels schema. | | Marketing webhooks | External marketing workflow plus MailChannels webhooks | Keep marketing preferences outside MailChannels and use MailChannels events only for delivery behavior. | | Blocked contacts | Suppression list | Map transactional blocks to transactional and non-transactional suppressions. | | Blacklisted contacts | Suppression list | Map marketing blacklists to non-transactional suppressions by default. | | Contact lists and email campaigns | External audience and campaign workflow | Replace with an external list manager or campaign system. | ## Send payload notes Brevo's transactional API and MailChannels both support API-based delivery, but their payloads and template assumptions differ. | Concern | Brevo behavior | MailChannels behavior | | - | - | - | | Authentication | Brevo API key | MailChannels API key authentication | | Template reference | SMTP template ID and params | Application-selected content plus `dynamic_template_data` | | Sender controls | Sender and domain resources | Authenticated domain and `from` address in payload | | Suppression categories | Blocked transactional contacts and marketing blacklist | `transactional` and `non-transactional` suppression types | | Campaigns | Brevo contact lists and campaigns | External workflow using MailChannels as delivery API | ## Template migration Export Brevo SMTP templates and identify every application path that references a Brevo template ID. Replace those IDs with application-owned template names, files, or records. Convert Brevo-specific variables to the data shape your MailChannels send payload will provide. ## Suppression policy Recommended defaults: | Brevo source | MailChannels target | | - | - | | Blocked transactional contacts | Suppress as both `transactional` and `non-transactional`. | | Marketing blacklists | Suppress as `non-transactional` unless policy requires a total block. | | Campaign unsubscribes | Preserve as marketing preference data and non-transactional suppression. | ## Marketing migration MailChannels Email API does not replace Brevo contact lists, segmentation, campaigns, or campaign analytics. Keep audience management and campaign scheduling in a separate system, and use MailChannels as the governed delivery layer for permission-based messages that should move through shared-sender controls. ## Cutover checklist * Replace Brevo send endpoint and authentication. * Move SMTP templates into application-owned storage. * Convert template params into `dynamic_template_data`. * Create and publish MailChannels DKIM records. * Recreate transactional webhooks for MailChannels event payloads. * Export or rebuild contact lists and campaign workflows externally. * Split blocked and blacklisted contacts into explicit suppression policy. # Migrate from Mailgun to MailChannels Email API Source: https://docs.mailchannels.com/email-api/migration-center/migrate-from-mailgun Learn how to migrate a Mailgun sending integration to MailChannels sender-risk infrastructure. ## Core difference Mailgun is strongly domain-centric. Sending, tracking, templates, webhooks, suppressions, and routes are often configured per domain. MailChannels is centered on governing many downstream senders through account and sub-account credentials, JSON send payloads, DKIM authentication, Domain Lockdown, per-message tracking settings, webhooks, and account or sub-account suppression policy. ## Feature mapping | Mailgun concept | MailChannels concept | Migration approach | | - | - | - | | Domain-scoped send API | `/send` payload | Replace Mailgun form-data calls with MailChannels JSON payloads. | | Sending domains | DKIM and Domain Lockdown | Recreate authentication with MailChannels DKIM records and DNS publication. | | Account and domain templates | Application-managed Mustache content | Export templates and move storage or versioning outside MailChannels. | | Per-domain tracking | Per-message `tracking_settings` | Convert domain defaults into application send-time decisions. | | Account and domain webhooks | MailChannels webhooks | Recreate webhook endpoint enrollment and update event consumers. | | Domain suppressions | MailChannels suppression list | Collapse domain-scoped suppression behavior into account or sub-account policy. | | Subaccounts | MailChannels sub-accounts | Map subaccounts directly when tenant boundaries should remain separate. | | Inbound routes | No MailChannels Email API equivalent | Replace inbound forwarding or webhook routing with another receiving service. | ## Send payload notes Mailgun send calls commonly use multipart form fields. MailChannels expects JSON. Build a translation layer around the application-level message model rather than copying request fields directly. | Concern | Mailgun behavior | MailChannels behavior | | - | - | - | | Request shape | Form-data fields and domain-specific endpoint | JSON payload sent to `/send` or `/send-async` | | Domain selection | Often implicit in endpoint domain | Explicit sender domain and DKIM configuration | | Recipient variables | Mailgun recipient variables | `dynamic_template_data` inside MailChannels personalizations | | Tracking | Domain-level defaults | Per-message `tracking_settings` | | Webhooks | Account or domain scoped | MailChannels webhook endpoint with different event schema | ## Template migration Mailgun templates can exist at account or domain scope. MailChannels does not provide the same stored-template control plane, so templates should move into application code or an external template store. If a Mailgun template has domain-specific variants, preserve those variants explicitly in the new template naming or configuration model. ## Suppression policy Recommended defaults: | Mailgun source | MailChannels target | | - | - | | Complaints and permanent bounces | Suppress as both `transactional` and `non-transactional`. | | Unsubscribes | Suppress as `non-transactional` unless policy requires blocking all mail. | | Domain-specific suppressions | Decide whether to preserve isolation with MailChannels sub-accounts or centralize suppression at the account level. | ## Inbound migration Mailgun inbound routes do not map to MailChannels Email API resources. Inventory routes early and replace them with a dedicated inbound mail service, application mailbox processing, or another webhook-based receiving architecture. ## Cutover checklist * Replace Mailgun form-data send calls with MailChannels JSON requests. * Move domain-specific tracking defaults into application send settings. * Export and relocate templates. * Create and publish MailChannels DKIM records. * Recreate webhook consumers for MailChannels events. * Decide whether Mailgun subaccounts or domains need MailChannels sub-account isolation. * Replace inbound routes before changing production MX or routing behavior. # Migrate from Mailjet to MailChannels Email API Source: https://docs.mailchannels.com/email-api/migration-center/migrate-from-mailjet Learn how to migrate a Mailjet sending integration to MailChannels sender-risk infrastructure. ## Core difference Mailjet combines transactional sending with sender identity management, stored templates, contact lists, and campaign workflows. MailChannels focuses on governed delivery for platforms with many senders: authenticated API delivery, runtime personalization, webhooks, DKIM, Domain Lockdown, suppression handling, and sender boundaries. Audience storage and campaign orchestration need a separate replacement. ## Feature mapping | Mailjet concept | MailChannels concept | Migration approach | | - | - | - | | Send API v3.1 | `/send` payload | Rewrite transactional sends to MailChannels JSON payloads and authentication. | | Messages and variables | `personalizations` and `dynamic_template_data` | Preserve recipient-specific merge values, but validate the new payload shape. | | Stored templates | Application-managed Mustache content | Export templates and manage them outside MailChannels. | | Sender identities | `from` identities | Keep valid sender addresses while shifting durable trust to domain authentication. | | Sender domains | DKIM and Domain Lockdown | Recreate domain authentication with MailChannels DNS records. | | Event callback URLs | MailChannels webhooks | Recreate callbacks and update consumers for MailChannels event schemas. | | Campaign-excluded contacts | Suppression list | Treat as non-transactional suppressions by default. | | Contact lists and campaigns | External audience and campaign workflow | Replace with an external list manager or campaign system. | ## Send payload notes Mailjet's v3.1 API supports nested messages and variables. In MailChannels, model each recipient through `personalizations` and place merge values in `dynamic_template_data`. | Concern | Mailjet behavior | MailChannels behavior | | - | - | - | | Authentication | Mailjet API key and secret | MailChannels API key authentication | | Sender setup | Sender identity and sender domain controls | Authenticated sender domain with DKIM and Domain Lockdown | | Variables | Mailjet variables | Mustache data in `dynamic_template_data` | | Tracking and callbacks | Event callback URLs and account settings | MailChannels webhook plus per-message tracking settings | | Campaigns | Mailjet campaign product | External workflow with MailChannels as delivery API | ## Template migration Treat Mailjet templates as content assets to export, review, and re-home. Confirm whether each template depends on Mailjet-specific syntax or metadata and convert it to the rendering approach used by the new application. ## Suppression policy Recommended defaults: | Mailjet source | MailChannels target | | - | - | | Campaign exclusions | Suppress as `non-transactional`. | | Hard bounces or complaints from delivery history | Suppress as both `transactional` and `non-transactional` when available. | | List-level unsubscribe intent | Preserve as non-transactional preference data in the external audience system. | ## Marketing migration MailChannels Email API does not replace Mailjet contact lists, newsletters, or campaign scheduling. Export list membership and campaign logic into an external audience workflow, then send permission-based messages through MailChannels when they need governed delivery and sender-risk controls. ## Cutover checklist * Replace Mailjet authentication and send endpoint. * Translate message variables into `dynamic_template_data`. * Move templates out of Mailjet. * Publish and validate MailChannels DKIM records. * Recreate event callbacks as MailChannels webhooks. * Move contact-list and campaign workflows outside MailChannels. * Apply a non-transactional-first policy for campaign exclusions. # Migrate from Postmark to MailChannels Email API Source: https://docs.mailchannels.com/email-api/migration-center/migrate-from-postmark Learn how to migrate a Postmark sending integration to MailChannels sender-risk infrastructure. ## Core difference Postmark is organized around servers and message streams. MailChannels is organized around sender governance for platforms with many senders: account or sub-account credentials, authenticated sending domains, runtime send payloads, webhooks, suppression types, and reputation boundaries. The key migration decision is whether Postmark server and stream boundaries should become MailChannels sub-accounts, application metadata, or a simpler shared account model. ## Feature mapping | Postmark concept | MailChannels concept | Migration approach | | - | - | - | | Server-scoped send API | `/send` payload | Replace server token sends with MailChannels API key sends. | | Servers | Account or sub-account boundaries | Preserve server isolation only when it matters for credentials, reporting, or tenancy. | | Message streams | Transactional flag and application metadata | Map transactional versus broadcast intent into send classification and suppression policy. | | Server templates | Application-managed Mustache content | Export templates and move storage outside Postmark. | | Sender signatures | `from` identities | Preserve sender addresses and align them with authenticated domains. | | Sender domains | DKIM and Domain Lockdown | Recreate DNS authentication through MailChannels. | | Webhooks and legacy hook URLs | MailChannels webhooks | Recreate event delivery and update payload consumers. | | Message-stream suppressions | MailChannels suppression list | Collapse stream-scoped suppressions into account or sub-account policy. | | Inbound processing | No MailChannels Email API equivalent | Replace inbound processing with another receiving architecture. | ## Send payload notes Postmark send calls often carry server and stream intent through the token, endpoint, or message stream field. MailChannels sends should make equivalent intent explicit in account selection, sub-account selection, send classification, and application metadata. | Concern | Postmark behavior | MailChannels behavior | | - | - | - | | Authentication | Account token or server token | MailChannels API key authentication | | Isolation | Server boundary | Account or sub-account boundary | | Stream type | Transactional or broadcast streams | `transactional` send behavior and suppression policy | | Templates | Server templates | Application-owned content plus runtime data | | Inbound | Server inbound processing and triggers | External receiving service | ## Template migration Export templates per Postmark server and preserve the server context in the new template naming scheme when needed. Replace Postmark template aliases or IDs in application code with application-owned template references. ## Suppression policy Recommended defaults: | Postmark source | MailChannels target | | - | - | | Hard bounces and spam complaints | Suppress as both `transactional` and `non-transactional`. | | Broadcast-stream manual suppressions | Suppress as `non-transactional` unless policy requires blocking all mail. | | Stream-scoped suppressions | Preserve isolation with sub-accounts only when stream boundaries are operationally important. | ## Inbound migration Postmark inbound processing and inbound rule triggers do not have MailChannels Email API equivalents. Replace those flows before cutover with a dedicated inbound provider, mailbox polling, or an application webhook receiver backed by another receiving service. ## Cutover checklist * Decide how Postmark servers map to MailChannels account boundaries. * Replace server-token authentication with MailChannels API keys. * Translate message stream intent into send classification and metadata. * Move templates out of Postmark. * Publish and validate MailChannels DKIM records. * Recreate webhook consumers for MailChannels events. * Replace inbound processing before production cutover. # Migrate from Resend to MailChannels Email API Source: https://docs.mailchannels.com/email-api/migration-center/migrate-from-resend Learn how to migrate a Resend sending integration to MailChannels sender-risk infrastructure. ## Core difference Resend provides a modern send API for a team's own mail, with domains, templates, contacts, broadcasts, audiences or segments, webhooks, and receiving-related event signals. MailChannels is built for platforms where users, tenants, customers, or agents are the senders. It adds authenticated outbound delivery, runtime personalization, DKIM, Domain Lockdown, webhooks, sender isolation, and explicit suppression types so you can contain abuse without taking down good senders. Resend broadcast and inbound receiving workflows need separate replacements. ## Feature mapping | Resend concept | MailChannels concept | Migration approach | | - | - | - | | Email send API | `/send` payload | Rewrite requests to MailChannels authentication, endpoint, and payload structure. | | Sending domains | DKIM and Domain Lockdown | Recreate authentication with MailChannels DNS records. | | Stored templates | Application-managed Mustache content | Export templates and move rendering ownership outside Resend. | | Webhooks | MailChannels webhooks | Recreate endpoint registration and update consumers for MailChannels event schemas. | | Contact unsubscribes | Suppression list | Treat as non-transactional suppressions by default. | | Broadcasts | External campaign workflow | Replace broadcast orchestration outside MailChannels. | | Audiences or segments | External audience system | Export or rebuild audience membership in a separate list manager. | | Receiving and `email.received` workflows | No MailChannels Email API equivalent | Replace inbound receiving with another service. | ## Send payload notes Resend send requests are straightforward, but MailChannels uses its own personalization-centered payload and authentication. | Concern | Resend behavior | MailChannels behavior | | - | - | - | | Authentication | Resend API key | MailChannels API key authentication | | Request shape | Resend send payload | MailChannels `/send` or `/send-async` payload | | Merge data | Template or application variables | `dynamic_template_data` in personalizations | | Broadcasts | Resend-managed campaign sends | External campaign system using MailChannels for delivery | | Inbound | Receiving-enabled domains and `email.received` events | External inbound service | ## Template migration Export Resend templates and replace template references in application code with application-owned template names, files, or records. Keep the runtime variable contract explicit so the MailChannels payload provides every value required by the new renderer. ## Suppression policy Recommended defaults: | Resend source | MailChannels target | | - | - | | Contact unsubscribes | Suppress as `non-transactional`. | | Complaints or hard-bounce style delivery failures | Suppress as both `transactional` and `non-transactional` when available. | | Audience or segment preferences | Preserve in the external audience system and mirror to non-transactional suppression where appropriate. | ## Marketing migration MailChannels Email API does not provide a Resend broadcast or audience store. Move broadcast planning, segmentation, approval, and scheduling into an external workflow. Use MailChannels as the governed delivery layer for permission-based messages that should move through shared-sender controls. ## Inbound migration Resend receiving and `email.received` workflows do not map to MailChannels Email API resources. Replace them with a dedicated inbound mail service or application-owned receiving architecture before changing production routing. ## Cutover checklist * Replace Resend endpoint and authentication. * Translate send payloads into MailChannels personalizations. * Move templates out of Resend. * Publish and validate MailChannels DKIM records. * Recreate webhooks for MailChannels event payloads. * Replace broadcasts and audiences externally. * Replace receiving workflows before production cutover. # Migrate from SendGrid to MailChannels Email API Source: https://docs.mailchannels.com/email-api/migration-center/migrate-from-sendgrid Learn how to migrate a SendGrid sending integration to MailChannels sender-risk infrastructure. ## Core difference SendGrid is broad and mature, with JSON send payloads, personalizations, headers, attachments, reply-to values, template-like data, event webhooks, marketing resources, unsubscribe groups, and branded links. MailChannels is built for platforms where many downstream senders share infrastructure and reputation. The migration should move send calls, but it should also map tenant boundaries, domain authorization, suppressions, webhooks, and containment paths so risky senders can be isolated without disrupting legitimate mail. ## Feature mapping | SendGrid concept | MailChannels concept | Migration approach | | - | - | - | | Mail Send API | `/send` payload | Rewrite the send call to MailChannels authentication, endpoint, and payload conventions. Personalizations and attachments usually map cleanly. | | Personalizations | `personalizations` | Preserve recipient-specific variables and addressing, but verify field names and nesting. | | Dynamic template data | `dynamic_template_data` and Mustache content | Keep runtime merge data, but move template rendering into application-controlled content. | | Dynamic transactional templates | Application-managed content | Export and review templates, then store them in application code or a separate template system. | | Verified senders | `from` identities plus authenticated domains | Keep valid `from` addresses, but rely on MailChannels domain authentication for durable production sending. | | Authenticated domains | DKIM and Domain Lockdown | Create MailChannels DKIM records, publish DNS, and validate before cutover. | | Link branding | MailChannels tracking behavior | Review tracking links and deliverability requirements. Do not assume SendGrid-style branded-link resources exist. | | Subusers | MailChannels sub-accounts | Map subusers to sub-accounts when isolation, credentials, billing, or reporting boundaries need to remain separate. | | Unsubscribe groups | Suppression list plus `transactional` flag | Collapse group-specific semantics into MailChannels transactional versus non-transactional suppression policy. | | Event Webhooks | MailChannels webhooks | Recreate endpoint registration and update consumers for MailChannels event schemas and signing behavior. | | Marketing lists and Single Sends | External audience and campaign workflow | Export audience data and replace campaign orchestration outside the MailChannels Email API. | ## Send payload notes SendGrid integrations often require less restructuring than other providers because MailChannels also centers the send request on message content plus `personalizations`. During migration, compare these areas carefully: | Concern | SendGrid behavior | MailChannels behavior | | - | - | - | | API authentication | SendGrid API key bearer token | MailChannels API key authentication | | Endpoint | SendGrid mail-send endpoint | MailChannels `/send` or `/send-async` | | Merge data | Dynamic template data attached to personalizations | `dynamic_template_data` used with Mustache-rendered content | | Tracking | Account, template, or request-level settings | Per-message `tracking_settings` | | Transactional versus marketing | Often modeled through products and unsubscribe groups | Explicit send classification and suppression behavior | ## Template migration Do not treat SendGrid dynamic templates as a target-side object copy. MailChannels sending can use runtime dynamic data, but template storage and versioning should move to application code, a CMS, or another template-management service. Preserve template IDs in your migration notes only long enough to connect old application paths to their new content source. ## Suppression policy Recommended defaults: | SendGrid source | MailChannels target | | - | - | | Bounces, invalid emails, spam reports | Suppress as both `transactional` and `non-transactional`. | | Global unsubscribes | Suppress as `non-transactional` unless business policy requires a total block. | | Unsubscribe groups | Map to `non-transactional` suppressions unless a group clearly applies to transactional mail. | ## Marketing migration MailChannels Email API does not provide a SendGrid Marketing Campaigns equivalent with managed audiences, Single Sends, and campaign scheduling. Move marketing audience storage and campaign orchestration into a separate system, then use MailChannels as the governed delivery layer for permission-based messages that should move through shared-sender controls. ## Cutover checklist * Replace SendGrid endpoint and authentication. * Translate payloads and verify personalization rendering. * Move templates out of SendGrid. * Create and publish MailChannels DKIM records. * Recreate webhooks and update event consumers. * Import or recreate suppressions with clear transactional policy. * Replace Marketing Campaigns workflows externally. * Run test sends for each sending domain before production cutover. # Migration Center Source: https://docs.mailchannels.com/email-api/migration-center/overview Plan a migration from a developer email provider to MailChannels sender-risk infrastructure for platforms with many senders. Use these guides to move sending workflows from another developer email provider to MailChannels Email API. The send call matters, but the larger migration decision is how you will govern many downstream senders without putting shared reputation at risk. Each guide focuses on the operational differences that usually matter during a migration: * Outbound send payload shape * Sender identity and domain authorization * Tenant isolation and sub-account mapping * Policy, limits, and containment paths * Template handling * Webhook and event delivery * Suppression and unsubscribe semantics * Marketing-list or campaign replacement * Inbound-mail feature gaps ## Migration inventory Before you write code, inventory the sending features you use today: | Current feature | MailChannels planning question | | - | - | | API keys, subusers, projects, servers, or tenants | Which boundaries need separate MailChannels sub-accounts, credentials, limits, suppressions, or webhooks? | | Sending domains | Which domains need Domain Lockdown, SPF, DKIM, and DMARC records before production traffic moves? | | Batch or bulk sends | Should the workflow use `/send-async`, webhooks, and per-sender limits to keep risk scoped? | | Tags or metadata | Which sender, tenant, campaign, agent, or application IDs should you preserve for monitoring and remediation? | | Suppressions and bounces | Which bounced or complained recipients should be migrated into your own suppression workflow? | | Webhooks | Which event handlers need new MailChannels event mappings so delivery outcomes become operational signals? | ## Provider guides Migrate SendGrid sending while replacing subuser, template, suppression, and marketing assumptions. Migrate Mailgun domain-scoped sending into account, sub-account, and domain authorization boundaries. Migrate Mailjet sending while moving campaigns and audience governance outside MailChannels. Migrate Brevo senders, templates, blocklists, and campaigns into explicit sender controls. Migrate Postmark servers and streams into MailChannels account, sub-account, or metadata boundaries. Migrate SES identities, configuration sets, events, and regional assumptions into MailChannels controls. Migrate Resend sends, domains, templates, audiences, and broadcasts into a governed delivery layer. ## Common MailChannels concepts | MailChannels concept | Migration meaning | | - | - | | `/send` | Runtime API for governed transactional and personalized messages. | | `/send-async` | Runtime API for larger or queued sends that need webhook-based outcome tracking. | | `personalizations` | Recipient-specific addressing and merge data. | | `dynamic_template_data` | Runtime data available to Mustache-rendered message content. | | `transactional` | Send classification that affects suppression behavior. | | `tracking_settings` | Per-message click, open, and unsubscribe tracking controls. | | DKIM | Domain authentication that requires DNS publication. | | Domain Lockdown | MailChannels control that authorizes which account can send for a protected domain. | | Sub-accounts | Tenant isolation boundaries for credentials, limits, suppressions, webhooks, and suspension paths. | | Webhooks | HTTPS event delivery that lets you monitor outcomes and remediate risky senders. | | Suppression list | MailChannels recipient block list with transactional and non-transactional suppression types. | ## Recommended migration sequence 1. Inventory current sending domains, downstream sender identities, templates, webhooks, suppressions, and any marketing or inbound workflows. 2. Decide whether source-side tenants, servers, subusers, agents, or configuration groups should map to one MailChannels account or to separate MailChannels sub-accounts. 3. Rebuild application send code against MailChannels payloads and authentication. 4. Move provider-managed templates into application code or another template store. 5. Create MailChannels DKIM and Domain Lockdown records and plan DNS publication. 6. Recreate webhook consumers so delivery outcomes feed monitoring, suppression, and remediation workflows. 7. Import or recreate suppressions with explicit transactional versus non-transactional policy. 8. Replace campaign, audience, broadcast, cold outreach, or inbound-mail workflows outside the MailChannels Email API when the source platform provided those features. 9. Run parallel test sends, validate authentication, verify webhooks, and then schedule production cutover. # Multi-tenancy overview Source: https://docs.mailchannels.com/email-api/multi-tenancy Understand the two isolation mechanisms available in Email API, and when to use each. Email API provides two levels of isolation for separating email traffic: **sub-accounts** and **sender IDs**. They operate at different layers and can be used independently or together. | | Sub-account | Sender ID | | - | - | - | | Separate API keys | Yes | No | | Separate sending limits | Yes | No | | Separate suppression list | Yes | No | | Separate webhooks | Yes | No | | Separate usage reporting | Yes | No | | Can be suspended | Yes | No | | Reputation isolation | Yes | Yes | ## Sub-accounts ### What are sub-accounts? Sub-accounts allow you to create and manage many distinct sending identities under your main account. This feature is useful for agencies, resellers, or organizations that need to manage email sending for multiple clients or departments while maintaining clear separation between them. Each sub-account has its own credentials, sending controls, suppression list, webhook configuration, and usage reporting. ### When to use sub-accounts Sub-accounts are useful when you need separate credentials, limits, or operational controls. | Use case | Why use a sub-account | | - | - | | Agency or reseller sending for multiple clients | Keep each client isolated with its own credentials and usage. | | Multi-tenant SaaS application | Limit the impact of one tenant's sending behavior on other tenants. | | Separate business units or brands | Give each group independent API keys, SMTP passwords, webhooks, and suppressions. | | Migration from providers with subusers, servers, or projects | Preserve existing tenancy boundaries while moving to MailChannels. | ### When not to use sub-accounts Do not create a sub-account just to send from another domain. If the same application and team own the traffic, Domain Lockdown, SPF, DKIM, and DMARC may be enough. Every sub-account adds another operational boundary. Each one comes with separate credentials, suppressions, webhooks, usage reporting, and limits. Those boundaries are helpful when different clients, tenants, teams, or applications need to be managed independently. They add unnecessary overhead when the only difference is the domain in the `from.email` address. For example, a SaaS product might send billing mail from `billing.example.com`, product alerts from `notifications.example.com`, and support updates from `support.example.com`. If the same engineering team owns all three streams and they share the same operational process, create the required Domain Lockdown records and authentication records for each domain instead of creating three sub-accounts. Use sub-accounts only when you need a separate owner, credential set, suppression policy, webhook endpoint, usage limit, or incident response path. ### Sub-accounts and suppression lists Each sub-account has its own suppression list, separate from the parent account's. A recipient on the parent account's list is not blocked from receiving mail from a sub-account unless they are also on that sub-account's list. To read or write a sub-account's suppression list, authenticate the request with the **sub-account's own API key** — not the parent's. See the [suppression list documentation](/email-api/suppression-api-actions) for the available endpoints. ### Sub-accounts and delivery events Each sub-account manages its own delivery-event webhook, separate from the parent account's. Events from a sub-account's messages are delivered only to that sub-account's webhook; the parent account does not receive a copy. To enroll, list, validate, or remove a sub-account's webhook, authenticate the request with the **sub-account's own API key**, not the parent's. See the [webhooks documentation](/email-api/webhooks) for the available endpoints and event schema. ## Sender IDs ### What is a sender ID? A sender ID is a label that Email API assigns to each outbound message based on characteristics found in the message headers. It acts as a **reputation boundary**: messages sharing the same sender ID are grouped together for delivery reputation tracking, so poor sending behavior from one traffic stream does not automatically affect others. Sender IDs are scoped to the account or sub-account that sends the message. If two sub-accounts happen to derive the same sender ID value from their message headers, those values remain separate because they are always qualified by the sub-account they belong to. ### What sender IDs provide Sender IDs give you reputation isolation within a single set of credentials. You can separate the sending reputation of distinct traffic streams, without creating a new sub-account for each stream. Unlike sub-accounts, sender IDs do not provide separate API keys, sending limits, suppression lists, webhooks, or usage reporting. They are a lighter-weight boundary suited to cases where operational separation is not required but reputation isolation is still useful. ### When to use sender IDs | Use case | Why use a sender ID | | - | - | | Separating transactional and promotional mail within one account | Protect transactional reputation from promotional sending risk, without splitting credentials. | | Distinguishing mail streams within a sub-account | Identify and isolate a specific integration, product, or campaign at the reputation level. | | Tracking reputation per sending use case for a single tenant | Get per-stream visibility without the overhead of managing multiple sub-accounts. | ## Choosing the right boundary Use this guide to help decide which mechanism to use. **Use a sub-account when:** * Different customers, tenants, or teams need their own API keys or SMTP credentials. * You need to enforce separate sending quotas or limits. * Each sender needs its own suppression list and webhook endpoint. * You need to be able to suspend or resume a tenant's sending independently. **Use a sender ID when:** * A single account or sub-account sends meaningfully different streams of mail, for example, transactional and promotional, and you want to isolate their reputations. * You want per-stream reputation tracking without the overhead of managing separate credentials, limits, and webhooks. # Send email from OpenClaw agents using MailChannels Source: https://docs.mailchannels.com/email-api/openclaw Configure an OpenClaw AI agent to send and track email through MailChannels, with a dedicated inbox via AgentMail for inbound replies. [OpenClaw](https://github.com/openclaw/openclaw) is an open-source AI agent framework that lets you build and run autonomous agents capable of executing tools and skills. One of the most common needs when running an agent is the ability to send email — for notifications, outreach, confirmations, and follow-ups. This guide walks you through connecting OpenClaw to the MailChannels Email API for outbound sending and delivery tracking, and optionally to AgentMail for giving your agent its own dedicated inbox. ## How the pieces fit together * **OpenClaw** runs your agent and handles tool and skill execution. * **MailChannels Email API** handles sending email and reports delivery events (delivered, bounced, dropped) back to your agent via webhooks. * **AgentMail** (optional, but recommended) gives your agent a dedicated inbox — separate from your personal email — with support for real-time events and reply handling. Each piece does one job, and your agent only gets the permissions it actually needs. ## What you need before you start * A self-hosted OpenClaw installation. * MailChannels Email API credentials: `MAILCHANNELS_API_KEY` and `MAILCHANNELS_ACCOUNT_ID` (your customer handle). * DNS access for the domain you plan to send from. * Optionally: a public HTTPS endpoint where MailChannels can POST delivery webhooks. ## Setting up the integration The MailChannels skill for OpenClaw reads two environment variables: * `MAILCHANNELS_API_KEY` — sent in the `X-Api-Key` header on every request. * `MAILCHANNELS_ACCOUNT_ID` — your customer handle, visible in the [MailChannels Console](https://console.mailchannels.net). There is also an optional override if you need a different base URL: * `MAILCHANNELS_BASE_URL` — defaults to `https://api.mailchannels.net/tx/v1`. Export them in your shell before launching OpenClaw: ```bash theme={null} export MAILCHANNELS_API_KEY="YOUR_API_KEY" export MAILCHANNELS_ACCOUNT_ID="YOUR_ACCOUNT_ID" # Optional: export MAILCHANNELS_BASE_URL="https://api.mailchannels.net/tx/v1" ``` Before letting your agent send email from your domain, configure **Domain Lockdown** via a DNS TXT record. This ensures only your MailChannels account can send as that domain. For each domain you plan to send from, add a TXT record: | Field | Value | | - | - | | **Host** | `_mailchannels.` | | **Value** | `v=mc1; auid=` | After adding the record, you can verify your full domain configuration (DKIM, SPF, and Domain Lockdown) using the `POST /check-domain` endpoint. Your domain should also have a valid SPF record that authorizes MailChannels as a sender. Without SPF, deliverability will be poor and some receiving servers will reject your mail. The MailChannels integration is published as an OpenClaw skill called `mailchannels-email-api`. Install it with: ```bash theme={null} npx openclaw add @ttulttul/mailchannels ``` This skill handles two things: sending email through the MailChannels API, and ingesting signed delivery-event webhooks into your OpenClaw environment. MailChannels exposes a `POST /send` endpoint. The minimum payload requires four fields: `personalizations` (who you are sending to), `from`, `subject`, and `content`. Here is a `curl` example showing what happens under the hood: ```bash theme={null} curl -sS -X POST "$MAILCHANNELS_BASE_URL/send" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "personalizations": [ { "to": [ { "email": "you@example.com" } ] } ], "from": { "email": "agent@your-domain.com", "name": "OpenClaw Agent" }, "subject": "Hello from OpenClaw via MailChannels", "content": [ { "type": "text/plain", "value": "This email was sent by my agent using the MailChannels Email API." } ] }' ``` Once the skill is installed and your environment variables are set, you can skip `curl` entirely and just instruct your agent: > Send an email to me using MailChannels with the subject "Test" and a one-line body confirming it worked. The agent takes it from there. In agent workflows, you usually do not want your agent blocking while an email is processed — especially when sending to multiple recipients. Use `POST /send-async` instead of `POST /send`. This endpoint queues the message and returns immediately with a request ID. Delivery events still arrive via webhooks, so your agent retains full visibility and can move on to its next task without waiting. Sending is only half the story. A genuinely useful agent can also answer: Did it deliver? Did it bounce? Should I retry or suppress that recipient? **Register a webhook endpoint** Use the following endpoints to manage your webhook subscriptions: | Endpoint | Description | | - | - | | `POST /webhook` | Enroll for webhook notifications | | `GET /webhook` | Retrieve enrolled webhooks | | `DELETE /webhook` | Remove webhooks you no longer need | | `POST /webhook/validate` | Test that your endpoint is reachable | | `GET /webhook/public-key` | Retrieve the webhook signing key | **Verify webhook signatures** MailChannels signs every delivery-event webhook. Incoming requests include three headers: `Content-Digest`, `Signature-Input`, and `Signature`. To verify them: 1. Parse `Signature-Input` — it includes `created`, `alg`, and `keyid`. 2. Reject anything with a stale timestamp. 3. Fetch the public key for the given `keyid` using `GET /webhook/public-key`. 4. Verify the Ed25519 signature per RFC 9421. **Correlate events back to your agent's actions** Webhook payloads include `email`, `customer_handle`, `timestamp`, `event`, and `request_id`. Persist the `request_id` from each send call so you can match it to delivery events and build a clean state machine: processed → delivered, soft-bounced, hard-bounced, or dropped. This is what lets your agent make smart decisions — retry on a soft bounce, suppress on a hard bounce, escalate on repeated failures. Everything above handles outbound email. Agents often also need to *receive* messages — replies, confirmations, OTP codes, inbound support requests. **AgentMail** is an API-first inbox provider built specifically for AI agents. Install the official OpenClaw integration with either: ```bash theme={null} openclaw skills install agentmail-to/agentmail-skills/agentmail ``` or via the ClawHub CLI: ```bash theme={null} npx clawhub@latest install agentmail ``` Then add your AgentMail API key to `~/.openclaw/openclaw.json`: ```json theme={null} { "skills": { "entries": { "agentmail": { "enabled": true, "env": { "AGENTMAIL_API_KEY": "your-api-key-here" } } } } } ``` AgentMail supports programmatic inbox creation, including custom domains. Once configured, your agent can receive replies in a dedicated inbox, search and triage messages, and trigger workflows from inbound email — all without touching your personal mailbox. ## Ideas for what to build next With MailChannels handling outbound and AgentMail handling inbound, your OpenClaw agent is ready for workflows that feel hands-free but stay operationally sound: * **Daily briefings** — Your agent compiles calendar items, tasks, and news into a single email, sends it via MailChannels, and tracks delivery. * **Support triage** — Inbound email arrives at AgentMail, the agent labels and routes it, then sends a reply (or drafts one for your approval) through MailChannels. * **Deliverability-aware automation** — If a recipient hard-bounces, the agent automatically adds them to the suppression list and notifies your ops team. ## API quick reference **Base URL:** `https://api.mailchannels.net/tx/v1` | Category | Endpoint | Description | | - | - | - | | Sending | `POST /send` | Send an email synchronously | | Sending | `POST /send-async` | Queue an email and return immediately | | Webhooks | `POST /webhook` | Enroll for webhook notifications | | Webhooks | `GET /webhook` | Retrieve enrolled webhooks | | Webhooks | `DELETE /webhook` | Delete webhooks | | Webhooks | `POST /webhook/validate` | Test your webhook endpoint | | Webhooks | `GET /webhook/public-key` | Retrieve the webhook signing key | | Domains | `POST /check-domain` | Check DKIM, SPF, and Domain Lockdown | | Usage | `GET /usage` | Retrieve account usage stats | | Suppression | `POST /suppression-list` | Add suppression entries | | Suppression | `GET /suppression-list` | Retrieve the suppression list | | Suppression | `DELETE /suppression-list/recipients/:recipient` | Remove a suppression entry | # MailChannels Email API: transactional email for platforms Source: https://docs.mailchannels.com/email-api/overview Send transactional email from apps with many users, tenants, customers, or agents. MailChannels adds sender controls that help protect shared reputation. MailChannels Email API is a transactional email delivery API for platforms with many senders. It helps you authorize downstream senders, contain abuse quickly, and keep legitimate mail flowing across shared reputation. Most developer email tools are optimized for a company sending its own mail. MailChannels is built for apps where your users, tenants, customers, or agents send through your product. ## How it works Your application calls the MailChannels API endpoint with the message details: sender, recipients, subject, and body. MailChannels validates the request against your domain configuration, evaluates sender-risk signals, applies the right controls, and delivers accepted messages to the recipient's mail server. The API uses standard JSON over HTTPS, so you can integrate it from any language or platform that can make HTTP requests. ## Choose the right starting point Use the path that matches what you are building: | Goal | Start here | | - | - | | Send your first transactional email | [Quickstart](/email-api/curl/quickstart) | | Send from Node.js | [Node.js quickstart](/email-api/javascript/quickstart) | | Authorize a downstream sending domain | [Domain Lockdown](/email-api/domain-lockdown) | | Improve domain authentication | [SPF, DKIM, and DMARC](/email-api/spf-dkim-dmarc) | | Track delivery outcomes | [Webhooks](/email-api/webhooks) | | Isolate tenants or traffic streams | [Multi-tenancy](/email-api/multi-tenancy) | | Understand abuse controls | [Reputation and abuse control](/email-api/reputation-abuse-control) | | Move from another provider | [Migration Center](/email-api/migration-center/overview) | ## Key benefits **Authorize downstream senders.** Domain Lockdown™ ties each sending domain to your account so another customer cannot send through MailChannels as that domain. **Detect risky senders.** Behavioral scoring, content signals, recipient feedback, and sender history help identify the source of risk before one sender's behavior becomes everyone's delivery problem. **Contain abuse fast.** Sub-accounts, sending limits, suppressions, suspensions, and policy enforcement keep abuse scoped to the sender or tenant causing the problem. **Keep good mail flowing.** Shared reputation stays protected so legitimate senders can continue delivering while your operators investigate and remediate the source of abuse. **Operate with evidence.** Signed webhooks, delivery events, metrics, and troubleshooting data give abuse, support, and engineering teams the signals they need to act. ## What you need to get started Before you can send your first email, you need three things: 1. A MailChannels account, created at [dash.mailchannels.net](https://dash.mailchannels.net) 2. An API key with the `api` scope, generated in the Console 3. A Domain Lockdown DNS TXT record for each domain you send from The quickstart guide walks you through each of these steps and ends with a working example. ## Production checklist Before you send production traffic, verify each item: * Store your API key in a secrets manager or environment variable. * Add a Domain Lockdown record for every domain used in the `from.email` field. * Publish SPF for each sending domain. * Configure DKIM signing and publish the DKIM DNS record. * Add a DMARC record, starting with a monitoring policy such as `p=none`. * Create a webhook endpoint and store delivery events for operational troubleshooting. Sign up, create an API key, configure your domain, and send your first email in minutes. Learn how API keys work and how to pass the `X-Api-Key` header in every request. Add the DNS TXT record that authorizes your domain to send through MailChannels. Review plans, usage limits, and billing details for the Email API. # PHP quickstart Source: https://docs.mailchannels.com/email-api/php/quickstart Send an email with the MailChannels Email API using PHP. Send an email in a few minutes. This guide walks through sending your first request to the MailChannels Email API. The PHP SDK is open source. Browse the [source code](https://bitbucket.org/mailchannels/mailchannels-email-api-sdk-php/). ## Prerequisites Before your first request, complete the following steps. [Sign up](https://dash.mailchannels.com) if you don't already have an account. Open [Account > API Keys](https://dash.mailchannels.com/account/api-keys) and click **Create API Key**. Add a descriptive label and set the scope to `api`. Copy the key; you won't be able to see it again. Treat your API key like a password. Don't commit it to source control or expose it in client-side code. For each domain you'll send from, add a `TXT` record on the `_mailchannels` subdomain authorizing your account to send. Replace `examplecorp` with your MailChannels account ID (visible in the panel footer when you log in): ``` _mailchannels.example.com TXT "v=mc1 auth=examplecorp" ``` See the [Domain Lockdown guide](/email-api/domain-lockdown) for details. Add MailChannels to the SPF record on each sending domain so recipient servers accept your mail: ``` example.com TXT "v=spf1 include:relay.mailchannels.net ~all" ``` If you already have an SPF record, merge in `include:relay.mailchannels.net` rather than creating a second record. A domain must have only one SPF record. Your sending domain must resolve. It needs a valid `A` or `MX` record. Without one, mail from the domain is rejected with `550 5.1.2 [SDNF] Sender Domain Not Found`. Most registered domains already have these records. Confirm yours with the [check-domain tool](https://dash.mailchannels.com/domain-health). See [Troubleshooting](/email-api/troubleshooting) for details. ## Install the PHP SDK ```php PHP theme={null} composer require mailchannels/mailchannels-php # You also need a PSR-18 client and PSR-17 factories. Guzzle is a popular choice. composer require guzzlehttp/guzzle ``` Using a PHP framework? See the [Laravel](/email-api/laravel), [Symfony](/email-api/symfony) integration guides. ## Send your first email To send an email, provide the recipients, sender, subject, and message content. The example below sends an HTML email to a single recipient. Until you add a payment method, sending is restricted to addresses belonging to verified [Users](https://dash.mailchannels.com/account/users) **on your account**. Add a user, click the link in the verification email, then use that address as the `to` email below. ```php PHP theme={null} emails->send([ 'from' => ['email' => $from_email, 'name' => 'Your Name'], 'to' => ['email' => $to_email, 'name' => 'Recipient'], 'subject' => 'Hello from MailChannels', 'text' => 'Hello! This is a plain-text fallback for clients that don\'t render HTML.', 'html' => '

Hello,

This is my first HTML email sent via the MailChannels Email API.

', ]); print_r($response->toArray()); ```
Set the following before running: * `MAILCHANNELS_API_KEY` — the API key generated above. * `FROM_EMAIL` — an address on the domain you configured with Domain Lockdown and SPF above (e.g. `hello@example.com` if you used `example.com` in the Domain Lockdown and SPF DNS records). * `TO_EMAIL` — a recipient inbox you can check, e.g. the address you use to sign up for MailChannels. ## Next steps * Read the [webhooks guide](/email-api/webhooks) to learn how to get real-time notifications about email delivery, bounces, and complaints. * Explore the [API reference](/email-api/api-reference-introduction) to see what else you can do with the API. # Python quickstart Source: https://docs.mailchannels.com/email-api/python/quickstart Send an email with the MailChannels Email API using Python. Send an email in a few minutes. This guide walks through sending your first request to the MailChannels Email API. The Python SDK is open source. Browse the [source code](https://bitbucket.org/mailchannels/mailchannels-email-api-sdk-py/). ## Prerequisites Before your first request, complete the following steps. [Sign up](https://dash.mailchannels.com) if you don't already have an account. Open [Account > API Keys](https://dash.mailchannels.com/account/api-keys) and click **Create API Key**. Add a descriptive label and set the scope to `api`. Copy the key; you won't be able to see it again. Treat your API key like a password. Don't commit it to source control or expose it in client-side code. For each domain you'll send from, add a `TXT` record on the `_mailchannels` subdomain authorizing your account to send. Replace `examplecorp` with your MailChannels account ID (visible in the panel footer when you log in): ``` _mailchannels.example.com TXT "v=mc1 auth=examplecorp" ``` See the [Domain Lockdown guide](/email-api/domain-lockdown) for details. Add MailChannels to the SPF record on each sending domain so recipient servers accept your mail: ``` example.com TXT "v=spf1 include:relay.mailchannels.net ~all" ``` If you already have an SPF record, merge in `include:relay.mailchannels.net` rather than creating a second record. A domain must have only one SPF record. Your sending domain must resolve. It needs a valid `A` or `MX` record. Without one, mail from the domain is rejected with `550 5.1.2 [SDNF] Sender Domain Not Found`. Most registered domains already have these records. Confirm yours with the [check-domain tool](https://dash.mailchannels.com/domain-health). See [Troubleshooting](/email-api/troubleshooting) for details. ## Install the Python SDK ```python Python theme={null} # pip pip install mailchannels # uv uv add mailchannels ``` ## Send your first email To send an email, provide the recipients, sender, subject, and message content. The example below sends an HTML email to a single recipient. Until you add a payment method, sending is restricted to addresses belonging to verified [Users](https://dash.mailchannels.com/account/users) **on your account**. Add a user, click the link in the verification email, then use that address as the `to` email below. ```python Python theme={null} import os import sys import mailchannels # The SDK reads MAILCHANNELS_API_KEY from the environment automatically. if not os.environ.get("MAILCHANNELS_API_KEY"): sys.exit("Set MAILCHANNELS_API_KEY before running") from_email = os.environ.get("FROM_EMAIL") to_email = os.environ.get("TO_EMAIL") if not from_email: sys.exit("Set FROM_EMAIL (must be on a Domain-Lockdown-authorized domain)") if not to_email: sys.exit("Set TO_EMAIL") response = mailchannels.Emails.send( { "from": {"email": from_email, "name": "Your Name"}, "to": [{"email": to_email, "name": "Recipient"}], "subject": "Hello from MailChannels", "text": "Hello! This is a plain-text fallback for clients that don't render HTML.", "html": ( "

Hello,

" "

This is my first HTML email sent via the " 'MailChannels Email API.

' ), } ) print(response) ```
Set the following before running: * `MAILCHANNELS_API_KEY` — the API key generated above. * `FROM_EMAIL` — an address on the domain you configured with Domain Lockdown and SPF above (e.g. `hello@example.com` if you used `example.com` in the Domain Lockdown and SPF DNS records). * `TO_EMAIL` — a recipient inbox you can check, e.g. the address you use to sign up for MailChannels. ## Next steps * Read the [webhooks guide](/email-api/webhooks) to learn how to get real-time notifications about email delivery, bounces, and complaints. * Explore the [API reference](/email-api/api-reference-introduction) to see what else you can do with the API. # Reputation and abuse control Source: https://docs.mailchannels.com/email-api/reputation-abuse-control Learn how MailChannels evaluates outbound email traffic at granular levels to stop abuse while protecting legitimate senders. MailChannels protects outbound email reputation with a multi-stage filtering pipeline built for transactional email from dynamic, customer-controlled sources. Messages can originate from applications, agents, CMS plugins, SaaS tenants, sub-accounts, scripts, and other automated systems. The pipeline evaluates identity, infrastructure, content, recipient patterns, and sending behavior before choosing how a message should be handled. This page explains the architecture at a high level. It does not expose proprietary thresholds, scoring formulas, model features, or threat intelligence sources. ## Granular identity tracking Before MailChannels evaluates message content, it first determines who is responsible for the traffic. The platform normalizes multiple signals into sender-level tracking context so abuse can be attributed more precisely than the visible `from.email` address. For Email API traffic, that context can include: * The parent account. * The sub-account, when a sub-account API key is used. * The sending domain authorized by Domain Lockdown. * The `campaign_id`, when included in the send request. * Envelope and header details that help correlate related traffic. This lets MailChannels separate legitimate traffic from suspicious traffic at narrow boundaries. For example, a reseller can isolate each client with sub-accounts, then use `campaign_id` to distinguish campaigns or segments inside a client sub-account. If one campaign starts producing abnormal bounce or complaint patterns, that traffic can be understood in context without treating every message from the reseller as identical. ## Policy and infrastructure checks MailChannels validates sender and account context before and during delivery. These checks help prevent spoofing, unauthorized sending, quota abuse, and infrastructure patterns commonly associated with compromised systems. Examples include: * Confirming that the sender is authorized through Domain Lockdown. * Evaluating SPF, DKIM, and DMARC posture for sending domains. * Checking account, sub-account, plan, and usage constraints. * Comparing sender, domain, IP, and URL signals with internal and external reputation data. * Applying additional scrutiny to new domains, new sender identities, or traffic patterns without an established history. These controls help stop abuse before it harms shared infrastructure or recipient trust. ## Content and payload analysis Each message is evaluated for signs of spam, phishing, malware, credential theft, and other abusive behavior. MailChannels uses layered scanning rather than relying on one signal. The platform can evaluate: * Message body and attachment characteristics. * URLs and domains found in the message. * Header structure and message formatting. * Sender and reply-to alignment. * Patterns commonly produced by compromised scripts or automated abuse tools. * Similarities between messages that may be part of the same campaign, even when they use different sender addresses. This layered approach lets MailChannels distinguish a normal transactional receipt from a credential phishing attempt, a malware attachment, or a sudden burst of low-quality bulk mail. ## Behavioral profiling over time Abuse is often visible only when messages are viewed as a stream. MailChannels evaluates sender behavior across short and longer time windows so it can detect changes that a single-message scan may miss. Behavioral signals can include: * Message volume and sending velocity. * Recipient validity and bounce patterns. * Complaint and unsubscribe behavior. * Repeated delivery failures. * Sudden changes in content, domains, URLs, or recipient lists. * New sender identities that ramp volume before building a positive history. This is especially important for shared platforms and SaaS products. One tenant, client, script, or campaign can be handled differently from the rest of the account when its behavior diverges from normal traffic. ## Recipient list and hygiene signals Recipient quality is a major reputation signal. Poor list hygiene can look similar to abuse, especially when a sender hits many invalid addresses, spam traps, or unrelated recipient patterns in a short time. MailChannels evaluates recipient behavior and list structure to identify: * Purchased or scraped lists. * Dictionary-style address generation. * High invalid-recipient rates. * Repeated hard bounces. * Complaint-heavy recipient groups. * Suppression-list conflicts. Use [suppressions and unsubscribe handling](/email-api/suppressions-unsubscribes) to keep bad recipients out of future sends and to honor recipient preferences. ## Routing and enforcement After MailChannels combines identity, policy, content, recipient, and behavioral signals, the platform chooses an enforcement outcome. The goal is to protect legitimate senders and recipient networks while containing suspicious or abusive traffic as narrowly as possible. Possible outcomes include: | Outcome | What it means | | - | - | | Accept | The message is queued for delivery. | | Defer | Sending is temporarily slowed or retried later when the traffic needs more control. | | Reject | The message is blocked because it violates policy or reputation safeguards. | | Drop | The message is stopped before delivery and reported through delivery events when applicable. | | Route with additional controls | Traffic may be handled differently based on risk, sender history, or reputation context. | These decisions are dynamic. A clean sender with stable behavior should not be treated the same way as a newly observed sender with a sudden spike in invalid recipients, suspicious URLs, or complaint-heavy traffic. ## What this means for Email API users For the specific things to do (and avoid) as a sender, see [sending policies and best practices](/email-api/sending-policies). # Security Source: https://docs.mailchannels.com/email-api/security Protect MailChannels Email API credentials, webhook consumers, and message data in production systems. Treat your email integration as production infrastructure. It can send messages to customers, expose personal data in logs, and affect your sender reputation. ## API keys * Store API keys in a secrets manager or environment variables. * Use separate keys for development, staging, and production. * Use one key per application or service. * Rotate keys on a schedule and immediately after suspected exposure. * Revoke unused keys. * Never expose API keys in client-side code, mobile apps, screenshots, logs, or support tickets. ## Webhook security * Verify every webhook signature before trusting the payload. * Reject signatures outside a short freshness window. * Store the raw body before parsing. * Make processing idempotent because replay and duplicate delivery can happen during recovery workflows. * See the [webhook](/email-api/webhooks) documentation for more details on webhooks and best practices. ## Data handling Email content and metadata can contain personal data. Avoid logging full message bodies, private DKIM keys, API keys, unsubscribe links, and sensitive recipient data. When support needs examples, provide `request_id`, timestamps, and sender and recipient domain. ## Transport security MailChannels sends outbound email over TLS when the receiving mail server supports it. ## Security contact If you believe you found a security issue, contact MailChannels support with a clear description, reproduction steps, affected endpoints, and any relevant timestamps. # Send vs send async Source: https://docs.mailchannels.com/email-api/send-vs-send-async Learn the differences between sending emails synchronously and asynchronously. ## Synchronous vs asynchronous sending There are two ways to send an email: synchronously and asynchronously. When you send an email synchronously, your application waits for the API to process the request and return a response. For a single email, this is usually pretty quick. But for larger sends, the processing time can take much longer. When you send an email asynchronously, your application makes a request and immediately receives a response. In the background, the API processes the request. Your application does not wait. In order to track the status of the send, you will receive webhooks for all delivery status updates (dropped, processed, delivered, hard-bounced, etc.). These webhooks are identical to the webhooks you receive for synchronous sends. ## When to use Send vs Send Async Synchronous sending should only be used when the user needs feedback on the status of the send before proceeding, and the number of emails being sent is small. Our evaluation process can take several seconds per message. Asynchronous sending should be used for most requests. It is important to have webhooks set up to receive delivery status updates. You can view webhook events on the [webhooks page](https://dash.mailchannels.com/webhooks). For more information on webhooks, see the [webhooks documentation](/email-api/webhooks). ## Examples Send synchronously: ```bash cURL theme={null} #!/usr/bin/env bash # Send an email synchronously set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}" : "${FROM_EMAIL:?Set FROM_EMAIL (must be on a Domain-Lockdown-authorized domain)}" : "${TO_EMAIL:?Set TO_EMAIL}" curl -X POST https://api.mailchannels.net/tx/v1/send \ -H "Content-Type: application/json" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" \ -d @- <emails->send([ 'from' => ['email' => $from_email, 'name' => 'Your Name'], 'to' => ['email' => $to_email, 'name' => 'Recipient'], 'subject' => 'Hello from MailChannels (sync)', 'text' => 'This message was sent with emails->send. The caller waited for the response.', ]); print_r($response->toArray()); ``` Send asynchronously: ```bash cURL theme={null} #!/usr/bin/env bash # Send an email asynchronously. set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}" : "${FROM_EMAIL:?Set FROM_EMAIL (must be on a Domain-Lockdown-authorized domain)}" : "${TO_EMAIL:?Set TO_EMAIL}" curl -X POST https://api.mailchannels.net/tx/v1/send-async \ -H "Content-Type: application/json" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" \ -d @- <queue posts to /tx/v1/send-async. Delivery events are reported via webhooks. $response = $client->emails->queue([ 'from' => ['email' => $from_email, 'name' => 'Your Name'], 'to' => ['email' => $to_email, 'name' => 'Recipient'], 'subject' => 'Hello from MailChannels (async)', 'text' => 'This message was sent with emails->queue. Delivery status will arrive via webhook.', ]); print_r($response->toArray()); ``` # Sending policies and best practices Source: https://docs.mailchannels.com/email-api/sending-policies Follow these practices so your traffic stays in good standing with MailChannels and reaches the inbox. How to prevent blocks and get your traffic accepted. For the controls behind these rules, see [reputation and abuse control](/email-api/reputation-abuse-control). ## Reading a rejection Rejections take the shape `550 5.7.1 [CODE] reason.` The bracketed code tells you which control fired. These codes are designed to help you understand why a message was rejected. They are not part of the SMTP standard and can change at any time as we update our policies. Below are some examples of common rejection codes and their meanings. ## Authenticate every sending domain * Configure [Domain Lockdown](/email-api/domain-lockdown) for every domain you send from. * Publish [SPF, DKIM, and DMARC](/email-api/spf-dkim-dmarc) records before you scale. * Keep `from.email`, `reply_to`, and the DKIM signing domain aligned. * Never send on behalf of domains you do not control. Rejections when authentication is wrong: ``` 550 5.7.1 This sender is not authorized to send from example.com 550 5.7.1 [BFD] Sender prohibited by SPF 550 5.7.1 [ESA] Sender blocked. ``` ## Segment your traffic * Use a separate [sub-account](/email-api/multi-tenancy) for each client, tenant, or application. * Use consistent headers within each mail stream. MailChannels derives a `sender-id` from message headers, so consistency allows each stream to build its own reputation history. * Set `campaign_id` per send for campaign-level metrics, webhook correlation, and isolation. * Use distinct domains or subdomains for transactional and marketing mail. ## Send only to recipients who asked for mail * Send only to recipients with explicit consent. Do not buy, scrape, or rent lists. * Do not send speculatively to generated or role addresses (`info@`, `sales@`, sequential local parts). * Validate addresses at signup. Drop typos and disposable domains. Rejections when list hygiene slips: ``` 550 5.7.1 [BR] Our system has detected an excessively high number of invalid recipients originating from your account. 550 5.7.1 [UNS] Recipient has unsubscribed from your list. 550 5.7.1 [RB] This recipient email address has been blocked. ``` ## Keep message content honest * Use a stable `from.email` per mail stream. Do not rotate sender addresses to evade filters. * Use HTTPS links to domains you control. Avoid URL shorteners and redirector chains. * Avoid executables, scripts, and password-protected archives as attachments. * Match the HTML and plain-text bodies. Do not hide content in one. Rejections from content and header scans: ``` 550 5.7.1 [CS] Our system has detected that this message is a virus. 550 5.7.1 [CS] Message blocked. If this is a false positive, please report this to your hosting service provider. 550 5.7.1 [CSR] Account blocked. See 550 5.7.1 [FEB] Your message could not be delivered because it contained an attachment type that is not allowed. 550 5.7.1 [SHF] Message rejected. 550 5.7.1 [XM] Blocked. ``` ## Ramp new traffic gradually * Warm new domains over days or weeks. Start low, grow as engagement stays healthy. * Treat a new sub-account, `campaign_id`, or recipient segment the same way. * Keep daily volume consistent. Unexplained spikes attract scrutiny. Velocity and quota rejections: ``` 550 5.7.1 [STF] Sending too fast. 550 5.7.1 [NS] Our system has detected an unusual amount of unsolicited email originating from your address. 550 5.7.1 This sender has exceeded their daily limit for email api requests. 451 4.7.1 Sending quota exceeded. ``` ## Process feedback signals * Subscribe to [webhooks](/email-api/webhook-event-types) and process them in your sending pipeline. * Watch [metrics](/email-api/metrics) for bounce rate, complaint rate, and engagement trends. # Sending via SMTP Source: https://docs.mailchannels.com/email-api/smtp-sending Learn how to send emails via SMTP. ## Overview Along with the HTTP Email API, you can also send mail via SMTP. This guide walks through sending your first email using the MailChannels SMTP API. ## Prerequisites Open [Account > SMTP Passwords](https://dash.mailchannels.com/account/smtp-credentials) and click **Create SMTP Credentials**. Treat your SMTP password like a password. Don't commit it to source control or expose it in client-side code. Add MailChannels to the SPF record on each sending domain so recipient servers accept your mail. See the [SPF page](/email-api/spf-dkim-dmarc) for more details. [Domain Lockdown](/email-api/domain-lockdown) is required for SMTP sending as well. Configure your MTA to relay through `smtp.mailchannels.net` on port `587` with TLS. Use your MailChannels account ID as the username and the SMTP password you created as the password. ## Configuring your MTA The [mail server setup guide](/outbound/configure-mail-server) will help you to configure your MTA to send via MailChannels. ## SMTP vs Email API MailChannels does not DKIM sign messages sent via SMTP. However, DKIM signatures on mail sent via SMTP will not be broken. Messages sent via SMTP do not produce webhooks. # Handling soft-bounced events Source: https://docs.mailchannels.com/email-api/soft-bounced-events Learn about the best practices for responding to soft-bounced webhook events. ## What is a soft-bounced event? A soft-bounced event is generated when a recipient's server temporarily rejects an email (returning a 4xx SMTP status code). The event data includes the specific reason for the bounce, for example, if there's a connection timeout or a full mailbox. MailChannels will automatically retry delivery for soft-bounced messages for up to six hours, or until the message is successfully delivered. If the message cannot be delivered within this retry window, it is treated as a permanent failure and won't be retried. You will receive a [hard-bounced event](/email-api/hard-bounced-events) for the message if this happens. Note that you may receive multiple soft-bounced events for a single message. The retry period may change in the future. ## What to do if you receive a soft-bounced event If you receive a soft-bounced event, you most likely don't need to take any action, as MailChannels will automatically retry delivery for soft-bounced messages. However, you can investigate the reason for the bounce using the `status` code and `reason` fields. For more in-depth information on SMTP response codes and their meanings, see our [SMTP response code documentation](https://blog.mailchannels.com/understanding-smtp-response-codes/). ## Fields for soft-bounced events * `email`: Sender email address. * `customer_handle`: Your account ID. * `timestamp`: Unix timestamp of the event. * `event`: The type of event, `soft-bounced` in this case. * `recipients`: List of recipient email addresses that couldn't receive the message. * `status`: The SMTP status code received for the soft bounce. * `reason`: A human-readable explanation of why the message bounced. * `campaign_id` *(optional)*: The campaign ID associated with the message, if applicable. * `request_id` *(optional)*: A unique identifier generated to track the original HTTP request. This will be blank if the message is sent via SMTP instead of the API. * `smtp_id`: A unique identifier used to track the message, matching the `Message-Id` header. ## Example soft-bounced event ```json theme={null} { "email": "sender@example.com", "customer_handle": "examplecorp", "timestamp": 1700000000, "event": "soft-bounced", "recipients": [ "recipient@example.net", "recipient2@example.net" ], "status": "421", "reason": "Service not available", "campaign_id": "spring-sale-2026", "request_id": "4b06083a23724f279c8d11a370e641f8", "smtp_id": "" } ``` # SPF, DKIM, and DMARC Source: https://docs.mailchannels.com/email-api/spf-dkim-dmarc Learn how SPF, DKIM, and DMARC records authenticate your email and improve deliverability. ## SPF (Sender Policy Framework) SPF is an email authentication standard that allows domain owners to specify which mail servers are authorized to send emails on their behalf. An SPF record is a DNS TXT record that lists the IP addresses and/or hostnames that may send mail for a domain. **How it works:** When a receiving mail server gets a message, it looks up the SPF record published in the DNS of the sender's `MAIL FROM` domain. Then, the connecting IP address is compared against the authorized senders listed in that record. If the IP matches, SPF passes; if it doesn't, SPF fails. **Why it matters:** SPF makes it harder for attackers to spoof your domain by sending mail from unauthorized servers. **If your domain has no SPF record:** Create a DNS TXT record with the following value, replacing `example.com` with your domain: ``` v=spf1 include:relay.mailchannels.net ~all ``` **If your domain has an SPF record:** Add `include:relay.mailchannels.net` to the existing record. For example, if your current record is `v=spf1 include:example.com ~all`, update it to: ``` v=spf1 include:example.com include:relay.mailchannels.net ~all ``` ## DKIM (DomainKeys Identified Mail) DKIM is an email authentication standard that uses public key cryptography to sign emails from your domain. **How it works:** The sending mail server adds a `DKIM-Signature` header to each outgoing message, containing a cryptographic signature generated from selected headers and the message body using a private key. The receiving server fetches the matching public key from a DNS TXT record at `._domainkey.` and uses it to verify the signature. A valid signature proves that the message was authorized by the domain owner and that the signed content has not been altered in transit. **Why it matters:** It verifies that the email came from the specified domain, and prevents tampering with certain headers and the message body. ## DMARC (Domain-based Message Authentication, Reporting, and Conformance) DMARC is an authentication standard that tells recipients what to do in the case of an email that fails SPF and DKIM checks. It provides senders with a way to publish policies in their DNS records that specify how to handle unauthenticated emails. **How it works:** DMARC specifies two things: *alignment* and *policy*. Alignment requires that the domain validated by SPF or DKIM matches the domain in the `From:` header. This prevents a nefarious actor from passing SPF/DKIM for one domain, and displaying another in the recipient's inbox. Policy, published as a DNS TXT record, tells receivers what to do with messages that fail alignment. **Why it matters:** SPF and DKIM on their own don't protect the `From:` address the recipient sees. DMARC closes that gap. ## Consequences of not configuring SPF, DKIM, and DMARC Without SPF, DKIM, and DMARC, your emails will be marked as spam, or rejected entirely by receiving servers. This means poor deliverability and a poor sender reputation. Gmail requires DKIM for domains sending more than [5,000 messages per day](https://support.google.com/a/answer/81126). [Yahoo](https://senders.yahooinc.com/best-practices/) and [Microsoft](https://techcommunity.microsoft.com/blog/microsoftdefenderforoffice365blog/strengthening-email-ecosystem-outlook%E2%80%99s-new-requirements-for-high%E2%80%90volume-senders/4399730) have similar requirements. ## MailChannels' requirements SPF, DKIM, and DMARC are not explicitly checked as part of the sending process. We will attempt to deliver your mail regardless of your adherence to these standards. **If you want your message to be received, you must have SPF, DKIM, and DMARC configured correctly.** # Authenticating a sub-account Source: https://docs.mailchannels.com/email-api/sub-accounts-authentication Issue API keys and SMTP passwords for sub-accounts. ## How sub-account authentication works Every sub-account has its own credentials, separate from the parent account. A request authenticated with a sub-account API key is treated as that sub-account: it sends from the sub-account's quota, writes to the sub-account's suppression list, and triggers the sub-account's webhooks. Sub-accounts have access to the full API and Console. Sub-accounts cannot create sub-accounts. To get the sub-account ready to send, create at least one API key. Use the parent API key when using the sub-account API to create a sub-account API key. Sub-accounts can manage their own [API keys](/email-api/authentication). Or a parent account can manage sub-account keys using the [sub-account API](/email-api/authentication#sub-account-key-management). # Creating a sub-account Source: https://docs.mailchannels.com/email-api/sub-accounts-create Create a sub-account under your MailChannels parent account. ## Before you start Only a parent account can create sub-accounts; sub-accounts cannot create further sub-accounts. ## Create a sub-account ```bash cURL theme={null} #!/usr/bin/env bash set -u : "${PARENT_API_KEY:?Set PARENT_API_KEY (a parent-account API key) before running}" : "${COMPANY_NAME:?Set COMPANY_NAME (display name for the sub-account)}" # SUB_ACCOUNT_HANDLE is optional. If unset, MailChannels generates a random handle. HANDLE_FIELD="" if [ -n "${SUB_ACCOUNT_HANDLE:-}" ]; then HANDLE_FIELD=", \"handle\": \"$SUB_ACCOUNT_HANDLE\"" fi curl -X POST https://api.mailchannels.net/tx/v1/sub-account \ -H "Content-Type: application/json" \ -H "X-Api-Key: $PARENT_API_KEY" \ -d "{ \"company_name\": \"$COMPANY_NAME\"$HANDLE_FIELD }" ``` ```javascript Node.js theme={null} import { MailChannels } from "mailchannels-sdk"; const { PARENT_API_KEY, COMPANY_NAME, SUB_ACCOUNT_HANDLE } = process.env; if (!PARENT_API_KEY) throw new Error("Set PARENT_API_KEY (a parent-account API key) before running"); if (!COMPANY_NAME) throw new Error("Set COMPANY_NAME (display name for the sub-account)"); const mailchannels = new MailChannels(PARENT_API_KEY); // SUB_ACCOUNT_HANDLE is optional. If omitted, MailChannels generates a random handle. const { data, error } = await mailchannels.subAccounts.create(COMPANY_NAME, SUB_ACCOUNT_HANDLE); if (error) { console.error("Create sub-account failed:", error); process.exit(1); } console.log(data); ``` ```python Python theme={null} import os import sys import mailchannels parent_api_key = os.environ.get("PARENT_API_KEY") company_name = os.environ.get("COMPANY_NAME") if not parent_api_key: sys.exit("Set PARENT_API_KEY (a parent-account API key) before running") if not company_name: sys.exit("Set COMPANY_NAME (display name for the sub-account)") mailchannels.api_key = parent_api_key # SUB_ACCOUNT_HANDLE is optional. If omitted, MailChannels generates a random handle. response = mailchannels.SubAccounts.create( company_name=company_name, handle=os.environ.get("SUB_ACCOUNT_HANDLE"), ) print(response) ``` ```php PHP theme={null} subAccounts->create( companyName: $company_name, handle: getenv('SUB_ACCOUNT_HANDLE') ?: null, ); print_r($response->toArray()); ``` Set the following before running: * `PARENT_API_KEY` — a parent-account API key with the `Sending Email` scope. * `COMPANY_NAME` — a display name for the sub-account, between 3 and 128 characters. Shown on the unsubscribe page when using Unsubscribe Links. * `SUB_ACCOUNT_HANDLE` — optional, between 3 and 128 characters, consisting of lowercase letters and digits. If omitted, MailChannels generates a random handle. The handle is the identifier used in every subsequent sub-account API call. A successful call returns a response with the new sub-account's `handle`, `company_name`, and `enabled` state. # Sub-account sending limits Source: https://docs.mailchannels.com/email-api/sub-accounts-limits Learn how to manage sending limits for sub-accounts. ## How sub-account limits work A sub-account limit represents the maximum number of messages that can be sent from that sub-account during a billing period. By default, sub-accounts have no limit; they can send as many messages as the parent account's limit allows. Sub-account limits can be set to any non-negative integer and their sum might exceed the parent account's overall limit. The parent account's limit is a hard ceiling; once the parent account's limit is reached, all subsequent sends from the parent account and all sub-accounts are blocked until the next billing period. ### Example Parent account `P` has two sub-accounts, `SUB_A` and `SUB_B`: | Account | Sending limit | | - | - | | `P` | 100,000 | | `SUB_A` | 70,000 | | `SUB_B` | 70,000 | The sub-account limits sum to 140,000, which is more than `P`'s overall limit of 100,000. That means the parent limit, not the sub-account limits, will be the ceiling once total sends across the parent and sub-accounts reach 100,000. Suppose `SUB_A` sends 70,000 messages and `SUB_B` has not sent any: * `SUB_A` has hit its own 70,000 limit and is blocked from sending further. * `P` has used 70,000 of its 100,000 overall capacity, leaving 30,000. * `SUB_B` can still send up to 30,000 messages, even though its own limit is 70,000. The parent's remaining capacity is now the binding ceiling. Once `SUB_B` sends those 30,000 messages, `P` reaches its 100,000 overall limit and no further sends are accepted from `P`, `SUB_A`, or `SUB_B` until the next billing period. The parent account is responsible for picking sub-account limits that match how much capacity each sub-account is expected to use. ## Set a limit Limits can be managed programmatically, or via the [sub-accounts page](https://dash.mailchannels.com/sub-accounts). ```bash cURL theme={null} #!/usr/bin/env bash set -u : "${PARENT_API_KEY:?Set PARENT_API_KEY (a parent-account API key) before running}" : "${SUB_ACCOUNT_HANDLE:?Set SUB_ACCOUNT_HANDLE (the sub-account to set a limit on)}" : "${SEND_LIMIT:?Set SEND_LIMIT (maximum sends per billing period, integer >= 0)}" curl -X PUT \ "https://api.mailchannels.net/tx/v1/sub-account/$SUB_ACCOUNT_HANDLE/limit" \ -H "Content-Type: application/json" \ -H "X-Api-Key: $PARENT_API_KEY" \ -d "{ \"sends\": $SEND_LIMIT }" ``` ```javascript Node.js theme={null} import { MailChannels } from "mailchannels-sdk"; const { PARENT_API_KEY, SUB_ACCOUNT_HANDLE, SEND_LIMIT } = process.env; if (!PARENT_API_KEY) throw new Error("Set PARENT_API_KEY (a parent-account API key) before running"); if (!SUB_ACCOUNT_HANDLE) throw new Error("Set SUB_ACCOUNT_HANDLE (the sub-account to set a limit on)"); if (!SEND_LIMIT) throw new Error("Set SEND_LIMIT (maximum sends per billing period, integer >= 0)"); const mailchannels = new MailChannels(PARENT_API_KEY); const { success, error } = await mailchannels.subAccounts.setLimit(SUB_ACCOUNT_HANDLE, { sends: Number(SEND_LIMIT), }); if (!success) { console.error("Set sub-account limit failed:", error); process.exit(1); } console.log({ success }); ``` ```python Python theme={null} import os import sys import mailchannels parent_api_key = os.environ.get("PARENT_API_KEY") handle = os.environ.get("SUB_ACCOUNT_HANDLE") send_limit = os.environ.get("SEND_LIMIT") if not parent_api_key: sys.exit("Set PARENT_API_KEY (a parent-account API key) before running") if not handle: sys.exit("Set SUB_ACCOUNT_HANDLE (the sub-account to set a limit on)") if not send_limit: sys.exit("Set SEND_LIMIT (maximum sends per billing period, integer >= 0)") mailchannels.api_key = parent_api_key response = mailchannels.SubAccounts.Limits.set(handle, sends=int(send_limit)) print(response) ``` ```php PHP theme={null} subAccounts->limits->set($sub_account_handle, sends: (int) $send_limit); print_r($response->toArray()); ``` Set the following before running: * `PARENT_API_KEY` — a parent-account API key. * `SUB_ACCOUNT_HANDLE` — the sub-account being limited. * `SEND_LIMIT` — maximum sends per billing period (integer, `0` or greater). Setting `sends` to `0` effectively pauses the sub-account without suspending it. ## Get the current limit ```bash cURL theme={null} #!/usr/bin/env bash set -u : "${PARENT_API_KEY:?Set PARENT_API_KEY (a parent-account API key) before running}" : "${SUB_ACCOUNT_HANDLE:?Set SUB_ACCOUNT_HANDLE (the sub-account to read the limit for)}" curl "https://api.mailchannels.net/tx/v1/sub-account/$SUB_ACCOUNT_HANDLE/limit" \ -H "X-Api-Key: $PARENT_API_KEY" ``` ```javascript Node.js theme={null} import { MailChannels } from "mailchannels-sdk"; const { PARENT_API_KEY, SUB_ACCOUNT_HANDLE } = process.env; if (!PARENT_API_KEY) throw new Error("Set PARENT_API_KEY (a parent-account API key) before running"); if (!SUB_ACCOUNT_HANDLE) throw new Error("Set SUB_ACCOUNT_HANDLE (the sub-account to read the limit for)"); const mailchannels = new MailChannels(PARENT_API_KEY); const { data, error } = await mailchannels.subAccounts.getLimit(SUB_ACCOUNT_HANDLE); if (error) { console.error("Get sub-account limit failed:", error); process.exit(1); } // A value of -1 means no explicit limit; the sub-account shares the parent's allocation. console.log(data); ``` ```python Python theme={null} import os import sys import mailchannels parent_api_key = os.environ.get("PARENT_API_KEY") handle = os.environ.get("SUB_ACCOUNT_HANDLE") if not parent_api_key: sys.exit("Set PARENT_API_KEY (a parent-account API key) before running") if not handle: sys.exit("Set SUB_ACCOUNT_HANDLE (the sub-account to read the limit for)") mailchannels.api_key = parent_api_key # A value of -1 means the sub-account has no explicit limit and is bounded # only by the parent account's overall limit. response = mailchannels.SubAccounts.Limits.retrieve(handle) print(response) ``` ```php PHP theme={null} subAccounts->limits->retrieve($sub_account_handle); print_r($response->toArray()); ``` A response of `sends: -1` means no explicit limit has been set. The sub-account is bounded only by the parent account's limit. ## Remove a limit Removing the limit lets the sub-account use any remaining capacity in the parent account's allocation. ```bash cURL theme={null} #!/usr/bin/env bash set -u : "${PARENT_API_KEY:?Set PARENT_API_KEY (a parent-account API key) before running}" : "${SUB_ACCOUNT_HANDLE:?Set SUB_ACCOUNT_HANDLE (the sub-account whose limit you want to clear)}" curl -X DELETE \ "https://api.mailchannels.net/tx/v1/sub-account/$SUB_ACCOUNT_HANDLE/limit" \ -H "X-Api-Key: $PARENT_API_KEY" ``` ```javascript Node.js theme={null} import { MailChannels } from "mailchannels-sdk"; const { PARENT_API_KEY, SUB_ACCOUNT_HANDLE } = process.env; if (!PARENT_API_KEY) throw new Error("Set PARENT_API_KEY (a parent-account API key) before running"); if (!SUB_ACCOUNT_HANDLE) throw new Error("Set SUB_ACCOUNT_HANDLE (the sub-account whose limit you want to clear)"); const mailchannels = new MailChannels(PARENT_API_KEY); const { success, error } = await mailchannels.subAccounts.deleteLimit(SUB_ACCOUNT_HANDLE); if (!success) { console.error("Delete sub-account limit failed:", error); process.exit(1); } console.log({ success }); ``` ```python Python theme={null} import os import sys import mailchannels parent_api_key = os.environ.get("PARENT_API_KEY") handle = os.environ.get("SUB_ACCOUNT_HANDLE") if not parent_api_key: sys.exit("Set PARENT_API_KEY (a parent-account API key) before running") if not handle: sys.exit("Set SUB_ACCOUNT_HANDLE (the sub-account whose limit you want to clear)") mailchannels.api_key = parent_api_key response = mailchannels.SubAccounts.Limits.delete(handle) print(response) ``` ```php PHP theme={null} subAccounts->limits->delete($sub_account_handle); print_r($response->toArray()); ``` # Managing suppressions Source: https://docs.mailchannels.com/email-api/suppression-api-actions Learn how to manage suppressions to protect recipients and your domain reputation. Suppression list entries can be managed via the [suppression list page](https://dash.mailchannels.com/suppression-lists) or the API. To create a suppression list entry for all sub-accounts, use an API key that belongs to the parent account, and set `add_to_sub_accounts` to `true`. Otherwise, **suppression list entries belong to the account of the API key used to create them.** ## Create a suppression entry ```bash cURL theme={null} #!/usr/bin/env bash # Create a suppression entry for a recipient. set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY (parent or sub-account) before running}" : "${RECIPIENT:?Set RECIPIENT to the email address to suppress}" # SUPPRESSION_TYPES is optional, a comma-separated list (e.g. "transactional,non-transactional"). # If unset, MailChannels uses the default "non-transactional". TYPES_FIELD="" if [ -n "${SUPPRESSION_TYPES:-}" ]; then IFS=',' read -ra TYPES_ARR <<< "$SUPPRESSION_TYPES" TYPES_JSON="" for t in "${TYPES_ARR[@]}"; do TYPES_JSON+=",\"$t\"" done TYPES_FIELD=", \"suppression_types\": [${TYPES_JSON#,}]" fi curl -X POST https://api.mailchannels.net/tx/v1/suppression-list \ -H "Content-Type: application/json" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" \ -d "{ \"suppression_entries\": [{ \"recipient\": \"$RECIPIENT\"$TYPES_FIELD }] }" ``` ```javascript Node.js theme={null} // Create a suppression entry for a recipient. import { MailChannels } from "mailchannels-sdk"; const { MAILCHANNELS_API_KEY, RECIPIENT, SUPPRESSION_TYPES } = process.env; if (!MAILCHANNELS_API_KEY) throw new Error("Set MAILCHANNELS_API_KEY (parent or sub-account) before running"); if (!RECIPIENT) throw new Error("Set RECIPIENT to the email address to suppress"); const mailchannels = new MailChannels(MAILCHANNELS_API_KEY); // SUPPRESSION_TYPES is optional, a comma-separated list (e.g. "transactional,non-transactional"). // If unset, MailChannels uses the default "non-transactional". const entry = { recipient: RECIPIENT }; if (SUPPRESSION_TYPES) { entry.types = SUPPRESSION_TYPES.split(",").map((t) => t.trim()).filter(Boolean); } const { success, error } = await mailchannels.suppressions.create({ entries: [entry] }); if (!success) { console.error("Create suppression failed:", error); process.exit(1); } console.log("Suppression created"); ``` ```python Python theme={null} import os import sys import mailchannels if not os.environ.get("MAILCHANNELS_API_KEY"): sys.exit("Set MAILCHANNELS_API_KEY (parent or sub-account) before running") recipient = os.environ.get("RECIPIENT") if not recipient: sys.exit("Set RECIPIENT to the email address to suppress") # SUPPRESSION_TYPES is optional, a comma-separated list (e.g. "transactional,non-transactional"). # If unset, MailChannels uses the default "non-transactional". entry: dict = {"recipient": recipient} types_env = os.environ.get("SUPPRESSION_TYPES") if types_env: entry["suppression_types"] = [t.strip() for t in types_env.split(",") if t.strip()] response = mailchannels.Suppressions.create([entry]) print(response) ``` ```php PHP theme={null} $recipient]; $types_env = getenv('SUPPRESSION_TYPES'); if ($types_env) { $entry['suppression_types'] = array_values(array_filter(array_map('trim', explode(',', $types_env)))); } $response = $client->suppressions->create([$entry]); print_r($response->toArray()); ``` Set the following before running: * `MAILCHANNELS_API_KEY` — a parent or sub-account API key. * `RECIPIENT` — the email address to suppress. * `SUPPRESSION_TYPES` — optional, a comma-separated list of suppression types to apply. If omitted, the default is `non-transactional`. If both `transactional` and `non-transactional` are included, the recipient will not receive any mail. If only `non-transactional` is included, the recipient will still receive necessary transactional messages. ## List suppression entries ```bash cURL theme={null} #!/usr/bin/env bash # Retrieve suppression entries for the account. set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY (parent or sub-account) before running}" curl -X GET "https://api.mailchannels.net/tx/v1/suppression-list" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" ``` ```javascript Node.js theme={null} import { MailChannels } from "mailchannels-sdk"; const { MAILCHANNELS_API_KEY } = process.env; if (!MAILCHANNELS_API_KEY) throw new Error("Set MAILCHANNELS_API_KEY before running"); const mailchannels = new MailChannels(MAILCHANNELS_API_KEY); const { data, error } = await mailchannels.suppressions.list(); if (error) { console.error("List suppressions failed:", error); process.exit(1); } console.log(data); ``` ```python Python theme={null} import os import sys import mailchannels if not os.environ.get("MAILCHANNELS_API_KEY"): sys.exit("Set MAILCHANNELS_API_KEY (parent or sub-account) before running") # Retrieve suppression entries for the account. response = mailchannels.Suppressions.list() print(response) ``` ```php PHP theme={null} suppressions->list(); print_r($response->toArray()); ``` Set `MAILCHANNELS_API_KEY` before running. The list endpoint also accepts optional `recipient`, `source`, `created_before`, `created_after`, `limit`, and `offset` query parameters for filtering and pagination. ## Delete a suppression entry ```bash cURL theme={null} #!/usr/bin/env bash # Delete a suppression entry for a recipient. set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY (parent or sub-account) before running}" : "${RECIPIENT:?Set RECIPIENT to the email address whose suppression entry should be removed}" # SOURCE is optional. Defaults to "api" if unset. Use "all" to delete every entry for the recipient. SOURCE_QUERY="" if [ -n "${SOURCE:-}" ]; then SOURCE_QUERY="?source=$SOURCE" fi curl -X DELETE "https://api.mailchannels.net/tx/v1/suppression-list/recipients/$RECIPIENT$SOURCE_QUERY" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" ``` ```javascript Node.js theme={null} import { MailChannels } from "mailchannels-sdk"; const { MAILCHANNELS_API_KEY, RECIPIENT } = process.env; if (!MAILCHANNELS_API_KEY) throw new Error("Set MAILCHANNELS_API_KEY before running"); if (!RECIPIENT) throw new Error("Set RECIPIENT to the email address whose suppression entry should be removed"); const mailchannels = new MailChannels(MAILCHANNELS_API_KEY); const { success, error } = await mailchannels.suppressions.delete(RECIPIENT); if (!success) { console.error("Delete suppression failed:", error); process.exit(1); } console.log("Suppression deleted"); ``` ```python Python theme={null} import os import sys import mailchannels if not os.environ.get("MAILCHANNELS_API_KEY"): sys.exit("Set MAILCHANNELS_API_KEY (parent or sub-account) before running") recipient = os.environ.get("RECIPIENT") if not recipient: sys.exit("Set RECIPIENT to the email address whose suppression entry should be removed") # SOURCE is optional. Defaults to "api". Pass "all" to delete every entry for the recipient. source = os.environ.get("SOURCE") response = mailchannels.Suppressions.delete(recipient, source=source) print(response) ``` ```php PHP theme={null} suppressions->delete($recipient, $source); print_r($response->toArray()); ``` Set the following before running: * `MAILCHANNELS_API_KEY` — the API key that owns the suppression entry. * `RECIPIENT` — the email address whose suppression entry should be removed. * `SOURCE` — optional, defaults to `api`. Pass `all` to delete every entry for the recipient regardless of source. # Suppressions and unsubscribe handling Source: https://docs.mailchannels.com/email-api/suppressions-unsubscribes Learn how to use suppressions and unsubscribes to protect recipients and your domain reputation. Suppression handling protects recipients and your domain reputation. Your application should decide when a recipient is eligible for transactional mail, non-transactional mail, or no mail at all. ## Suppression types MailChannels supports two suppression types: | Type | Use it for | | - | - | | `transactional` | Block all mail to a recipient when you should not send any message, including operational mail. | | `non-transactional` | Block marketing, lifecycle, newsletter, or promotional mail while still allowing necessary transactional messages. | If you create a suppression entry without `suppression_types`, the default is `non-transactional`. ## Suppression sources Suppression entries can come from: * `api` * `unsubscribe_link` * `list_unsubscribe` * `hard_bounce` * `spam_complaint` Use the source to separate recipient preference, automated bounce handling, and manual administrative actions. ## Non-transactional messages Set `transactional` to `false` for non-transactional sends. Non-transactional messages must be DKIM signed and must have exactly one recipient per personalization. MailChannels adds unsubscribe headers for non-transactional messages. Do not mark marketing or promotional email as transactional to bypass suppression handling. That can create compliance risk and damage sender reputation. ## Operational workflow 1. Import existing unsubscribes and hard bounces into the suppression list before cutover. 2. Store `hard-bounced`, `complained`, `unsubscribed`, and unsubscribe-related webhook events. 3. Update your application preference store from those events. 4. Check your own preference store before calling MailChannels. 5. Review suppression list entries by recipient or source when debugging. # Symfony Source: https://docs.mailchannels.com/email-api/symfony Send email through Symfony Mailer using the MailChannels transport plugin. The `mailchannels/mailchannels-php` package ships a [Symfony Mailer](https://symfony.com/doc/current/mailer.html) transport. If you already have a Symfony application that builds `Symfony\Component\Mime\Email` objects and sends them through `MailerInterface`, you can switch to MailChannels with just a few lines of configuration. This plugin is optional and lives alongside the rest of the PHP SDK. The plugin itself can only send mail. It doesn't implement the full API surface (sub-accounts, keys, webhooks, etc.). For those features, use the SDK's `Client` directly. ## Prerequisites Before you send your first message, follow the [PHP quickstart](/email-api/php/quickstart#prerequisites) prerequisite section. This will walk you through creating an account, generating an API key, and adding the required Domain Lockdown and SPF DNS records. ## Installation Install the SDK and the Symfony Mailer component: ```bash theme={null} composer require mailchannels/mailchannels-php symfony/mailer ``` The SDK also needs a PSR-18 HTTP client and PSR-17 factories. Symfony HTTP Client + Nyholm PSR-7 is a natural fit for a Symfony project (Guzzle works too, but install one or the other, not both): ```bash theme={null} composer require symfony/http-client nyholm/psr7 ``` ### Configuring Symfony Symfony discovers mailer transports through tagged services. Register the factory in `config/services.yaml`: ```yaml config/services.yaml theme={null} services: MailChannels\Plugins\Symfony\Transport\MailChannelsTransportFactory: tags: ['mailer.transport_factory'] ``` See Symfony's docs on [custom transport factories](https://symfony.com/doc/current/mailer.html#custom-transport-factories) for background on how this mechanism works. Set the `MAILER_DSN` environment variable, in your `.env` file or your environment: ```bash .env theme={null} MAILER_DSN=mailchannels+api://${MAILCHANNELS_API_KEY}@default ``` * Scheme: `mailchannels` or `mailchannels+api` (both accepted). * DSN user: your MailChannels API key. * DSN host: leave as `default` for the production API endpoint. See Symfony's [transport setup](https://symfony.com/doc/current/mailer.html#transport-setup) docs for more on the DSN format and how it's typically wired up per-environment. ## Sending mail Once configured, send mail through the standard `MailerInterface`. No MailChannels-specific code is required for a basic send: ```php theme={null} use Symfony\Component\Mailer\MailerInterface; use Symfony\Component\Mime\Email; class YourService { public function __construct(private readonly MailerInterface $mailer) {} public function sendWelcomeEmail(): void { $email = (new Email()) ->from('sender@example.com') ->to('recipient@example.com') ->subject('Welcome to MailChannels') ->text('Plain-text body.') ->html('

HTML body.

'); $this->mailer->send($email); } } ``` See Symfony's docs on [creating and sending messages](https://symfony.com/doc/current/mailer.html#creating-sending-messages) for everything else `Email` supports (multiple recipients, reply-to, attachments, and so on). This transport only builds one personalization per message. If you need to send multiple individualized emails in one request, call the SDK's `Client` directly instead of going through `MailerInterface`. See [How emails are structured](/email-api/how-emails-are-structured) for more information on personalizations. ### Control headers Symfony's `Email` object has no equivalent for some MailChannels API features. Reach those instead through custom headers prefixed with `x-mailchannels-`, set on the `Email`'s header bag. The `x-mailchannels-*` headers are stripped before the message is sent, so recipients never see them. ```php theme={null} $email->getHeaders()->addTextHeader('x-mailchannels-transactional', 'true'); ``` If the same custom header is added twice, only the **last** value is used. Boolean headers accept only the literal strings `"true"` / `"false"`; anything else throws `Symfony\Component\Mailer\Exception\TransportException`. An empty value is treated as unset (falls back to the default). | Header | Description | | - | - | | `x-mailchannels-send-async` | Queue the message (`true`, the default) instead of sending it synchronously (`false`). Equivalent to calling the SDK's `queue()` vs. `send()`. Queued sends report delivery status via [webhooks](/email-api/webhooks) rather than in the response. | | `x-mailchannels-click-tracking-enable` | Enable click tracking. | | `x-mailchannels-click-tracking-domain` | Custom domain for click-tracking links. See [Custom tracking](/email-api/custom-tracking). | | `x-mailchannels-open-tracking-enable` | Enable open tracking. | | `x-mailchannels-open-tracking-domain` | Custom domain for open-tracking links. See [Custom tracking](/email-api/custom-tracking). | | `x-mailchannels-unsubscribe-domain` | Custom domain for unsubscribe links. See [Custom tracking](/email-api/custom-tracking). | | `x-mailchannels-transactional` | Whether the mail is transactional or non-transactional (marketing, etc.). Affects List-Unsubscribe handling, see [Unsubscribe](/email-api/unsubscribe). | | `x-mailchannels-dkim-domain` | Domain to use for DKIM signing. | | `x-mailchannels-dkim-selector` | Selector to use for DKIM signing. | | `x-mailchannels-dkim-private-key` | Private key to use for DKIM signing. | | `x-mailchannels-campaign-id` | Campaign that this message is part of. | Each of these corresponds directly to a field on the [send endpoint](/api-reference/send/send-an-email). See that reference for full semantics. This integration only supports DKIM via the `x-mailchannels-dkim-*` headers above. If a `DKIM-Signature` header is already present on the message, the message is rejected, since the provided signature won't match the message the API constructs. See [DKIM signing and key management](/email-api/configuring-dkim) for information on how MailChannels handles signing and keys. ## Reading the result * Synchronous send (`x-mailchannels-send-async: false`): the sent message's ID is written back onto Symfony's `SentMessage` object, so you can read it after sending. * Queued (default): the API doesn't return a message ID per call, so Symfony's own generated message ID is left untouched on `SentMessage`. ```php theme={null} $sentMessage = $this->mailer->send($email); echo $sentMessage?->getMessageId(); ``` ## Error handling Any SDK exception raised while sending (authentication, validation, rate limiting, server errors) is caught and re-thrown as `Symfony\Component\Mailer\Exception\TransportException`. Access the original exception via `$e->getPrevious()` to inspect it: ```php theme={null} use Symfony\Component\Mailer\Exception\TransportException; try { $this->mailer->send($email); } catch (TransportException $e) { $previous = $e->getPrevious(); if ($previous instanceof \MailChannels\Exception\RateLimitException) { // handle rate limit } elseif ($previous instanceof \MailChannels\Exception\ValidationException) { // handle validation error } else { // handle other transport errors } } ``` ## Limitations * **One personalization per message.** No per-recipient template data or per-recipient header overrides through this transport. * **No Mustache templating.** This transport has no way to use mailchannels' mustache template rendering. If you need to do so, use Symfony's [Template Support](https://symfony.com/doc/current/templates.html), or use the SDK's `Client` directly; see [Templates](/email-api/templates) for how. * **DKIM only via the MailChannels API**, as described above. ## Webhooks The plugin also has optional support for parsing webhook events into Symfony's `RemoteEvent` objects. ### Installation Install the Symfony Webhook and RemoteEvent components, as well as ext-sodium for webhook signature verification: ```bash theme={null} composer require symfony/webhook symfony/remote-event ext-sodium ``` While the Symfony mailer plugin requires version 6.4 or higher, the webhook plugin requires Symfony 7.2 or higher, as previous versions do not have support for batched webhook processing. ### Configuring Symfony Register the webhook parser in `config/services.yaml`: ```yaml config/services.yaml theme={null} services: MailChannels\Plugins\Symfony\Webhook\MailChannelsRequestParser: ~ ``` Register the webhook route in `config/webhooks.yaml`: ```yaml config/webhooks.yaml theme={null} framework: webhook: routing: mailchannels: service: MailChannels\Plugins\Symfony\Webhook\MailChannelsRequestParser ``` See the Symfony docs on [webhook endpoints](https://symfony.com/doc/current/webhook.html#a-centralized-webhook-endpoint) for more information on how this works. You will now have a webhook route available at `/webhook/mailchannels`. ### Consuming RemoteEvents When a webhook is received, it will automatically be parsed into a `Symfony\Component\RemoteEvent\RemoteEvent` object and passed to your consumer. ```php src/RemoteEvent/MailChannelsWebhookConsumer.php theme={null} namespace App\RemoteEvent; use Symfony\Component\RemoteEvent\Attribute\AsRemoteEventConsumer; use Symfony\Component\RemoteEvent\Consumer\ConsumerInterface; use Symfony\Component\RemoteEvent\RemoteEvent; #[AsRemoteEventConsumer('mailchannels')] // must match routing name final class MailChannelsWebhookConsumer implements ConsumerInterface { public function consume(RemoteEvent $event): void { // handle the event based on your business logic } } ``` Almost all events will automatically be parsed into Symfony's `MailerDeliveryEvent` or `MailerEngagementEvent`, with additional information about the type of event and the message it relates to. See the [Symfony RemoteEvent docs](https://symfony.com/doc/current/webhook.html#consuming-the-remoteevent) for more information on how to consume events. The MailChannels webhook integration adds one extra RemoteEvent type: `MailChannels\Plugins\Symfony\RemoteEvent\TestEvent`. This will be used when you send a test webhook from the MailChannels dashboard or API. You can use this to verify that your webhook endpoint is working correctly. ## Next steps * Read the [webhooks guide](/email-api/webhooks) to learn more about real-time notifications for email delivery, bounces, and complaints. * Explore the [API reference](/email-api/api-reference-introduction) to see what else you can do with the API. # Templates Source: https://docs.mailchannels.com/email-api/templates Send personalized emails to many recipients with Mustache templates. ## How templates work Email templates let you send flexible, custom emails to many different recipients in a single request. MailChannels currently supports [Mustache](https://mustache.github.io/mustache.5.html) templates. ### Enabling templates Two changes turn a regular send into a template send. **1. Mark the content part as a template.** ```json cURL theme={null} "content": [ { "type": "text/plain", "value": "Hello {{name}}", "template_type": "mustache" } ] ``` ```javascript Node.js theme={null} template: { type: "mustache" }, text: "Hello {{name}}", ``` ```python Python theme={null} "content": [ { "type": "text/plain", "value": "Hello {{name}}", "template_type": "mustache", }, ], ``` ```php PHP theme={null} 'content' => [ [ 'type' => 'text/plain', 'value' => 'Hello {{name}}', 'template_type' => 'mustache', ], ], ``` The template type field must be set in each content part that contains a template. Otherwise, that content part will not be modified, and any templating within will be sent as normal text. The JavaScript SDK avoids this by applying `template.type` to all content parts. **2. Supply per-recipient data.** Add per-recipient template data to each personalization. The keys must match the placeholders in your template: ```json cURL theme={null} "personalizations": [ { "to": [{ "email": "alice@example.com" }], "dynamic_template_data": { "name": "Alice" } }, { "to": [{ "email": "bob@example.com" }], "dynamic_template_data": { "name": "Bob" } } ] ``` ```javascript Node.js theme={null} personalizations: [ { to: [{ email: "alice@example.com" }], template: { data: { name: "Alice" } } }, { to: [{ email: "bob@example.com" }], template: { data: { name: "Bob" } } }, ], ``` ```python Python theme={null} "personalizations": [ {"to": [{"email": "alice@example.com"}], "dynamic_template_data": {"name": "Alice"}}, {"to": [{"email": "bob@example.com"}], "dynamic_template_data": {"name": "Bob"}}, ], ``` ```php PHP theme={null} 'personalizations' => [ ['to' => [['email' => 'alice@example.com']], 'dynamic_template_data' => ['name' => 'Alice']], ['to' => [['email' => 'bob@example.com']], 'dynamic_template_data' => ['name' => 'Bob']], ], ``` Placeholders without a matching key in `dynamic_template_data` render as empty strings. ### Full example ```bash cURL theme={null} #!/usr/bin/env bash set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}" : "${FROM_EMAIL:?Set FROM_EMAIL (must be on a Domain-Lockdown-authorized domain)}" : "${TO_EMAIL_1:?Set TO_EMAIL_1}" : "${TO_EMAIL_2:?Set TO_EMAIL_2}" curl -X POST https://api.mailchannels.net/tx/v1/send \ -H "Content-Type: application/json" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" \ -d @- <emails->send([ 'from' => ['email' => $from_email], 'subject' => 'Hello from MailChannels', 'content' => [ [ 'type' => 'text/plain', 'value' => 'Hello {{name}}', 'template_type' => 'mustache', ], ], 'personalizations' => [ ['to' => [['email' => $to_email_1]], 'dynamic_template_data' => ['name' => 'Alice']], ['to' => [['email' => $to_email_2]], 'dynamic_template_data' => ['name' => 'Bob']], ], ]); print_r($response->toArray()); ``` Alice receives `Hello Alice` and Bob receives `Hello Bob`. ## Testing checklist * Render the template with typical, empty, and unusually long values. * Verify links and unsubscribe behavior. * Preview in common email clients. * Check message size before adding attachments. * Confirm Gmail does not clip long messages. * Send a test through a real domain with SPF, DKIM, and DMARC configured. ## Mustache features Mustache is a simple and flexible templating language that allows you to create custom messages for all your recipients. See the [Mustache spec](https://mustache.github.io/mustache.5.html) for the complete reference; features not in the [supported features](#supported-features) list at the bottom of this page are not available. ### Nested fields Use `.` to access nested fields. **Data** ```json theme={null} "dynamic_template_data": { "name": { "title": "Ms.", "first": "Jane", "last": "Doe" } } ``` **Template** ``` Dear {{name.title}} {{name.last}}, your account name is {{name.first}} {{name.last}}. ``` **Result** ``` Dear Ms. Doe, your account name is Jane Doe. ``` ### Lists Wrap a section in `{{#list}}…{{/list}}` to repeat it for each item. Inside the section, unqualified keys refer to fields of the current item. **Data** ```json theme={null} "dynamic_template_data": { "items": [ { "name": "Standard plan", "price": "9.99" }, { "name": "Add-on seat", "price": "4.00" } ] } ``` **Template** ```html theme={null}
    {{#items}}
  • {{name}}: ${{price}}
  • {{/items}}
``` **Result** ```html theme={null}
  • Standard plan: $9.99
  • Add-on seat: $4.00
``` ### Conditionals The same `{{#key}}…{{/key}}` syntax renders the block only when `key` is truthy: boolean `true`, a non-empty list, or any non-false value. Other fields in the data can be referenced inside the block. **Template** ``` Thanks for being a customer!{{#anniversary}} You've been with us for {{years}} year(s) — here's a code: 50OFF{{/anniversary}} ``` With `"anniversary": true, "years": 3`: ``` Thanks for being a customer! You've been with us for 3 year(s) — here's a code: 50OFF ``` With `"anniversary": false`: ``` Thanks for being a customer! ``` ### Negated conditionals The inverse — `{{^key}}…{{/key}}` — renders only when `key` is falsy or missing. **Template** ``` {{#verified}}Your account is verified.{{/verified}}{{^verified}}Verification failed.{{/verified}} ``` With `"verified": true`: ``` Your account is verified. ``` With `"verified": false`: ``` Verification failed. ``` ### HTML escaping `{{value}}` HTML-escapes its output so injected content can't break the layout. To insert content as raw HTML, use either triple braces or `{{&value}}`. **Data** ```json theme={null} "dynamic_template_data": { "msg": "Bold" } ``` **Template** ``` escaped: {{msg}} raw (triple): {{{msg}}} raw (ampersand): {{&msg}} ``` **Result** ``` escaped: <b>Bold</b> raw (triple): Bold raw (ampersand): Bold ``` Only emit raw HTML for content you control. Substituting recipient-supplied values without escaping is a common HTML injection vector. ### Comments `{{! ... }}` is stripped from the rendered output. **Template** ``` Welcome.{{! TODO: add personalization here }} Glad to have you. ``` **Result** ``` Welcome. Glad to have you. ``` ## Supported features The Mustache features MailChannels supports, using the names from the [Mustache spec](https://mustache.github.io/mustache.5.html): * Variables * Dotted names * Implicit iterator * Non-empty lists * Non-false values * Inverted sections * Comments * Set delimiter # Troubleshooting Source: https://docs.mailchannels.com/email-api/troubleshooting Learn how to troubleshoot common issues. ## Account issues ### "An account with that email address already exists" or "This email address is already in use" This typically happens when an account was created for another product, for example, [Inbound Filtering](https://app.mailchannels.com). **If you have an account with another product** contact [sales@mailchannels.com](mailto:sales@mailchannels.com) to add the Email API to your account. **If you do not have an account** contact [support@mailchannels.com](mailto:support@mailchannels.com) and the team will help resolve the issue. **If you are not sure** contact [support@mailchannels.com](mailto:support@mailchannels.com) and the team will help you resolve the issue. ### API actions are returning a successful response, but seem to have no effect This typically happens when the API key in the X-Api-Key header is not associated with the account you think it is. For example, if you have an account `examplecorp`, and a sub-account `examplecorp-marketing`, API keys created in the `examplecorp` account will make API calls that affect the `examplecorp` account, not the `examplecorp-marketing` account. Make sure you are using an API key that is associated with the account you want to affect. The only header that is required for the Email API is the X-Api-Key header. Other headers are ignored. ## Sending errors ### 550 5.7.1 This sender is not authorized to send from `domain` This error indicates that the Domain Lockdown record is not properly configured for the sending domain. Make sure that the Domain Lockdown record is configured and that the DNS record has propagated. For each domain you send from, create a TXT record at the `_mailchannels` subdomain. For example, if you send from `example.com`, create a TXT record at `_mailchannels.example.com`. The TXT record should contain the following value: ``` v=mc1 auth=examplecorp ``` where `examplecorp` is your MailChannels account name. To check that your record has propagated, you can use the [check-domain tool](https://dash.mailchannels.com/domain-health) or the API. Below are examples of how to use the API to check that the record has propagated. ```bash cURL theme={null} #!/usr/bin/env bash # Check that a domain's DKIM, SPF, and Domain Lockdown records are configured # correctly and have propagated. set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}" : "${DOMAIN:?Set DOMAIN (the sending domain, e.g. example.com)}" curl -X POST "https://api.mailchannels.net/tx/v1/check-domain" \ -H "Content-Type: application/json" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" \ -d @- <checkDomain->check($domain); print_r($response->toArray()); ``` ### 550 5.1.2 \[SDNF] Sender Domain Not Found. This error indicates that the sending domain does not have a valid DNS record. Make sure that the sending domain has a valid A or MX record. Use the check-domain tool to verify that the sending domain has a valid DNS record. ## What to include when contacting support Include the failed request, the error response, timestamp with time zone, sending domain, sender address, recipient domain, `request_id` if available, and your account name. The more information you can provide, the faster the team can investigate and resolve the issue. # Unsubscribe Source: https://docs.mailchannels.com/email-api/unsubscribe Give recipients one-click opt-out using the mc-unsubscribe-url placeholder and automatic List-Unsubscribe headers for compliant, deliverable email. MailChannels supports two ways to let recipients opt out of future email: * An **unsubscribe link** that you place inside the message body. * **List-Unsubscribe headers** that email clients render as a native unsubscribe control. ## Unsubscribe link Insert the `mc-unsubscribe-url` placeholder anywhere in your `text/html` or `text/plain` content. At render time, MailChannels replaces it with a unique URL that opens a one-click unsubscribe page hosted by MailChannels. To use the placeholder: * The content part must set `template_type` to `mustache`. * Each personalization must contain exactly one recipient, so the generated URL is unique to that recipient. Only `text/html` and `text/plain` content parts are supported. The placeholder is ignored in other content types. ### HTML content Place the placeholder in the `href` of an `` element so it renders correctly across email clients: ```bash cURL theme={null} #!/usr/bin/env bash set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}" : "${FROM_EMAIL:?Set FROM_EMAIL (must be on a Domain-Lockdown-authorized domain)}" : "${TO_EMAIL:?Set TO_EMAIL}" curl -X POST https://api.mailchannels.net/tx/v1/send \ -H "Content-Type: application/json" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" \ -d @- <Unsubscribe", "template_type": "mustache" } ] } JSON ``` ```javascript Node.js theme={null} import { MailChannels } from "mailchannels-sdk"; const { MAILCHANNELS_API_KEY, FROM_EMAIL, TO_EMAIL } = process.env; if (!MAILCHANNELS_API_KEY) throw new Error("Set MAILCHANNELS_API_KEY before running"); if (!FROM_EMAIL) throw new Error("Set FROM_EMAIL (must be on a Domain-Lockdown-authorized domain)"); if (!TO_EMAIL) throw new Error("Set TO_EMAIL"); const mailchannels = new MailChannels(MAILCHANNELS_API_KEY); const { data, error } = await mailchannels.emails.send({ from: { email: FROM_EMAIL, name: "Your Name" }, to: { email: TO_EMAIL, name: "Recipient" }, subject: "Hello from MailChannels", html: "Unsubscribe", template: { type: "mustache" }, }); if (error) { console.error("Send failed:", error); process.exit(1); } console.log(data); ``` ```python Python theme={null} import os import sys import mailchannels if not os.environ.get("MAILCHANNELS_API_KEY"): sys.exit("Set MAILCHANNELS_API_KEY before running") from_email = os.environ.get("FROM_EMAIL") to_email = os.environ.get("TO_EMAIL") if not from_email: sys.exit("Set FROM_EMAIL (must be on a Domain-Lockdown-authorized domain)") if not to_email: sys.exit("Set TO_EMAIL") response = mailchannels.Emails.send( { "from": {"email": from_email, "name": "Your Name"}, "subject": "Hello from MailChannels", "content": [ { "type": "text/html", "value": "Unsubscribe", "template_type": "mustache", }, ], "personalizations": [ {"to": [{"email": to_email, "name": "Recipient"}]}, ], } ) print(response) ``` ```php PHP theme={null} emails->send([ 'from' => ['email' => $from_email, 'name' => 'Your Name'], 'subject' => 'Hello from MailChannels', 'content' => [ [ 'type' => 'text/html', 'value' => "Unsubscribe", 'template_type' => 'mustache', ], ], 'personalizations' => [ ['to' => [['email' => $to_email, 'name' => 'Recipient']]], ], ]); print_r($response->toArray()); ```
### Plain text content Include the placeholder inline: ```bash cURL theme={null} #!/usr/bin/env bash set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}" : "${FROM_EMAIL:?Set FROM_EMAIL (must be on a Domain-Lockdown-authorized domain)}" : "${TO_EMAIL:?Set TO_EMAIL}" curl -X POST https://api.mailchannels.net/tx/v1/send \ -H "Content-Type: application/json" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" \ -d @- <emails->send([ 'from' => ['email' => $from_email, 'name' => 'Your Name'], 'subject' => 'Hello from MailChannels', 'content' => [ [ 'type' => 'text/plain', 'value' => 'To unsubscribe, visit: {{mc-unsubscribe-url}}', 'template_type' => 'mustache', ], ], 'personalizations' => [ ['to' => [['email' => $to_email, 'name' => 'Recipient']]], ], ]); print_r($response->toArray()); ``` ## List-Unsubscribe headers Clients such as Gmail and Apple Mail render a native **Unsubscribe** control when a message carries `List-Unsubscribe` and `List-Unsubscribe-Post` headers. We automatically add both headers when the `transactional` field is set to **false** in the send request — **unless you include your own `List-Unsubscribe` header, in which case we use yours and don't add ours.** When `transactional` is false, the following conditions must also be met: * Each personalization contains **exactly one recipient** * The email is [DKIM signed](/email-api/configuring-dkim) # Handling unsubscribed events Source: https://docs.mailchannels.com/email-api/unsubscribed-events Learn about the best practices for responding to unsubscribed webhook events. ## What is an unsubscribed event? An unsubscribed event is generated when a recipient clicks the unsubscribe link in an email message. This indicates that they no longer wish to receive emails from the sender. The recipient's email address is automatically added to your suppression list so you won't accidentally send mail to them again, but you should remove the address from your mailing list as well. For more information, see [suppressions and unsubscribe handling](/email-api/suppressions-unsubscribes). ## What to do if you receive an unsubscribed event If you receive an unsubscribed event, you should remove the recipient's email address from your mailing list to avoid sending to them again in the future. Don't already have unsubscribe links in your emails? See how to add them on the [Unsubscribe](/email-api/unsubscribe) page. ## Fields for unsubscribed events * `email`: Sender email address. * `customer_handle`: Your account ID. * `timestamp`: Unix timestamp of the event. * `event`: The type of event, `unsubscribed` in this case. * `recipients`: List of recipient email addresses that unsubscribed. * `campaign_id` *(optional)*: The campaign ID associated with the message, if applicable. * `request_id` *(optional)*: A unique identifier generated to track the original HTTP request. This will be blank if the message is sent via SMTP instead of the API. * `smtp_id`: A unique identifier used to track the message, matching the `Message-Id` header. ## Example unsubscribed event ```json theme={null} { "email": "sender@example.com", "customer_handle": "examplecorp", "timestamp": 1700000000, "event": "unsubscribed", "recipients": [ "recipient@example.net" ], "campaign_id": "spring-sale-2026", "request_id": "4b06083a23724f279c8d11a370e641f8", "smtp_id": "" } ``` # Event types Source: https://docs.mailchannels.com/email-api/webhook-event-types Learn about the different types of events that MailChannels can send to your webhook and the fields included in each event. ## Supported event types | Event Type | Description | | - | - | | processed | System received and began processing email for delivery | | delivered | Recipient's server accepted the email | | dropped | System encountered an error when processing the email | | unsubscribed | Recipient clicked the unsubscribe link in the email | | hard-bounced | Recipient's server rejected the email | | soft-bounced | Temporary delivery failure (will retry automatically) | | open | Recipient opened the email | | click | Recipient clicked a link in the email | | complained | Recipient filed a spam complaint about the email | | test | Test event, used only for webhook validation | `open` and `click` events require tracking to be enabled, which is not available in our free tier. # Webhook retries and delivery behavior Source: https://docs.mailchannels.com/email-api/webhook-retries Design reliable webhook consumers with MailChannels webhook status tracking, validation, and batch resend. Your webhook receiver should assume that delivery events are operational data. Store them before running business logic, process them asynchronously, and make handlers idempotent. ## Success and failure MailChannels treats a webhook request as successful when your endpoint returns a `2xx` HTTP status. Other responses are recorded by status category, including `1xx`, `3xx`, `4xx`, `5xx`, and `no_response`. Use the webhook batch API to inspect recent webhook batches. If no time range is supplied, the API returns batches from the last 3 days. If you provide `created_after` and `created_before`, the range must not exceed 31 days. ```bash cURL theme={null} #!/usr/bin/env bash # List webhook batches from the last 3 days. set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}" curl -X GET "https://api.mailchannels.net/tx/v1/webhook-batch" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" ``` ```javascript Node.js theme={null} import { MailChannels } from "mailchannels-sdk"; const { MAILCHANNELS_API_KEY } = process.env; if (!MAILCHANNELS_API_KEY) throw new Error("Set MAILCHANNELS_API_KEY before running"); const mailchannels = new MailChannels(MAILCHANNELS_API_KEY); const { data, error } = await mailchannels.webhooks.batches(); if (error) { console.error("List webhook batches failed:", error); process.exit(1); } console.log(data); ``` ```python Python theme={null} import os import sys import mailchannels if not os.environ.get("MAILCHANNELS_API_KEY"): sys.exit("Set MAILCHANNELS_API_KEY before running") # List webhook batches from the last 3 days. response = mailchannels.Webhooks.batches() print(response) ``` ```php PHP theme={null} webhooks->batches(); print_r($response->toArray()); ``` ## Replay failed batches After you fix a receiver problem, you can resend a specific batch. ```bash cURL theme={null} #!/usr/bin/env bash # Resend a specific webhook batch. set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}" : "${BATCH_ID:?Set BATCH_ID before running}" curl -X POST "https://api.mailchannels.net/tx/v1/webhook-batch/$BATCH_ID/resend" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" ``` ```javascript Node.js theme={null} import { MailChannels } from "mailchannels-sdk"; const { MAILCHANNELS_API_KEY, BATCH_ID } = process.env; if (!MAILCHANNELS_API_KEY) throw new Error("Set MAILCHANNELS_API_KEY before running"); if (!BATCH_ID) throw new Error("Set BATCH_ID to the ID of the batch to resend"); const mailchannels = new MailChannels(MAILCHANNELS_API_KEY); const { data, error } = await mailchannels.webhooks.resendBatch(Number(BATCH_ID)); if (error) { console.error("Resend webhook batch failed:", error); process.exit(1); } console.log(data); ``` ```python Python theme={null} import os import sys import mailchannels if not os.environ.get("MAILCHANNELS_API_KEY"): sys.exit("Set MAILCHANNELS_API_KEY before running") batch_id_str = os.environ.get("BATCH_ID") if not batch_id_str: sys.exit("Set BATCH_ID before running") # Resend a specific webhook batch. response = mailchannels.Webhooks.resend_batch(int(batch_id_str)) print(response) ``` ```php PHP theme={null} webhooks->resendBatch((int) $batch_id); print_r($response->toArray()); ``` A successful resend API response means MailChannels made the resend attempt and captured the endpoint response. It does not mean your endpoint returned a `2xx` status. Check the response body and your receiver logs. ## Test delivery Use webhook validation before production launch and after endpoint changes. Validation sends a test event with your customer handle, a test sender, a test event type, and a generated `smtp_id`. # JSON schema for webhooks Source: https://docs.mailchannels.com/email-api/webhook-schema The JSON schema for the data sent to your webhook when delivery events occur. ```json JSON Schema theme={null} { "$schema": "http://json-schema.org/draft-07/schema#", "type": "array", "items": { "type": "object", "properties": { "email": { "type": "string", "format": "email", "description": "The sender's email address" }, "customer_handle": { "type": "string", "description": "The MailChannels account ID that generated the webhook. If the message was sent by a sub-account, this field contains the sub-account handle." }, "timestamp": { "type": "integer", "description": "The Unix timestamp (in seconds) when the event occurred; the timezone is always UTC" }, "event": { "type": "string", "enum": ["processed", "delivered", "open", "click", "hard-bounced", "soft-bounced", "dropped", "complained", "unsubscribed"], "description": "The type of event that occurred" }, "request_id": { "type": "string", "description": "A unique identifier generated to track the original HTTP request" }, "status": { "type": "string", "description": "For hard-bounced, soft-bounced, and dropped events, the SMTP status code that caused the bounce" }, "reason": { "type": "string", "description": "For hard-bounced, soft-bounced, and dropped events, a human-readable explanation of why the message hard-bounced" }, "campaign_id": { "type": "string", "description": "The campaign identifier for the message that generated the event" }, "url": { "type": "string", "description": "For click events, the URL that was clicked by the recipient" }, "user_agent": { "type": "string", "description": "For click and open events, the User-Agent header given when the recipient clicked a link or opened a message" }, "smtp_id": { "type": "string", "description": "For click and open events, the Message-Id of the message that generated the event" }, "ip": { "type": "string", "description": "For click and open events, the IP address of the host that made the HTTP request" }, "recipients": { "type": "array", "description": "The recipients of the message" } }, "required": ["customer_handle", "timestamp", "event"], "additionalProperties": false }, "minItems": 1, "maxItems": 1000 } ``` # Webhooks Source: https://docs.mailchannels.com/email-api/webhooks Learn how to set up and manage webhooks to receive notifications about email delivery events. ## What is a webhook? A webhook is an HTTP request sent by MailChannels when certain events in the email delivery process occur. By leveraging these events, you gain real-time insights into email delivery status, improve deliverability, and enhance your email sending strategies. ## Webhook management MailChannels provides tools to create, retrieve, delete, and validate webhooks. The first step to receiving webhooks is to create a webhook. ### Creating a webhook There are two ways to create a webhook: visit the [webhooks page](https://dash.mailchannels.com/webhooks) and click **Create Webhook**, or use the API. To create a webhook via the API, see the code examples below. ```bash cURL theme={null} #!/usr/bin/env bash # Enroll a webhook endpoint to receive delivery events. set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}" : "${WEBHOOK_ENDPOINT:?Set WEBHOOK_ENDPOINT to the URL that should receive events}" curl -X POST "https://api.mailchannels.net/tx/v1/webhook?endpoint=$WEBHOOK_ENDPOINT" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" ``` ```javascript Node.js theme={null} import { MailChannels } from "mailchannels-sdk"; const { MAILCHANNELS_API_KEY, WEBHOOK_ENDPOINT } = process.env; if (!MAILCHANNELS_API_KEY) throw new Error("Set MAILCHANNELS_API_KEY before running"); if (!WEBHOOK_ENDPOINT) throw new Error("Set WEBHOOK_ENDPOINT to the URL that should receive events"); const mailchannels = new MailChannels(MAILCHANNELS_API_KEY); const { success, error } = await mailchannels.webhooks.enroll(WEBHOOK_ENDPOINT); if (!success) { console.error("Enroll webhook failed:", error); process.exit(1); } console.log("Webhook enrolled"); ``` ```python Python theme={null} import os import sys import mailchannels if not os.environ.get("MAILCHANNELS_API_KEY"): sys.exit("Set MAILCHANNELS_API_KEY before running") endpoint = os.environ.get("WEBHOOK_ENDPOINT") if not endpoint: sys.exit("Set WEBHOOK_ENDPOINT to the URL that should receive events") # Enroll a webhook endpoint to receive delivery events. response = mailchannels.Webhooks.create(endpoint) print(response) ``` ```php PHP theme={null} webhooks->create($endpoint); print_r($response->toArray()); ``` ### Event notification format Once configured, MailChannels will send event notifications to your webhook in the following format: ```json theme={null} [ { "email": "sender@example.com", "customer_handle": "abc123", "timestamp": 1625097600, "event": "processed", "request_id": "wBLWrCnK0Z965pf-cgxhNg8bo5s=" }, { "email": "sender@example.com", "customer_handle": "abc123", "timestamp": 1625098000, "event": "delivered", "request_id": "wBLWrCnK0Z965pf-cgxhNg8bo5s=" } ] ``` ### Event fields All event notifications include these common fields: * `email`: The sender's `From` address. * `customer_handle`: Your MailChannels account ID. Visible in the upper-right corner of the Console. * `timestamp`: Unix timestamp of when the event occurred. * `event`: Event type, such as `processed`, `delivered`, or `hard-bounced`. * `request_id`: A unique identifier generated to track the original HTTP request. The `customer_handle` field lets a single webhook receiver handle events for multiple accounts. This is useful if you have sub-accounts and want a single webhook receiver for all of them. Some event types include additional fields. For the full list, see [event types](/email-api/webhook-event-types). For operational guidance on failed webhook deliveries and replaying batches, see [webhook retries and delivery behavior](/email-api/webhook-retries). For an investigation workflow that starts from a sent message, see [debug a sent message](/email-api/debug-sent-message). It is important to make sure your webhook receiver can handle up to 1,000 items in each request body. Some libraries may have default limits on the JSON body size or array length. Be sure to adjust those settings if necessary. ### Retrieving webhook configuration To view your current webhook configuration: ```bash cURL theme={null} #!/usr/bin/env bash # List all webhook endpoints currently enrolled for the account. set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}" curl -X GET "https://api.mailchannels.net/tx/v1/webhook" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" ``` ```javascript Node.js theme={null} import { MailChannels } from "mailchannels-sdk"; const { MAILCHANNELS_API_KEY } = process.env; if (!MAILCHANNELS_API_KEY) throw new Error("Set MAILCHANNELS_API_KEY before running"); const mailchannels = new MailChannels(MAILCHANNELS_API_KEY); const { data, error } = await mailchannels.webhooks.list(); if (error) { console.error("List webhooks failed:", error); process.exit(1); } console.log(data); ``` ```python Python theme={null} import os import sys import mailchannels if not os.environ.get("MAILCHANNELS_API_KEY"): sys.exit("Set MAILCHANNELS_API_KEY before running") # List all webhook endpoints currently enrolled for the account. response = mailchannels.Webhooks.list() print(response) ``` ```php PHP theme={null} webhooks->list(); print_r($response->toArray()); ``` A successful call returns **200 OK** with a JSON array of enrolled webhook endpoints. At this time, only one webhook can be configured per account. ```json Response theme={null} [ { "webhook": "https://example.com/webhooks/mailchannels" } ] ``` ### Deleting a webhook To stop receiving event notifications: ```bash cURL theme={null} #!/usr/bin/env bash # Delete all webhook endpoints enrolled for the account. set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}" curl -X DELETE "https://api.mailchannels.net/tx/v1/webhook" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" ``` ```javascript Node.js theme={null} import { MailChannels } from "mailchannels-sdk"; const { MAILCHANNELS_API_KEY } = process.env; if (!MAILCHANNELS_API_KEY) throw new Error("Set MAILCHANNELS_API_KEY before running"); const mailchannels = new MailChannels(MAILCHANNELS_API_KEY); const { success, error } = await mailchannels.webhooks.deleteAll(); if (!success) { console.error("Delete webhooks failed:", error); process.exit(1); } console.log("All webhooks deleted"); ``` ```python Python theme={null} import os import sys import mailchannels if not os.environ.get("MAILCHANNELS_API_KEY"): sys.exit("Set MAILCHANNELS_API_KEY before running") # Delete all webhook endpoints enrolled for the account. response = mailchannels.Webhooks.delete_all() print(response) ``` ```php PHP theme={null} webhooks->deleteAll(); print_r($response->toArray()); ``` A successful call returns **204 No Content** with an empty body. ### Validating webhook configuration To verify your current webhook setup, send a test event to each enrolled webhook: ```bash cURL theme={null} #!/usr/bin/env bash # Send a test event to each enrolled webhook and report whether it responded # with a 2xx status. The request_id field is optional; if omitted, one is # generated automatically. set -u : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}" curl -X POST "https://api.mailchannels.net/tx/v1/webhook/validate" \ -H "Content-Type: application/json" \ -H "X-Api-Key: $MAILCHANNELS_API_KEY" \ -d @- <webhooks->validate(requestId: 'test_request_1'); print_r($response->toArray()); ``` The response reports whether each webhook returned a 2xx status code: ```json Response theme={null} { "all_passed": true, "results": [ { "result": "passed", "webhook": "https://example.com/webhooks/mailchannels", "response": { "status": 200, "body": "Webhook received successfully" } } ] } ``` A test event message will be sent to your webhook containing the following fields: * `email`: "[test@mailchannels.com](mailto:test@mailchannels.com)". * `customer_handle`: Your account ID. * `timestamp`: Unix timestamp of the test event. * `event`: "test". * `request_id`: Either provided by you or generated automatically. * `smtp_id`: Generated automatically. ## Best practices 1. Verify the message signature. 2. Ensure your webhook endpoint can handle concurrent requests. 3. Process events asynchronously to avoid blocking the webhook receiver. 4. Implement retry logic in case of temporary failures. 5. Store raw event data before processing to allow for reprocessing if needed. ## Storing webhook data Treat webhook storage as part of your email system, not as an afterthought. A durable event table lets you answer support questions, rebuild derived state, and satisfy audit requirements. At minimum, store: * The full raw JSON payload. * The event type. * The event timestamp. * The `customer_handle`. * The `request_id` and `smtp_id`, when present. * Your processing status, such as `received`, `processed`, or `failed`. A common pattern is to acknowledge the webhook quickly, enqueue the raw event for background processing, and update the processing status after your worker finishes. Plan your retention period intentionally. Operational debugging may only need recent events, while compliance or customer support workflows may require longer retention. ## Verifying message signatures All webhooks are signed by default. There are three HTTP headers to consider during the signature verification process: * Content-Digest: hash of the message body * Signature-Input: describes what parts of the message are signed, along with other data about the signing method * Signature: the cryptographic signature ### How to verify a message The `Signature-Input` header describes how the request was signed and which parts of it were covered. For example: ``` Signature-Input: sig_1738775282=("content-digest");created=1738868393;alg="ed25519";keyid="mckey" ``` From this header you can extract: * **Signature name** — `sig_1738775282`. The matching entry in the `Signature` header uses the same name. * **Covered components** — `("content-digest")`. Only the `Content-Digest` header was signed. * **Created** — `1738868393`, a Unix timestamp. * **Algorithm** — `ed25519`. * **Key ID** — `mckey`. Identifies which public key to use for verification. Reject signatures that are older than a short window (five minutes is a reasonable default) to defend against replay attacks. Fetch the public key from MailChannels using the `keyid` from step 1: ``` GET https://api.mailchannels.net/tx/v1/webhook/public-key?id=mckey ``` Cache the response and reuse it for subsequent requests. You only need to refetch when you see a `keyid` you haven't encountered before. Rebuild the exact byte string that MailChannels signed, following the algorithm in [RFC 9421, Section 2.5](https://www.rfc-editor.org/rfc/rfc9421.html#section-2.5). For our example, the signing string is: ``` "content-digest": sha-256=:6R+3pwkD8ueMsjjr7Q6+7Zvj9BhpMJKHEAqpc1YRxi0=: "@signature-params": ("content-digest");created=1738868393;alg=ed25519;keyid=mckey ``` The rules are: * For each covered component, write the lowercased name in quotes, then `: `, then the header value, on a single line. * Append a final `"@signature-params"` line with the parenthesised component list followed by the remaining `Signature-Input` parameters. * Join lines with a single `\n` and do not include a trailing newline. Use an HTTP Message Signatures library that implements RFC 9421 rather than building this string by hand. Small deviations (whitespace, header casing, trailing newlines) cause verification to fail silently. The `Signature` header carries each signature as `=::`. For example: ``` Signature: sig_1738775282=:YtB126we6ICgMHHjLg...: ``` Base64-decode the value between the colons, then verify the resulting bytes against the signing string from step 4 using the public key and the `ed25519` algorithm. ## Example signature verification code ```go Go theme={null} package main import ( "crypto/ed25519" "crypto/x509" "encoding/json" "encoding/pem" "fmt" "io" "log" "net/http" "net/url" "time" "github.com/yaronf/httpsign" ) const ( ExpectedCustomerHandle = "myaccountid" ) type Handler struct { Verifier httpsign.Verifier } type WebhookPayload struct { Email string `json:"email"` CustomerHandle string `json:"customer_handle"` Timestamp int64 `json:"timestamp"` Event string `json:"event"` RequestID string `json:"request_id"` } func (h Handler) webhookHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } body, err := io.ReadAll(r.Body) if err != nil { http.Error(w, "Error reading request body", http.StatusInternalServerError) return } defer r.Body.Close() var payloads []WebhookPayload err = json.Unmarshal(body, &payloads) if err != nil { http.Error(w, "Error parsing JSON", http.StatusBadRequest) return } err = httpsign.VerifyRequest("sig_1738775282", h.Verifier, r) fmt.Printf("verified: %t\n", err == nil) if err != nil { fmt.Printf("Error verifying request: %s\n", err) http.Error(w, "Error verifying request", http.StatusInternalServerError) return } for _, payload := range payloads { // Verify customer handle if payload.CustomerHandle != ExpectedCustomerHandle { http.Error(w, "Invalid customer handle", http.StatusForbidden) return } // Validate required fields if payload.CustomerHandle == "" || payload.Timestamp == 0 || payload.Event == "" { http.Error(w, "Missing required fields", http.StatusBadRequest) return } // Validate event type validEvents := map[string]bool{ "open": true, "click": true, "processed": true, "dropped": true, "delivered": true, "hard-bounced": true, "soft-bounced": true, "unsubscribed": true, } if !validEvents[payload.Event] { http.Error(w, "Invalid event type", http.StatusBadRequest) return } // Process the webhook payload fmt.Printf("Received webhook: Email: %s, Customer: %s, Event: %s, Id: %s, Time: %s\n", payload.Email, payload.CustomerHandle, payload.Event, payload.RequestID, time.Unix(payload.Timestamp, 0).Format(time.RFC3339)) } w.WriteHeader(http.StatusOK) w.Write([]byte("Webhook received successfully")) } func parsePEMED25519PublicKey(pemData string) (ed25519.PublicKey, error) { block, _ := pem.Decode([]byte(pemData)) if block == nil { return nil, fmt.Errorf("failed to decode PEM block") } pubKey, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { return nil, fmt.Errorf("failed to parse public key: %w", err) } edPubKey, ok := pubKey.(ed25519.PublicKey) if !ok { return nil, fmt.Errorf("not a valid Ed25519 public key") } return edPubKey, nil } func retrievePublicKey(baseURL, keyID string) (string, error) { endpoint := fmt.Sprintf("%s/webhook/public-key", baseURL) params := url.Values{} params.Add("id", keyID) fullURL := fmt.Sprintf("%s?%s", endpoint, params.Encode()) resp, err := http.Get(fullURL) if err != nil { return "", fmt.Errorf("failed to make request: %w", err) } defer resp.Body.Close() switch resp.StatusCode { case http.StatusOK: var key struct { ID string `json:"id"` Key string `json:"key"` } if err := json.NewDecoder(resp.Body).Decode(&key); err != nil { return "", fmt.Errorf("failed to decode response: %w", err) } return key.Key, nil case http.StatusNotFound: return "", fmt.Errorf("key not found") case http.StatusInternalServerError: return "", fmt.Errorf("internal server error") default: return "", fmt.Errorf("unexpected status code: %d", resp.StatusCode) } } func main() { baseURL := "https://api.mailchannels.net/tx/v1/" keyID := "mckey" publicKeyStr, err := retrievePublicKey(baseURL, keyID) if err != nil { log.Fatalf("error retrieving public key: %s", err) } publicED25519Key, err := parsePEMED25519PublicKey(publicKeyStr) if err != nil { log.Fatalf("error parsing public key: %s", err) } config := httpsign.NewVerifyConfig().SetKeyID(keyID).SetVerifyCreated(true).SetNotOlderThan(5 * time.Minute) verifier, _ := httpsign.NewEd25519Verifier(publicED25519Key, config, httpsign.Headers("Content-Digest")) h := Handler{Verifier: *verifier} http.HandleFunc("/", h.webhookHandler) fmt.Println("Starting server on :8080") log.Fatal(http.ListenAndServe(":8080", nil)) } ``` ```javascript Node.js theme={null} import express from 'express'; import { Webhooks } from 'mailchannels-sdk'; const app = express(); const port = 3000; const EXPECTED_CUSTOMER_HANDLE = 'myaccountid'; app.use(express.json({ limit: '5mb', verify: (req, res, buf) => { req.rawBody = buf.toString(); } })); app.post('/', async (req, res) => { const { error } = await Webhooks.verify({ payload: req.rawBody, headers: req.headers, }); if (error) { console.error('Webhook verification failed:', error.message); return res.status(403).json({ error: error.message }); } for (const payload of req.body) { const { email, customer_handle, timestamp, event, request_id } = payload; if (!customer_handle || !timestamp || !event) { return res.status(400).json({ error: 'Missing required fields' }); } if (customer_handle !== EXPECTED_CUSTOMER_HANDLE) { return res.status(403).json({ error: 'Invalid customer handle' }); } console.log(`Received webhook: Email: ${email}, Customer: ${customer_handle}, Event: ${event}, ID: ${request_id}, Time: ${new Date(timestamp * 1000).toISOString()}`); } res.status(200).json({ message: 'Webhook received successfully' }); }); app.listen(port, () => { console.log(`Webhook receiver listening at http://localhost:${port}`); }); ``` ```python Python theme={null} from flask import Flask, request import mailchannels app = Flask(__name__) EXPECTED_CUSTOMER_HANDLE = "myaccountid" VALID_EVENTS = { "open", "click", "processed", "delivered", "dropped", "hard-bounced", "soft-bounced", "unsubscribed", } # Fetch the public key once at startup. In a long-running service, refresh # this if the `keyid` parameter in incoming Signature-Input headers changes. public_key = mailchannels.Webhooks.public_key("mckey").key @app.post("/") def webhook(): raw_body = request.get_data() headers = dict(request.headers) # Verifies Content-Digest, Signature-Input freshness, and the Ed25519 signature. if not mailchannels.verify_webhook_signature(headers, raw_body, public_key): return {"error": "Invalid signature"}, 403 payloads = request.get_json() if not isinstance(payloads, list): return {"error": "Payload must be an array"}, 400 for payload in payloads: customer_handle = payload.get("customer_handle") timestamp = payload.get("timestamp") event = payload.get("event") if not customer_handle or not timestamp or not event: return {"error": "Missing required fields"}, 400 if customer_handle != EXPECTED_CUSTOMER_HANDLE: return {"error": "Invalid customer handle"}, 403 if event not in VALID_EVENTS: return {"error": "Invalid event type"}, 400 print(f"Received webhook: {payload}") return {"message": "Webhook received successfully"}, 200 if __name__ == "__main__": app.run(port=3000) ``` # Introduction Source: https://docs.mailchannels.com/inbound/api-reference-introduction View the HTTP API reference for the MailChannels Inbound Provisioning API. This section provides in-depth information about the MailChannels Inbound Provisioning API. If you're new to inbound filtering, consider starting with the [Overview](/inbound/overview), or the [Provisioning API](/inbound/provisioning-api) guide for a walkthrough of what you can automate. ## Base URL The base URL for API requests is `https://api.mailchannels.net/inbound/v1`. All requests are made to this URL, and must use HTTPS. ## Authentication All requests to the Provisioning API must contain the `X-API-Key` header with your API key as the value. Requests without a valid API key are rejected with a `401 Unauthorized` response. API keys are created and managed in the Host Console under **Settings > Account > API Keys**. See the [Provisioning API](/inbound/provisioning-api) guide for more information about API keys and how to manage them. Most domain operations also require a `subscriptionHandle` query parameter that identifies which MailChannels subscription the domain belongs to. Retrieve your subscription handles from the `/subscriptions` endpoint. ## Rate limits The API allows up to 100 requests per second per customer. Requests that exceed this limit receive a `503 Service Temporarily Unavailable` response. # MailChannels Inbound Filtering pricing and billing Source: https://docs.mailchannels.com/inbound/billing Per-domain pricing with volume discounts, free trial details, and how billing and payments work for MailChannels Inbound Filtering. Inbound Filtering is priced per domain per month with volume discounts ? the more domains you protect, the lower your cost per domain. You can start with a 30-day free trial before committing to a paid plan. ## Pricing Pricing is calculated per domain per month. Volume tiers mean your cost per domain decreases as the number of protected domains grows. Because pricing depends on your domain volume, MailChannels provides quotes directly rather than publishing a fixed price list. To get a quote, [contact MailChannels Sales](https://www.mailchannels.com/contact-sales/) and let them know how many domains you need to filter. ## Free trial MailChannels offers a **30-day free trial** for Inbound Filtering. There is no upfront cost and no obligation to continue. To start a trial, [contact the sales team](https://www.mailchannels.com/contact-sales/). Once your account is set up, follow the [hosting provider setup guide](/inbound/hosting-providers) to configure your first domains. MailChannels also provides a **satisfaction guarantee**: if Inbound Filtering does not meet your expectations within the first 30 days of a paid subscription, [submit a support request](https://support.mailchannels.com/hc/en-us/requests/new) to the billing team for a full refund. ## Contract terms MailChannels offers both monthly plans and longer-term contracts: * **Month-to-month** ? the default option. You are billed monthly based on the number of domains protected during that billing period. * **Longer-term contracts** ? annual or multi-year terms are available and may offer better per-domain rates. Contact [sales@mailchannels.com](mailto:sales@mailchannels.com) for details. There is **no minimum commitment** on the month-to-month plan. You can cancel at any time with no cancellation fee. If you cancel, you will be billed for the current month but will not be charged again after that. Longer-term contracts may have different cancellation terms. Review your agreement or contact [sales@mailchannels.com](mailto:sales@mailchannels.com) if you are on a term contract and want to make changes. ## Payment methods MailChannels accepts the following payment methods, all billed in USD: * Visa * Mastercard * Discover * American Express * PayPal If you need to update your billing details or have questions about a payment, [submit a support request](https://support.mailchannels.com/hc/en-us/requests/new) to the billing team. ## Manage your subscription To upgrade your plan, add more domains, or discuss a longer-term contract, contact [MailChannels Sales](https://www.mailchannels.com/contact-sales/). For billing questions, cancellations, or refund requests, [submit a support request](https://support.mailchannels.com/hc/en-us/requests/new) to the billing team. # Attachment restrictions in MailChannels Inbound Filtering Source: https://docs.mailchannels.com/inbound/blocked-attachments MailChannels Inbound Filtering enforces a 40 MB message size limit and blocks high-risk file attachment types to protect recipients from malware and phishing. MailChannels Inbound Filtering enforces two types of attachment restrictions: a maximum message size and a list of blocked file types. ## Maximum attachment size Inbound messages are accepted up to a maximum size of **40 MB**. Messages that exceed this limit are rejected at the connection level and not delivered. ## Blocked file types MailChannels blocks certain high-risk file attachment types to protect recipients from malware, phishing, and other security threats. The blocked file type policy applies to both inbound and outbound filtering and is consistent with the policies of major providers such as Gmail, Microsoft Outlook, and Yahoo. For the full list of blocked file extensions, the reasons they are blocked, and guidance on safe alternatives, see [File attachments blocked by MailChannels](/outbound/blocked-attachments). # MailChannels WHM/cPanel plugin for Inbound Filtering Source: https://docs.mailchannels.com/inbound/cpanel-plugin Install and configure the MailChannels WHM/cPanel plugin to automate inbound domain management directly from the WHM admin interface. The MailChannels WHM/cPanel plugin automates inbound domain management directly from the WHM admin interface. Once installed, you can protect domains, manage their status, and keep MX records up to date without leaving WHM. ## What the plugin does * Protect individual domains or all domains in bulk from the WHM interface * View the protection status of every domain on your server * Automatically provision newly added cPanel domains * Automatically deprovision domains removed from cPanel * Automatically update MX records and downstream addresses when protecting a domain * Provision parked and addon domains alongside primary domains * Support multiple MailChannels subscriptions on the same server ## Requirements | Requirement | Minimum version | | - | - | | cPanel & WHM | 68 or higher | | PHP | 5.6 or higher | ## Install the plugin 1. Log in to your server via SSH as `root`. 2. Run the following command: ```bash theme={null} wget https://s3.amazonaws.com/mailchannels-reseller/plugins/mailchannels-cpanel-v3-installer \ && bash mailchannels-cpanel-v3-installer ``` If the v2 plugin is currently installed, the v3 installer will prompt you to remove it. If you decline, the installer exits and you must manually remove v2 before running the installer again. ## Uninstall the plugin Run the following command as `root`: ```bash theme={null} /usr/local/mailchannels/bin/uninstall.sh ``` Add the `--purge` flag to also remove all log files (`/var/log/mailchannels`) and configuration files (`/etc/mailchannels`) from disk. Without `--purge`, those files are left in place. ## Set up the plugin ### Open the plugin in WHM 1. Log in to your WHM admin dashboard. 2. Search for **MailChannels** in the navigation search box. WHM navigation search showing the MailChannels plugin menu item 3. Click the result to open the plugin. ### Configure your API key 1. In the plugin, click **Configuration** in the left navigation. 2. Expand the **API** section. 3. Retrieve your API key from the [Host Console](https://console.mailchannels.net) under **Settings > Account > API Keys**. 4. Enter your API key in the field and click **Update**. API configuration panel showing the API key input field ## Inbound configuration To configure inbound filtering options, expand the **Inbound** dropdown inside the **Configuration** view. If your API key is not yet configured, the Inbound section will show an alert: *"You don't have a valid API key set up. Please set up your API key before selecting a subscription."* Configure your API key first before continuing. Inbound configuration panel in the WHM plugin ### Subscription Select the MailChannels subscription to provision domains against. If you have only one subscription it is selected automatically. Changing this setting does not affect domains that are already provisioned ? it only applies to future provisioning actions. ### Automatically protect newly added domains When enabled, newly created cPanel accounts are automatically added to inbound filtering. Pair this with **Automatically update DNS configuration** for full hands-off coverage. ### Automatically deprovision removed domains When enabled, domains deleted from cPanel are automatically removed from your MailChannels subscription, freeing up license allocations. Enabled by default. ### Automatically update DNS configuration When enabled, the plugin automatically writes the required downstream and MX records into the zone file when a domain is protected, and removes them when a domain is unprotected. Enabled by default. ### Process parked and addon domains When enabled, bulk and automatic protection operations include parked and addon domains alongside primary domains. MX records for parked domains are replaced with MailChannels entries and restored when unprotected. ### Redirect mail to custom downstream address When enabled, filtered mail is forwarded to a specific downstream address you define, rather than using the domain's existing MX records as the delivery target. ## Bulk domain protection To protect multiple domains at once, use the **Protect Selected** or **Protect All** actions in the plugin. After a bulk operation completes, a summary report shows how many domains were **Completed**, **Skipped**, or **Failed**. * Filter the results by clicking the **Status** column header to group failures together. * Hover over a failed domain's status to see the specific error. * Use **Retry** on individual domains or **Retry All Failed** to re-queue all failures. # DMARC enforcement in MailChannels Inbound Filtering Source: https://docs.mailchannels.com/inbound/dmarc-enforcement How MailChannels enforces DMARC policies on inbound mail and how the sender's DMARC policy interacts with your spam handling configuration. DMARC (Domain-based Message Authentication, Reporting & Conformance) is an email authentication standard that lets domain owners publish a policy instructing receiving mail servers how to handle messages that fail SPF or DKIM alignment checks. When a sender's domain has a DMARC policy, MailChannels reads and enforces it on all inbound messages. The action MailChannels takes depends on the combination of the sender's published DMARC policy and the spam handling policy you have configured for your protected domain. | Sender DMARC policy | Your spam handling policy | MailChannels action | | - | - | - | | `p=reject` | block | block | | `p=reject` | quarantine | block | | `p=reject` | flag | block | | `p=quarantine` | block | block | | `p=quarantine` | quarantine | quarantine | | `p=quarantine` | flag | flag | | `p=none` | block | none | | `p=none` | quarantine | none | | `p=none` | flag | none | When the sending domain publishes `p=reject`, MailChannels blocks unauthorized messages regardless of your local spam handling policy. When the sender uses `p=none`, DMARC monitoring mode applies and no DMARC-based action is taken. Senders whose messages are rejected due to DMARC will receive a `550 5.7.1 Unauthenticated email is not accepted due to domain's DMARC policy` error. See [SMTP errors](/inbound/smtp-errors) for more detail. To change how your domain handles spam, see [Configure spam handling](/inbound/domain-admins#configure-spam-handling). # Become a domain administrator Source: https://docs.mailchannels.com/inbound/domain-admin-promotion Verify DNS ownership of your domain to become a domain administrator and manage spam policies, safelists, and blocklists in Inbound Filtering. Creating a MailChannels account does not automatically make you a domain administrator. You need to verify that you control the domain's DNS to gain administrative privileges ? such as configuring spam handling policies, safelists, and blocklists for your entire domain. You must be logged in as the user you want to promote. The **Verify** button is not available when accessing a domain through Ghost Admin access (a hosting provider's Login shortcut). ## Promote a domain administrator 1. Go to the [Getting Started](https://app.mailchannels.com/gettingStarted) page in your Domain Console. 2. Open the **Promote a Domain Administrator** panel. 3. Add the DNS TXT record shown in the panel at the root of your domain. 4. Click **Verify** to confirm the record and complete the promotion. DNS changes can take several minutes to propagate. If verification doesn't succeed immediately, wait a few minutes and try again. If you don't have access to your domain's DNS, contact someone in your organization who does. # Set up MailChannels Inbound Filtering as a domain admin Source: https://docs.mailchannels.com/inbound/domain-admins Create a MailChannels account, verify domain ownership, and configure MX records and downstream delivery to activate inbound filtering for your domain. Domain admins are the people responsible for managing a domain's email settings ? typically the person who controls the domain's DNS records. If your hosting provider has provisioned your domain for inbound filtering, you complete the setup by creating a MailChannels account, verifying your domain ownership, and configuring DNS and delivery records. MailChannels Inbound Filtering is currently available through hosting provider partners. Before starting, confirm with your hosting provider that they support MailChannels and that it has been enabled for your domain. ## Set up inbound filtering Visit [app.mailchannels.com/signup](https://app.mailchannels.com/signup) and create an account using an email address at your private domain. Public webmail addresses such as `@gmail.com` or `@yahoo.com` are not accepted. After signing up, open the confirmation email from MailChannels and click **Confirm Email Address** to activate your account. Creating an account does not automatically make you a domain administrator. Verify DNS ownership of your domain to gain full administrative privileges. See [Domain admin promotion](/inbound/domain-admin-promotion) for instructions. Configure a downstream record so MailChannels knows where to deliver filtered mail, then update your MX records to route inbound traffic through MailChannels. See [MX & downstream records](/inbound/downstream-records) for full instructions. ## Configure spam handling Once filtering is active, configure how MailChannels handles messages identified as spam: 1. Sign in to the [Domain Console](https://app.mailchannels.com). 2. Click **Domain Settings** in the left navigation. 3. In the **Configure Spam Handling** panel, select one of the following options: * **Block Spam** ? spam is rejected at the connection level and not delivered. * **Quarantine Spam** ? spam is held in quarantine for you to review. See [Quarantine](/inbound/quarantine) for how to manage quarantined messages. * **Flag Spam** ? spam is delivered with a custom header. Enter the header name and value in the fields provided. 4. To prevent users from overriding this setting for their own account, enable the **Enforce for all users** toggle. 5. Click **Update** to save. Domain Settings page showing the Configure Spam Handling panel ## What happens next With your MX records pointing to MailChannels and a downstream record configured, inbound filtering is fully active. All email sent to your domain passes through MailChannels before reaching your mail server. Use your [Domain Console](https://app.mailchannels.com) to manage your filtering settings. Review, release, and manage messages held in quarantine for your domain. Allow or block specific senders, domains, and IP addresses for your domain. Configure how MailChannels handles messages that fail DMARC authentication. View all mailbox users in your domain and see which users have email filtering active. # Domain aliases in MailChannels Inbound Filtering Source: https://docs.mailchannels.com/inbound/domain-alias Associate additional domain names with a primary domain so MailChannels filters mail for every alias under a single shared configuration and policy. Domain aliasing lets you associate additional domain names with an existing primary (canonical) domain. All messages sent to an alias domain are processed exactly as if they were sent to the primary domain ? they follow the same downstream delivery path, spam policy, and quarantine settings. Key points about domain aliases: * Aliases do not count against your filtered domain quota and do not incur extra charges. * A primary domain can have any number of aliases. * You cannot create logins using an alias domain. Authentication in the Domain Console requires the primary domain. * A quarantine digest includes messages addressed to both the primary recipient and any alias recipients with the same username (for example, `b@alias.example.com` is treated as an alias recipient for `b@example.com`). ## Add a domain alias In the Domains list, find the primary domain you want to alias. The primary domain must already be added to your console before you can assign an alias. Click the **Settings** (gear) icon for the primary domain, then select **Domain Alias Settings** from the menu. The dialog shows any existing aliases for this domain. Inbound Domain Alias Settings Gear Enter the full alias domain name in the text box and click **Add**. The alias appears both in the main Domains list and inside the Domain Alias Settings dialog for the primary domain. Any attempt to manage an alias domain as if it were a primary domain ? for example, adding blocklist entries directly to it ? will result in an error. Always manage settings on the primary domain. # Manage domains in MailChannels Inbound Filtering Source: https://docs.mailchannels.com/inbound/domains Add and manage domains protected by MailChannels Inbound Filtering with single entry, bulk CSV upload, domain aliases, and downstream delivery. The Domains section of the Host Console is where you add and manage every domain protected by MailChannels Inbound Filtering. You can add domains one at a time, upload them in bulk using a CSV file, configure domain aliases, and provision parked domains. Once a domain is added, you can search for it, access its settings, and log in to the Domain Console to configure users, downstream mail servers, and safelists. ## Add a single domain Go to [console.mailchannels.net](https://console.mailchannels.net) and sign in with your host credentials. Google SSO MC Hostconsole Login From the Dashboard, click **Inbound**, then click **Domains**. Click **Add New Domains**, enter the domain name in the text field, and click **Add**. Inbound Add A Domain A confirmation message displays the number of domains successfully added, and the new domain appears in the list below ## Add domains in bulk using a CSV file You can add up to 1,000 domains at once by uploading a CSV file. Each row in the file represents one domain and its downstream mail server address or addresses. Create a plain text file with one domain per line. Do not include column headers. Each row must contain the domain name followed by its downstream address in `target:port` or `target:port:weight` format. The downstream address tells MailChannels where to deliver filtered mail after scanning. For domains with multiple downstream targets, enclose all addresses in double quotes and separate them with commas: ```csv theme={null} example.com,mx.example.com:25 example2.com,"mx.example.com:25:10,mx2.example.com:25" ``` The `weight` value is optional. When you specify multiple downstream addresses, MailChannels uses the weight to determine delivery priority. A lower weight means higher priority. From the Dashboard, click **Inbound**, then click **Domains**. Click **Add New Domains**, choose the file upload option, select your CSV file, and click **Add**. After the upload completes, a summary message confirms how many domains were added successfully. Any rows that failed to import are reported so you can correct and re-upload them. ## Add a domain alias Domain aliasing lets you associate additional domain names with an existing primary domain. Aliases share the same downstream delivery path, spam policy, and quarantine settings as the primary domain and do not count against your filtered domain quota. See [Domain aliases](/inbound/domain-alias) for full details and setup instructions. ## Search for a domain 1. From the Dashboard, click **Inbound**, then click **Domains**. 2. Type your search terms into the **Search by domain** field. 3. Press **Enter** or click the magnifying glass button. The list filters to show matching domains. You can search by exact match or partial match. ## Manage domain settings To configure settings for a specific domain, click **Login** next to the domain in the Domains list. This opens the Domain Console for that domain, where you can manage: * Users and user policies * Domain administrators * MX records and downstream mail server addresses * Safelists and blocklists ## API-key domain association You can associate groups of domains with specific API keys rather than managing all domains under a single key. This limits the blast radius if a key is ever compromised ??? only the domains associated with that key are at risk. See [Automate inbound domain management with the Provisioning API](/inbound/provisioning-api) for details. # Configure MX records and downstream delivery Source: https://docs.mailchannels.com/inbound/downstream-records Configure your domain's MX records to route mail through MailChannels and set up a downstream record so filtered mail is delivered to your mail server. MailChannels Inbound DNS Architecture Two records must be configured for mail to flow through MailChannels: * **Downstream record** ? tells MailChannels where to deliver filtered mail (your mail server) * **MX records** ? tells the internet to send mail for your domain to MailChannels first Configure your downstream record **before** updating your MX records. Once your MX records go live, mail starts routing through MailChannels immediately. If the downstream record is not in place, MailChannels will have nowhere to forward filtered mail and messages will not be delivered. ## Configure your downstream record Go to the [Settings](https://app.mailchannels.com/settings) page in your Domain Console and expand the **Configure Downstream Records** panel. Configure Downstream Records panel Enter your mail server hostname ? this is the same hostname currently used in your domain's MX records. Click **Add Record** for each hostname, then click **Update** to save and validate. If you are unsure about Port and Weight, leave the default values. Validation checks that `postmaster@` exists at the downstream hostname you provide. This is a required reserved account per [RFC 5321](https://tools.ietf.org/html/rfc5321#section-4.5.1). If validation fails, ensure your mail server has a valid `postmaster` user and try again. ## Update your MX records Only proceed once your downstream record is saved and validated. Update your domain's MX records to route inbound mail through MailChannels by replacing your existing records with: | Priority | Mail server | | - | - | | 10 | `mx1.mailchannels.net` | | 20 | `mx2.mailchannels.net` | Configure MX Records panel DNS changes can take several minutes to propagate. Once the records are live, click **Check** in the Configure MX Records panel to confirm they are set up correctly. MX records validation success If your mail server or firewall restricts incoming SMTP connections, safelist the MailChannels egress IP addresses so filtered mail can reach your server. See [Restrict inbound connections](/inbound/restrict-inbound-connections#safelist-mailchannels-service-ips) for the full IP list. # MailChannels Inbound Filtering FAQ Source: https://docs.mailchannels.com/inbound/faq Answers to common questions about MailChannels Inbound Filtering, including data storage, message retention, spam handling, and account setup. This page answers common questions about MailChannels Inbound Filtering. If you have a question not covered here, contact [support@mailchannels.com](mailto:support@mailchannels.com). All data is stored in the United States. Email messages are queued on US-based infrastructure during filtering. Queuing is typically brief ? only a few seconds in most cases ? but messages may remain queued longer if your downstream mail server is temporarily unavailable. If quarantine is enabled for your domain, quarantined messages are stored in the US for up to 7 days before being automatically deleted. MailChannels does not currently offer a native WHMCS module. However, you can build your own WHMCS integration using the [Inbound Provisioning API](/inbound/provisioning-api). The API lets you provision and deprovision domains, manage safelists and blocklists, and automate domain configuration ? giving you everything you need to connect MailChannels to WHMCS or any other billing and provisioning platform. For questions about API access, contact [support@mailchannels.com](mailto:support@mailchannels.com). # Set up MailChannels Inbound Filtering as a hosting provider Source: https://docs.mailchannels.com/inbound/hosting-providers Provision MailChannels inbound filtering for customer domains, prepare your mail servers, and manage spam and phishing protection from the Host Console. As a hosting provider, you provision inbound filtering for your customer domains from a single Host Console. You add domains, prepare your mail servers to work with MailChannels, and hand off per-domain DNS configuration to domain admins ? or complete it yourself if you're acting in that role. ## Sign up for Inbound Filtering [Contact MailChannels Sales](https://www.mailchannels.com/contact-sales/) to get started with Inbound Filtering. A member of the sales team will reach you by email within 24 hours. ## Set up inbound filtering If your servers run WHM/cPanel, the [MailChannels cPanel plugin](/inbound/cpanel-plugin) automates most of the manual steps below. If you're not using the plugin, follow the steps in this guide to set up inbound filtering manually. Once the MailChannels sales team has provisioned your account, log in to your [Host Console](https://console.mailchannels.net/login/auth) and select **Inbound** from the left navigation menu. From the Inbound menu, select **Domains**, then click **Add Domains**. You can add domains in three ways: * **Single domain** ? enter one domain name and click **Add**. * **Bulk upload** ? upload a CSV file with up to 10,000 domains per submission. * **Provisioning API** ? automate domain provisioning programmatically. See the [Provisioning API](/inbound/provisioning-api) guide for details. Once provisioned, all domains appear in your **Host Console ? Inbound ? Domains** list, where you can search and access each one. Provisioning a domain in the Host Console does not route mail through MailChannels yet. Domain admins still need to complete DNS configuration before filtering is active. Your mail servers must support the same SMTP services advertised by the MailChannels MX servers. Specifically, **SMTPUTF8** is required. MailChannels MX relay servers negotiate with the sender at the time of receipt. If your mail server doesn't support SMTPUTF8, any message negotiated using UTF-8 encoding will be returned to the sender as undeliverable ? it cannot be renegotiated at delivery time. Enable SMTPUTF8 globally on your mail server. The exact steps vary by software; consult your mail server's documentation. Your mail servers must accept connections from MailChannels delivery servers so that filtered mail can be forwarded to your downstream mail server. Add the MailChannels service IP addresses to your mail server's allowlist. See [Restrict inbound connections](/inbound/restrict-inbound-connections#safelist-mailchannels-service-ips) for the full IP list. Each provisioned domain needs DNS configuration before filtering is active ? MX records pointing to MailChannels and a downstream record pointing to the actual mail server. This can be completed by the hosting provider or delegated to the domain admin. You have two options: * **Delegate to your customer** ? share the [domain admin setup guide](/inbound/domain-admins) with the customer who manages the domain's DNS. * **Complete it yourself** ? go to [Inbound ? Domains](https://console.mailchannels.net/inbound/domains) and click **Login** next to the domain to open its Domain Console, then follow the [MX & downstream records](/inbound/downstream-records) guide. The **Login** button gives you access to configure that domain's settings, but does not promote you to domain administrator. To gain full administrative privileges, the domain admin must complete the [domain admin promotion](/inbound/domain-admin-promotion) process. ## Next steps Guide for domain admins to create an account, verify domain ownership, and configure MX records and downstream delivery. Configure your mail servers to only accept inbound connections from MailChannels for domains protected by inbound filtering. Configure global safelists and blocklists that apply across all your provisioned domains. Automate domain provisioning at scale using the MailChannels Provisioning API. # Internationalized domain names (IDNs) in Inbound Filtering Source: https://docs.mailchannels.com/inbound/internationalized-domain-names Protect internationalized domain names (IDNs) with MailChannels Inbound Filtering, including Punycode-encoded domains and non-ASCII top-level domains. An internationalized domain name (IDN) is a domain name that contains non-ASCII characters ? Arabic, Chinese, Cyrillic, accented Latin letters, and so on. Because DNS only supports ASCII characters, IDNs are encoded using [Punycode](https://datatracker.ietf.org/doc/html/rfc5891), which converts Unicode characters into an ASCII-compatible form. Punycode-encoded domain labels begin with the prefix `xn--`. For example: | Unicode (U-label) | Punycode (A-label) | | - | - | | `m?nchen.de` | `xn--mnchen-3ya.de` | | `??.jp` | `xn--r8jz45g.jp` | | `?????.??` | `xn--80a1acny.xn--p1ai` | MailChannels Inbound Filtering supports both second-level IDNs and internationalized top-level domains (TLDs). ## How MailChannels handles IDNs When you add an IDN, MailChannels automatically registers both its Unicode (U-label) form and its Punycode (A-label) equivalent. This ensures mail is filtered correctly regardless of which form a sending server presents. Email authentication checks (including SPF) convert U-labels to A-labels before performing DNS lookups, so records published under Punycode domain names are evaluated correctly. MX record verification in the Domain Console applies the same conversion. ## Add an IDN Add an IDN the same way you add any domain ? single entry or CSV bulk upload in the Host Console, or via the Provisioning API. See [domain management](/inbound/domains) for the full steps. You can enter the domain in either its Unicode form (`m?nchen.de`) or its Punycode form (`xn--mnchen-3ya.de`). MailChannels handles both. If you use the cPanel/WHM plugin to provision domains, IDN domains are supported and can be added directly through the plugin. ## IDNs and domain aliases If you add an IDN as a [domain alias](/inbound/domain-alias), MailChannels automatically creates a second alias for the Punycode equivalent. Deleting the Unicode alias also removes the Punycode alias. You do not need to manage both manually. ## Convert between Unicode and Punycode Use a tool like [punycoder.com](https://www.punycoder.com/) to convert between the Unicode and Punycode forms of a domain. # MailChannels Inbound Filtering: stop spam before delivery Source: https://docs.mailchannels.com/inbound/overview A cloud-based email filter that blocks spam, phishing, malware, and viruses before they reach your mail server ? for hosting providers and domain admins. MailChannels Inbound Filtering is a cloud-based email security service that scans all incoming mail before it reaches your server. Your domain's MX records point to MailChannels, so every message passes through the filter first ? spam, phishing, malware, and viruses are caught before they ever reach your users. ## How it works Inbound Filtering sits between the public internet and your mail server. When a sender delivers email to your domain, their message is routed to MailChannels instead of directly to your server. MailChannels scans the message, applies your spam handling policies, and then forwards clean mail to your downstream mail server. To enable this flow, you configure two things: * **MX records** ? point `mx1.mailchannels.net` and `mx2.mailchannels.net` as the MX records for your domain, so all inbound mail is routed through MailChannels first. * **Downstream record** ? a record inside your MailChannels Domain Console that tells MailChannels where to deliver mail after filtering (your actual mail server). Once configured, the full flow is: sender ? MailChannels (scan) ? your mail server ? user's inbox. ## What it blocks MailChannels Inbound Filtering identifies and handles: * **Spam** ? unsolicited bulk email and junk messages * **Phishing** ? messages that impersonate trusted senders to steal credentials or data * **Malware and viruses** ? attachments and links carrying malicious payloads * **Email authentication failures** ? messages that fail SPF, DKIM, or DMARC checks ## How filtered mail is handled When a message is caught, you control what happens next. You can configure your spam handling policy to **block** (reject the message entirely), **quarantine** (hold it for review), or **flag** (deliver it with a warning header). Senders are notified when a message is rejected. In some cases, senders are also notified when a message is quarantined ? the notification may include a link to release the message directly. Mailbox users receive a periodic quarantine digest with links to release, block, or safelist messages. ## What it is not MailChannels Inbound Filtering is a filter, not a mail host. It scans incoming mail and forwards clean messages to your downstream mail server ? it does not receive, store, or serve mailboxes for your users. You still need a separate mail hosting provider or self-hosted mail server to hold and deliver mail to inboxes. The MailChannels [Email API](/email-api/overview) is likewise a sending service, not a receiving one. If you need to process replies to messages sent through the Email API, see [Inbound email replies](/email-api/inbound-email-replies) for the supported pattern. ## Who it's for Hosting providers are the primary audience ? they provision and manage inbound filtering across their customer domains. Once a provider has enabled filtering for a domain, domain admins can log in to complete setup and manage their own settings. Provision and manage inbound filtering across all your customer domains from a central Host Console. Add domains individually or in bulk, configure global safelists and blocklists, and let domain admins complete per-domain DNS configuration. Complete setup after your hosting provider has enabled inbound filtering for your domain. Create an account, verify domain ownership, configure MX records and downstream delivery, and manage your spam policies. MailChannels Inbound Filtering is currently available through hosting provider partners. If you're a domain admin, contact your hosting provider to confirm that they support MailChannels and that it has been enabled for your domain. # Parked domains in MailChannels Inbound Filtering Source: https://docs.mailchannels.com/inbound/parked-domains Automatically provision and protect parked and addon domains alongside primary domains in MailChannels Inbound Filtering using the WHM/cPanel plugin. ## Provision parked domains If you use the MailChannels WHM/cPanel plugin (version 4.3.0 or later), parked domains can be provisioned automatically alongside your primary domains. The parked domain provisioning task runs approximately 60 seconds after the standard domain provisioning task, because the parked domain must exist in WHM before MailChannels can protect it. To enable automatic provisioning of parked and addon domains, open the plugin's **Inbound Configuration** section in WHM and enable the **Process parked and addon domains during bulk and automatic protection operations** option. If your parked domains are not appearing after 60 seconds, your server may be missing the `at` package, which handles delayed job execution. Install it using your distribution's package manager (`apt` or `yum`) and then re-provision the domains. To troubleshoot parked domain provisioning, set `LOG_LEVEL` to `LogLevel::DEBUG` in `/usr/local/mailchannels/conf/Config.php`. Remember to set it back to `LogLevel::INFO` once you have collected the log data you need. # Automate inbound domain management with the Provisioning API Source: https://docs.mailchannels.com/inbound/provisioning-api Use the MailChannels Inbound Provisioning API to manage domains, safelists, blocklists, downstream addresses, and console logins programmatically. The MailChannels Inbound Provisioning API gives you full programmatic control over your inbound filtering configuration. Instead of managing domains one by one through the Host Console, you can provision and deprovision domains in bulk, manage safelists and blocklists at every tier, update downstream addresses, and generate Domain Console login links ? all from your own systems or automation scripts. The full API reference, including all endpoints, request parameters, and response schemas, is available at [api.mailchannels.net/inbound/v1/documentation](https://api.mailchannels.net/inbound/v1/documentation). ## Authentication All requests must be made over HTTPS to the base URL: ``` https://api.mailchannels.net/inbound/v1/ ``` Authenticate by including your API key in the `X-API-Key` header on every request: ```http theme={null} X-API-Key: your-api-key-here ``` Requests without a valid API key are rejected with a `401 Unauthorized` response. API keys are created and managed in the Host Console under **Settings > Account > API Keys**. You can create as many keys as you need, assign a description to each one to identify its purpose, and delete keys that are no longer in use. Most domain operations also require a `subscriptionHandle` query parameter that identifies which MailChannels subscription the domain belongs to. You can retrieve your subscription handles from the `/subscriptions` endpoint. The API allows up to 100 requests per second per customer. Requests that exceed this limit receive a `503 Service Temporarily Unavailable` response. ## What you can do ### Manage domains * Provision or deprovision up to 1,000 domains in a single request. * Retrieve a list of all domains associated with your account or a specific API key. * Retrieve a single domain's current settings and admins. There is no separate endpoint for this. Call [List domains](/api-reference/domains/list-domains) (`GET /domains`) with the `domains` query parameter set to the domain name. Each domain in the response includes `settings` (abuse policy and spam header), `admins`, `aliases`, and `downstreamAddresses`. * [Update domain settings](/api-reference/domains/update-domain-settings), including spam handling policy, domain admins, and downstream addresses. * Create or remove domain aliases. * Associate or re-associate a domain with a specific API key. ### Manage safelists and blocklists The API supports list management at all three tiers of the MailChannels filtering hierarchy. See [Safelists and blocklists](/inbound/safelists-blocklists) for how these tiers interact. * **Account-level lists** ? Add, remove, or retrieve entries in the global safelist or blocklist that applies across all domains in your account. * **Domain-level lists** ? Add, remove, or retrieve entries in the safelist or blocklist for a specific domain. * **User-level lists** ? Add, remove, or retrieve entries in the safelist or blocklist for a specific mailbox user. You can also update user settings through the API. The API uses `safelist` and `blocklist` as the list type identifiers. The older terms `whitelist` and `blacklist` are deprecated. ### Generate Domain Console login links You can generate a time-limited login link that signs a user into the Domain Console as a domain administrator. This is useful for cPanel or WHMCS integrations where you want domain admins to access their logs and settings from within your control panel. The API supports generating login links for a single domain or in bulk for up to 1,000 domains at once. ### Report misclassifications Submit false positive or false negative reports directly through the API without requiring a manual support request. ## Using multiple API keys for security By default, any valid API key in your account can manage any domain in your account. For greater security, you can associate each domain with a specific API key. Once associated: * Only that key can list, edit, or deprovision the domain. * Other API keys cannot see or modify domains they are not associated with. * If a key is compromised, only the domains tied to that key are at risk. A domain can also be left unassociated, in which case any of your API keys can manage it. ### Example use case If you manage inbound filtering across several shared hosting servers, you can create one API key per server and associate each server's domains with its corresponding key. A compromise on one server exposes only that server's domains ? not your entire account. ### How key association works When provisioning a domain, you can pass the `associate-key` flag to link it with the API key making the request. You can also update a domain's key association at any time using the `PUT /domains/{domain}/api-key` endpoint. When you delete an API key, all its domain associations are removed and those domains revert to being manageable by any key. Domains provisioned through the WHM/cPanel plugin are not automatically associated with a key. Manage key associations for plugin-provisioned domains from the Host Console or via the API. Use caution when changing key associations ? domains can appear to disappear from the plugin's view if they are no longer associated with the key the plugin is configured to use. You can configure which API key the plugin uses from **API Configuration** in the WHM plugin settings. # Review and manage the MailChannels inbound quarantine Source: https://docs.mailchannels.com/inbound/quarantine Learn how to review, release, and report quarantined messages in MailChannels Inbound, and how the quarantine fits into your spam handling policy. When spam handling is set to **Quarantine Spam**, MailChannels holds suspected spam in a secure holding area instead of delivering it to your inbox or rejecting it outright. You can review held messages, release any that were incorrectly flagged, and block or safelist senders directly from the quarantine. ## How the quarantine works Every message that arrives at your domain is scanned by MailChannels. When a message is identified as spam and your domain's spam handling policy is set to quarantine, the message is stored by MailChannels rather than forwarded to your mail server. The message does not touch your inbox unless you explicitly release it. Quarantined messages are stored for up to **7 days**, after which they are automatically deleted. The quarantine is only active when your domain admin has selected **Quarantine Spam** in Domain Settings. If your domain is set to block or flag spam instead, messages will not appear in the quarantine. See [Configure spam handling](/inbound/domain-admins#configure-spam-handling) for instructions. ## Access your quarantine 1. Sign in to the [Domain Console](https://app.mailchannels.com). 2. Click **Logs** in the left navigation. 3. Select the **Quarantined** filter in the search bar and click **Search**. Logs page showing quarantined messages with the Quarantined filter applied Each row shows the message status, sender, subject, and time. Quarantined messages are tagged **QUARANTINED** with the reason ? for example, **Spam Content**. 4. Click the **?** arrow to the left of the subject line to expand the message details. The panel shows the sender, recipient, date, subject, and tags. 5. Click **Show Message** to render the full message content and verify whether it is legitimate or spam. Domain admins see logs for all users in the domain. Mailbox users see only their own logs. The interface is the same for both. ## Take action on a message Hover over any message row to reveal three action icons. Click the action you want to take: Quarantined message row showing the Release, Safelist, and Block action icons on hover ### Release a message Click the **Release** (?) icon to deliver a message to your inbox immediately. Use this when a legitimate message has been incorrectly quarantined. ### Safelist a sender Click the **Safelist** (?) icon to add the sender to your safelist. Future messages from them will be delivered without being quarantined. If you are signed in as a domain admin, the entry is added to the domain-level safelist. If you are signed in as a mailbox user, it is added to your personal safelist. ### Block a sender Click the **Block** (?) icon to add the sender to your blocklist. Future messages from them will be automatically rejected. If you are signed in as a domain admin, the entry is added to the domain-level blocklist. If you are signed in as a mailbox user, it is added to your personal blocklist. For instructions on managing your safelist and blocklist more broadly, see [Safelists and blocklists](/inbound/safelists-blocklists). ## Report a false positive If a legitimate message was quarantined, release it first to deliver it to your inbox, then report it to help improve filter accuracy. See [Report false positives and false negatives](/outbound/report-false-positives) for instructions. ## Quarantine digest When your domain's spam handling policy is set to **Quarantine Spam**, the quarantine digest is automatically active and cannot be turned off. Each mailbox user receives one digest email per day summarizing the messages currently held in their quarantine. The time the digest is sent is determined by the system. The digest includes messages addressed to the mailbox user as well as any alias recipients associated with their address. Messages can be reviewed and released directly from the digest without signing in to the Domain Console. To stop receiving digest emails, switch your spam handling policy to **Block Spam** or **Flag Spam** in [Domain Settings](/inbound/domain-admins#configure-spam-handling). # Restrict inbound connections to MailChannels Source: https://docs.mailchannels.com/inbound/restrict-inbound-connections Configure Exim ACLs on WHM/cPanel and DirectAdmin servers to reject direct SMTP connections and only accept inbound mail from MailChannels delivery IPs. When a domain's MX records point to MailChannels, all mail for that domain should arrive via MailChannels ? not directly from the open internet. Without additional configuration, your mail servers will still accept direct SMTP connections from anyone, allowing senders to bypass inbound filtering entirely. To enforce this, configure an ACL (Access Control List) on your mail server that rejects direct connections for any domain whose MX records point to MailChannels. The ACL checks whether the connecting host is a MailChannels delivery server, and temporarily rejects the connection if it is not ? prompting the sender to use the correct MX path. The steps below cover WHM/cPanel and DirectAdmin servers running Exim. ## Safelist MailChannels service IPs MailChannels delivers filtered inbound mail to your server from a fixed set of egress IP addresses. Your mail server and firewall must accept connections from these addresses. Add the following IP addresses to your mail server's allowlist: * `35.161.220.134` * `35.163.189.64` * `34.214.167.131` * `23.83.223.254` * `23.83.220.5` * `23.83.212.2` * `23.83.208.2` These IP addresses are used exclusively for inbound email filtering. Refer to your mail server's documentation for how to configure an IP-based allowlist. ## Restrict connections via Exim ACL ### WHM/cPanel servers **Step 1: Create protection scripts** SSH into your server and create the following two files: `/opt/mc-mx-protect`: ```bash theme={null} #!/bin/bash host -t MX $1 | sort -n -k1 | cut -d ' ' -f 7 | sed -e 's/\.$//' | xargs | sed -e 's/ /:/g' | tr -d '\n' ``` `/opt/mc-ptr-protect`: ```bash theme={null} #!/bin/bash host -t PTR $1 | cut -d ' ' -f5 | sed 's/\.$//g' | tr -d '\n' ``` Add execute permissions to both files: ```bash theme={null} chmod +x /opt/mc-mx-protect && chmod +x /opt/mc-ptr-protect ``` **Step 2: Update Exim configuration** 1. Log in to the WHM panel. 2. Open **Exim Configuration Editor ? Advanced Editor**. 3. Search for `custom_begin_recp_verify`. 4. Add the following snippet in the provided box: ``` ################## Start MailChannels verification ##################################### defer !condition = ${if match_domain{${run {/opt/mc-ptr-protect $sender_host_address}}}{*.mailchannels.net}} set acl_m_mx_records = ${run {/opt/mc-mx-protect $domain}} condition = ${if eq{$acl_m_mx_records}{mx1.mailchannels.net:mx2.mailchannels.net}} message = Please deliver mail to the address specified in the MX records for this domain. ################## End MailChannels verification ##################################### ``` 5. Save the configuration and restart Exim. ### DirectAdmin servers (v1.63.0+) **Step 1: Create protection scripts** SSH into your server and create the following two files: `/usr/local/directadmin/scripts/custom/mc-mx-protect`: ```bash theme={null} #!/bin/bash host -t MX $1 | sort -n -k1 | cut -d ' ' -f 7 | sed -e 's/\.$//' | xargs | sed -e 's/ /:/g' | tr -d '\n' ``` `/usr/local/directadmin/scripts/custom/mc-ptr-protect`: ```bash theme={null} #!/bin/bash host -t PTR $1 | cut -d ' ' -f5 | sed 's/\.$//g' | tr -d '\n' ``` Add execute permissions: ```bash theme={null} chmod +x /usr/local/directadmin/scripts/custom/mc-mx-protect && chmod +x /usr/local/directadmin/scripts/custom/mc-ptr-protect ``` **Step 2: Update Exim configuration** 1. Navigate to the custombuild directory: ```bash theme={null} cd /usr/local/directadmin/custombuild ``` 2. Ensure Exim is configured: ```bash theme={null} ./build update ./build set exim yes ./build exim ``` 3. Add the custom ACL to the Exim configuration: ```bash theme={null} vi /etc/exim.acl_check_rcpt.conf.custom ``` 4. Insert the following ACL rule: ``` ################## Start MailChannels verification ##################################### defer !condition = ${if match_domain{${run {/usr/local/directadmin/scripts/custom/mc-ptr-protect $sender_host_address}}}{*.mailchannels.net}} set acl_m_mx_records = ${run {/usr/local/directadmin/scripts/custom/mc-mx-protect $domain}} condition = ${if eq{$acl_m_mx_records}{mx1.mailchannels.net:mx2.mailchannels.net}} message = Please deliver mail to the address specified in the MX records for this domain. ################## End MailChannels verification ##################################### ``` 5. Rebuild the Exim configuration and restart: ```bash theme={null} ./build exim_conf service exim restart ``` ## What these ACL rules do Once applied, your mail server will: * **Accept** mail from hosts with a PTR record matching `*.mailchannels.net` or from localhost. * **Temporarily reject** direct delivery attempts that don't originate from MailChannels, directing senders to use the MX path instead. * **Allow direct delivery** for domains whose MX records do not point to MailChannels, since those domains are not routed through inbound filtering. # Safelists and blocklists in MailChannels Inbound Filtering Source: https://docs.mailchannels.com/inbound/safelists-blocklists Manage global, per-domain, and user-level safelists and blocklists to control which senders, domains, and IP addresses are allowed or blocked. MailChannels Inbound Filtering gives you three layers of control over which messages are allowed or blocked: global lists that hosting providers manage across all domains, per-domain lists that domain admins manage for individual domains, and user-level lists that mailbox users manage for their own accounts. Each layer can override the one above it ? user-level settings take the highest precedence, followed by per-domain, then global. Within any layer, safelists take precedence over blocklists. List precedence diagram showing three layers: User-level overrides Per-domain, which overrides Global. Within each layer, Safelist takes precedence over Blocklist. ## Global safelists and blocklists Global lists are managed from the Host Console and apply to every domain in your account ? including domains you add in the future. As soon as you add an entry to a global list, it takes effect immediately for all current and future domains. You can add any of the following entry types to a global safelist or blocklist: * **Email address** ? for example, `newsletter@example.com` * **Domain** ? for example, `example.com` * **IP address** ? for example, `192.0.2.1` MailChannels automatically detects the entry type when you add it, so you do not need to categorize it manually. You can filter the list by type or search for a specific entry using either an exact match or a partial match. ### Add a global safelist or blocklist entry 1. Sign in to the [Host Console](https://console.mailchannels.net). 2. From the left navigation, click **Inbound**, then click **Global Safelist** or **Global Blocklist**. 3. Click **Add New Safelist Items** (or **Add New Blocklist Items**). Global Blocklist page showing the Add New Blocklist Items panel The Safelist page looks the same. 4. Enter one or more email addresses, domains, or IP addresses in the text field, separated by commas. To add many entries at once, upload a CSV file with no column header instead. 5. Click **Add**. Entries appear in the list immediately and apply to all domains in your account. To remove an entry, click **Remove** next to it. Removing an entry from a global list ## Per-domain safelists and blocklists Domain admins and mailbox users both sign in to the [Domain Console](https://app.mailchannels.com) to manage their safelists and blocklists. Domain admins see domain-level entries and configure domain-level settings. Mailbox users see and manage only their own personal entries. The UI is the same for both ? user-level settings take precedence over domain and global settings. You can add any of the following entry types: * **Email address** ? for example, `newsletter@example.com` * **Domain** ? for example, `example.com` * **IP address** ? for example, `192.0.2.1` ### Add a per-domain safelist or blocklist entry 1. Sign in to the [Domain Console](https://app.mailchannels.com). 2. Click **Safelist** or **Blocklist** in the left navigation. Blocklist page in the Domain Console The Safelist page looks the same. 3. Type an email address, IP address, or domain in the **Add/Search** field. 4. Click **Add Policy**. The entry appears in the list immediately. To search existing entries, type in the **Add/Search** field and click **Search**. Use the **All** dropdown to filter by type. To remove an entry, click the **?** menu next to it and select **Delete**. Removing an entry from the blocklist in the Domain Console The Safelist removal works the same way. ## User-level safelists and blocklists Mailbox users manage their own personal safelists and blocklists from the [Domain Console](https://app.mailchannels.com). Entries apply only to your own mailbox and take precedence over domain and global settings. The steps to add and remove entries are the same as the [per-domain steps above](#add-a-per-domain-safelist-or-blocklist-entry) ? sign in to the Domain Console, click **Safelist** or **Blocklist** in the left navigation, and use **Add Policy** to add entries and the **?** menu to delete them. # MailChannels Inbound Filtering SMTP errors Source: https://docs.mailchannels.com/inbound/smtp-errors Diagnose SMTP error responses from MailChannels Inbound Filtering, including greylisting, blocked IPs, relay access denied, and policy mismatches. When MailChannels Inbound Filtering cannot process a message as expected, it returns an SMTP error response to the sending mail server. This page explains what each error means and what to do about it. If you believe a legitimate message was incorrectly rejected, see [Report misclassified email](/outbound/report-false-positives). This response indicates that the message has been greylisted. Greylisting is a spam-reduction technique where MailChannels temporarily defers mail from an unrecognized sender, requiring the sending server to retry delivery. Legitimate mail servers automatically retry after a short delay, and the message is delivered on future attempts. No action is required on the recipient's side. If a message never arrives after the retry period, the sender should check their mail server logs for delivery errors. This error means the connecting IP address has been blocked by MailChannels. The sending server cannot deliver mail until the block is resolved. The sender should contact [MailChannels support](mailto:support@mailchannels.com) to request a review of the block. This error means MailChannels was unable to resolve the sender's domain ? the DNS lookup returned an NXDOMAIN (domain not found) response. This can be a transient DNS issue, or in the case of newly created domains, the domain may not have fully propagated yet. The sender should wait and retry. If the problem persists, contact [MailChannels support](mailto:support@mailchannels.com). This error means the domain is not authorized to use MailChannels Inbound Filtering. MailChannels received a message for a domain that has not been provisioned on the service. If you are a hosting provider, make sure the domain has been added to your MailChannels account. If you are a domain admin, contact your hosting provider to confirm that inbound filtering has been enabled for your domain. This error means the message was identified as abuse ? spam, phishing, malware, or another policy violation ? and has been permanently rejected. The message will not be accepted or retried. This error means the message was rejected because it was identified as spam, phishing, malware, a virus, or another form of abuse. The message will not be accepted or retried. In some cases, this notification includes a link. If present, the sender can use it to release the message from quarantine directly. This error means the message was identified as spam and has been permanently rejected. The message will not be accepted or retried. If the sender believes their message was incorrectly rejected, they should contact [MailChannels support](mailto:support@mailchannels.com) for assistance. This error means the message contained a file attachment with a blocked extension. MailChannels rejects messages with certain high-risk attachment types to protect recipients from malware and phishing. The sender should remove the attachment or use a safer alternative format. See [Attachment restrictions](/inbound/blocked-attachments) for the list of blocked file types and guidance on alternatives. This error means the message exceeds the maximum allowed size. See [Attachment restrictions](/inbound/blocked-attachments) for the size limit and guidance on alternatives. This error means the message was rejected because the sending server is not authorized to send email on behalf of the sender's domain. SPF (Sender Policy Framework) is an email authentication standard that lets a domain publish a list of IP addresses permitted to send mail in its name. When an incoming message is sent from an IP address not listed in the domain's SPF record, MailChannels rejects it. This is a permanent rejection ? the message will not be retried. If you are the sender, check that your sending IP address is included in your domain's SPF record. For help diagnosing SPF failures, contact your DNS or email administrator. This error means the message was rejected because it failed the sender's domain DMARC policy. DMARC (Domain-based Message Authentication, Reporting & Conformance) allows a domain owner to instruct receiving mail servers to reject or quarantine messages that fail SPF or DKIM alignment checks. When the sending domain's DMARC policy is set to `reject`, MailChannels rejects non-compliant messages on the domain owner's behalf. This is a permanent rejection ? the message will not be retried. If you are the sender, check that your messages pass SPF or DKIM alignment for your domain. See [DMARC enforcement](/inbound/dmarc-enforcement) for more detail on how MailChannels applies DMARC policies. Each recipient on a domain can have their own spam handling policy ? for example, one recipient may quarantine spam while another rejects it outright. When a single message is sent to multiple recipients with different policies, MailChannels cannot apply two different actions to the same message in the same SMTP transaction. In this situation, MailChannels accepts the message for recipients whose policy matches the first recipient it processed, and returns a `452 4.5.3 Recipient policy mismatch` response for the remaining recipients. The `452` code is a temporary deferral, not a permanent rejection. Well-behaved mail-sending applications automatically retry deferred recipients in a separate SMTP transaction, so the message is ultimately delivered to all recipients ? possibly with a brief delay. This behavior is transparent to most senders. If a message has not been delivered after the retry period, try sending it to recipients one at a time. If the problem persists, contact [MailChannels support](mailto:support@mailchannels.com). # Manage users in MailChannels Inbound Filtering Source: https://docs.mailchannels.com/inbound/users-manager Use the Users Manager in the Domain Console to view mailbox users in your domain who receive email filtered by MailChannels Inbound Filtering. The Users Manager in the Domain Console gives domain admins a view of all users in their domain who are receiving filtered email. Use it to see which users are actively being protected and to manage filtering at the user level. Users Manager page in the Domain Console showing a list of users ## How the user list is populated The user list populates automatically as email is processed by MailChannels ? there is no need for users to create accounts or for domain admins to add them manually. When MailChannels receives a message for a new address at your domain, that address is added to the list. ## Do you need to create accounts for each user? No. Hosting providers and domain admins do not need to create Domain Console accounts for each recipient in advance. Once a domain is associated with MailChannels, all recipient addresses have their email automatically processed. Users are identified by their email delivery activity ? no manual setup is required. ## Protected recipients limit If your domain's plan includes a limit on the number of protected recipients, filtering is enabled on a first-come, first-served basis up to that limit. The first addresses to receive email through MailChannels are protected; addresses that arrive after the limit is reached are not filtered until capacity becomes available. ## Enable or disable filtering for a user Domain admins can toggle email filtering on or off for individual users directly from the Users Manager. This lets you control which users in your domain have their email filtered, without affecting other users. To enable or disable filtering for a user: 1. Sign in to the [Domain Console](https://app.mailchannels.com). 2. Click **Users Manager** in the left navigation. 3. Find the user in the list and use the toggle next to their name to enable or disable filtering. When filtering is disabled for a user, MailChannels delivers their email without scanning it for spam, phishing, or malware. # Account settings Source: https://docs.mailchannels.com/outbound/account-settings Manage your MailChannels Outbound Filtering account: SMTP passwords, console users, Google Sign-On (SSO), API keys, and Insights settings. The [MailChannels Host Console](https://console.mailchannels.net) is the central interface for managing your Outbound Filtering account. This page covers the settings available under **Settings > Account**. ## Create an account or log in To sign up, visit [mailchannels.com/pricing](https://www.mailchannels.com/pricing/) and choose Outbound Filtering or Inbound Filtering. You can also [request a free trial](https://mailchannels.com/contact-sales). To log in, go to [console.mailchannels.net](https://console.mailchannels.net). You can sign in with your email and password or use **Sign in with Google**. Host Console login The Host Console supports single sign-on (SSO) through Google Sign-On. To enable Google Sign-On for an existing account, log in as a Superuser and navigate to **Settings > Account > Google Sign-On Domains**. Click **Add Domain** and complete the form. Google Sign-On domain configuration ## SMTP Passwords Your account has a single SMTP username (Account ID). You can create multiple SMTP passwords — one per mail server or logical group — from **Settings > Account > SMTP Passwords**. SMTP Passwords settings ## Console Users Console Users are administrators with access to the Host Console. Each user has a name, login email, enabled/disabled state, and the following options: | Option | Description | | - | - | | **Super User** | Required to access the Accounts and Billing areas under Settings | | **Usage Alerts** | Receives billing volume and overage warning emails (not spam monitor alerts) | Console Users settings Starter and Lite plans limit the number of console users that can be created. Upgrade your plan to add more. **To add a new console user:** 1. Navigate to **Settings** > **Account** > **Console Users**. 2. Click **New User**. 3. Enter the user's name and email address, set a password, and configure their access permissions. 4. Click **Save**. Create new console user ## Google Sign-On Domains Lists the domains permitted to sign in to your Host Console using Google authentication (SSO). To sign domain administrators into the Domain Console from your own control panel without a password, generate a time-limited login link with the Inbound API. See [Create login link](/api-reference/login-links/create-login-link) and [Bulk create login links](/api-reference/login-links/bulk-create-login-links). Google Sign-On Domains ## API Keys API keys are used to group and secure inbound filtering domains. A domain can be associated with a single key or left unpaired. Keys created here are assigned to domains in **Inbound > Domains**. API Keys settings ## Insights Insights embeds a customized link in NDR bounce messages, giving your users a self-service way to report false positives without contacting support. The link leads to a page showing the reason for the rejection and a **Not Spam** button. You can enable or disable Insights from **Settings > Account > Insights**, and set how long Insights links remain active before expiring. Insights settings ## Insights Branding Customize the logo, favicon, and support text shown on Insights pages. Available on Standard plans and above. Insights Branding settings To upload custom branding, see [White-label Insights with your logo](/outbound/insights#white-label-insights-with-your-logo). # Introduction Source: https://docs.mailchannels.com/outbound/api-reference-introduction Search email delivery logs, retrieve sender profiles, query alerts, and request sender delisting with the MailChannels Log Search API. The Log Search API gives you programmatic access to [email delivery logs](/outbound/log-search), [sender profiles](/outbound/sender-profiles), and [alerts](/outbound/monitors-alerts). You can also request sender delisting through the remediation endpoint. Use it to investigate delivery issues, monitor sender activity, and build these capabilities into your own control panel or support tools. ## Base URL Use `https://api.mailchannels.net/outbound/v1` as the base URL for all API requests. All requests must use HTTPS. ## Authentication Include your Outbound Filtering API key in the `X-API-KEY` header with every request. ## Rate limits The API allows up to 100 queries per second per customer. Queries that exceed this limit may receive a `503 Service Temporarily Unavailable` response. ## Pagination `POST /search` returns 20 results by default. Use the `count` query parameter to request up to 100 results per page. Use `start_at` to set the offset, starting at 0. To fetch the next page, increase `start_at` by `count`. The API rejects requests with a `400 Bad Request` response if `start_at + count` exceeds 9,999. To retrieve more results, split your search into smaller date ranges using `start_date` and `end_date`, and paginate through each range. ## Time range By default, `POST /search` searches the last 3 days of data. To search a different date range, include both `start_date` and `end_date` in the request body, using the `YYYY-MM-DD` format. # Billing FAQ Source: https://docs.mailchannels.com/outbound/billing/billing-faq Answers to common billing and account questions: free trial, setup fees, multi-domain support, reseller program, support options, and how to contact the billing and sales teams. ## Can multiple domains be used with the same account? Yes. A single MailChannels Outbound Filtering account works across all your servers and supports any number of domains. ## Does MailChannels have a reseller or partner program? Yes. Hosting providers can deploy MailChannels Inbound Filtering on cPanel servers and resell it to customers. The WHMCS module that previously supported a Reseller API has been sunset. There are no current plans to update that integration. ## Does MailChannels offer phone support? Phone support is not available except for customers on a Premium Support plan. To get a timely response, [open a support ticket](https://support.mailchannels.com/hc/en-us/requests/new) and the 24/7 support team will respond promptly. ## Is there a free trial? Yes. MailChannels offers a 30-day free trial for Outbound Filtering. Contact the [sales team](https://www.mailchannels.com/contact-sales/) to request one. We also offer a 30-day satisfaction guarantee. If MailChannels does not meet your expectations within the first 30 days, contact [billing@mailchannels.com](mailto:billing@mailchannels.com) for a full refund. ## Is there a setup fee? No. There is no charge for setup or configuration assistance during onboarding. ## How do I cancel my account? To close your account, email [billing@mailchannels.com](mailto:billing@mailchannels.com). Include your Host Console username so the team can locate your account. ## Who do I contact for billing or sales help? | Topic | Contact | | - | - | | Plan changes, payments, invoices | [billing@mailchannels.com](mailto:billing@mailchannels.com) | | Volume pricing, term agreements, sales questions | [Contact sales](https://www.mailchannels.com/contact-sales/) | | Technical support | [Open a support ticket](https://support.mailchannels.com/hc/en-us/requests/new) | # Change your plan Source: https://docs.mailchannels.com/outbound/billing/change-plan Upgrade or downgrade your Outbound Filtering plan at any time from the Host Console. You can upgrade or downgrade your plan at any time from the [MailChannels Host Console](https://console.mailchannels.net). Log in to the [Host Console](https://console.mailchannels.net) and navigate to **Settings** > **Billing**. Scroll to the **Change Your Plan** section. Your current plan is shown on the left. Click **Select A Different Plan** to open the plan selector and browse available options. Selecting a plan from the list shows its details alongside your current plan for comparison. Once you've selected the plan you want, click **Switch To This Plan** to apply the change immediately. When you switch plans, any outstanding balance is charged immediately and your billing cycle resets to the date of the change. If you are downgrading mid-cycle, the unused portion of your current plan is credited toward your new plan. If you need help choosing a plan or want to discuss volume pricing, contact [billing@mailchannels.com](mailto:billing@mailchannels.com). # Download invoices and statements Source: https://docs.mailchannels.com/outbound/billing/invoices Download your current and recent MailChannels invoices from the Host Console, or contact billing for older statements. The Host Console lets you download your current invoice and the three most recent statements. Navigate to [console.mailchannels.net/billing](https://console.mailchannels.net/billing) and scroll down to the **Statement** section, approximately halfway down the page. Statement section in the Host Console Click the **Select Statement** dropdown and choose the statement you want to download. Select Statement dropdown Click the download button to the left of the dropdown to save the selected statement. ## Older statements If you need statements older than three months, email [billing@mailchannels.com](mailto:billing@mailchannels.com). Include your MailChannels Host Console username (shown in the upper right of the billing section) so the billing team can locate your account quickly. # Exceeding your monthly message limit Source: https://docs.mailchannels.com/outbound/billing/overage-policy What happens to your account when you go over your monthly message limit, and how overage billing works on Standard plans and above. What happens when you go over your monthly message limit depends on your plan. ## Starter and Lite plans On Starter and Lite plans, exceeding your monthly message limit triggers a graduated service degradation: 1. **First overage** — an email is sent to all console super users warning that the limit has been exceeded and that service may be suspended unless the plan is upgraded. 2. **Second billing run in overage** — MailChannels begins rejecting 20% of SMTP connections with a temporary failure error. Your mail server will retry these connections automatically, so no email is permanently lost. Retried messages are not counted toward your total. A second warning email is sent. 3. **Third billing run** — 40% of SMTP connections are rejected. 4. **Fourth billing run** — 80% of SMTP connections are rejected. 5. **Fifth billing run** — the account is temporarily suspended. A warning email is sent at each stage. ## Standard plan and above On Standard plans and higher, mail continues to flow after you exceed your monthly allotment. Additional messages beyond your plan limit are billed at the corresponding rate per thousand messages. To avoid unexpected overage charges, consider upgrading your plan before you reach your limit. See [Change your plan](/outbound/billing/change-plan) for instructions. # Update your payment method Source: https://docs.mailchannels.com/outbound/billing/update-payment Update your credit card or billing information in the MailChannels Host Console. Log in to the [Host Console](https://console.mailchannels.net) and navigate to **Settings** > **Billing**. Scroll down to the **Payment Information** section and click the **Update** link on the right side of the panel to open the payment details dialog. Update your card details or billing information and click **Save Changes**. If your payment is declined, MailChannels will contact you before suspending service. For billing-related assistance, email [billing@mailchannels.com](mailto:billing@mailchannels.com). # How MailChannels counts usage Source: https://docs.mailchannels.com/outbound/billing/usage-and-billing MailChannels bills based on the number of messages accepted by the Outbound Filtering service, including messages identified as spam. When your mail server relays email through MailChannels, each successfully authenticated SMTP connection is counted toward your usage — regardless of whether the message is ultimately delivered, blocked, or identified as spam. Filtering and blocking spam is part of the service. Preventing that spam from reaching recipients is exactly what Outbound Filtering does, and the cost of that filtering is reflected in your usage count. ## Why spam messages are counted Charging for every message your infrastructure submits — including spam — gives you a direct economic incentive to act quickly when abuse is detected on your platform. When a compromised account starts generating blocked traffic that appears on your invoice, you have every reason to investigate and shut it down. For a full explanation of the reasoning behind this policy, see [Why we charge for rejected emails](/outbound/billing/why-we-charge-for-rejected-emails). ## How MailChannels limits your exposure MailChannels has built-in protections to reduce the impact of sudden abuse spikes: * **Rate limiting** — if an account suddenly sends significantly more volume than usual, MailChannels throttles it automatically. * **Monitors & Alerts** — when unusual activity is detected, MailChannels notifies you via the [Monitors](/outbound/monitors-alerts) feature in the Host Console so you can act quickly. # Why we charge for rejected emails Source: https://docs.mailchannels.com/outbound/billing/why-we-charge-for-rejected-emails Every message submitted to Outbound Filtering is billed — including blocked and rejected messages. Here's the reasoning behind that policy. This article applies to Outbound Filtering accounts. For Email API accounts, see [How usage is counted](/outbound/billing/usage-and-billing). If you've looked at your MailChannels invoice and noticed charges for messages that were blocked, rate-limited, or rejected, you might be wondering whether that's a mistake. It isn't. Charging for every message you attempt to deliver through our service — regardless of whether it reaches the recipient's inbox — is a deliberate policy, and one we've maintained for many years. Why we charge for rejected emails ## The short answer We charge for rejected messages because doing so gives you a powerful economic incentive to clean up abuse on your platform before it spirals out of control. ## The problem we're solving together If you're a MailChannels customer, you're most likely a web hosting provider or a multi-tenant SaaS platform. That means you have hundreds or thousands of users sending email through your infrastructure, and you can't personally review every message. Some percentage of those users — often without your knowledge — will at some point start sending spam, phishing emails, or other forms of abuse. When that happens, MailChannels steps in. Our filtering engine identifies the problematic traffic and blocks it before it can damage your IP reputation or the reputation of the broader sending pool. This is the core of what we do, and it works. But blocking abuse is only half the battle. The other half — the half that actually matters for long-term deliverability — is making sure *you* take action against the abusive sender on your end. ## Why free rejections create the wrong incentive Imagine a world where we didn't charge for rejected messages. When one of your users starts blasting spam and we block it, there's no cost to you. The spam doesn't go out, your reputation is protected, and your bill stays the same. Problem solved, right? Not really. Because there's no cost, there's no urgency. The abusive account stays active on your platform. It keeps trying to send. We keep blocking. And over time, the volume of attempted abuse grows — not just from that one account, but from others like it, because your platform has become known as one that tolerates abusive signups without consequence. This is the classic problem in any shared-responsibility system: if one party bears all the cost of enforcement while the other bears none, enforcement gradually breaks down. Charging for spam creates an incentive for change ## How charging changes the equation When you're billed for every attempted message — including the ones we block — something different happens. You start paying attention. You set up a webhook to receive real-time alerts from the [Monitors](/outbound/monitors-alerts) service. You notice that a particular user or domain on your platform is generating a spike in blocked traffic. And because that spike is hitting your bottom line, you take action: you suspend the account, tighten your signup process, implement better verification, or reach out to the user to understand what's going on. **Billing for rejected messages closes the feedback loop.** It transforms our blocking signals from something you might glance at into something you act on. ## The counterintuitive outcome: you send less and pay less When you start actively responding to the signals MailChannels provides — shutting down abusive senders, tightening onboarding, monitoring your sending patterns — the total volume of email flowing through our service tends to go down. Not because your legitimate email decreases, but because the illegitimate email that was inflating your numbers gets eliminated at the source. The customers who lean into the feedback loop end up with cleaner platforms, better deliverability, and lower bills. The ones who ignore the signals tend to see their costs and abuse volumes grow in lockstep. ## We know it can feel unfair When you see charges for messages that never reached anyone's inbox — messages sent by an abusive user you didn't authorize — it can feel like you're paying for someone else's bad behavior. And in a narrow sense, you are. But consider the alternative. In email, reputation is everything. If we simply absorbed the cost of blocking abuse and asked nothing of you, we'd be enabling a dynamic where abuse on your platform has no consequences until it's too late — until your IPs are blacklisted, your legitimate customers can't send email, and the damage is already done. By then, the cost of remediation is far greater than the cost of a few blocked messages on an invoice. ## What you should do If you're seeing charges for rejected messages and want to bring those costs down: The [Monitors & Alerts](/outbound/monitors-alerts) feature surfaces the users and domains on your platform generating abuse. Check your alerts regularly — or integrate them into your operations workflow by configuring [webhooks](/outbound/webhooks) so you can automate responses to abuse signals. When Monitors flags a sender, investigate and take action. Suspend accounts that are clearly abusive. Reach out to users who might have a compromised account without realizing it. Many abuse problems start at signup. If you're seeing repeated patterns of abusive accounts, consider adding email verification, CAPTCHA, phone verification, or manual review for new signups. Reasonable per-user sending limits can contain the blast radius of a compromised or abusive account before it generates significant blocked traffic. ## A shared commitment to a cleaner email ecosystem MailChannels sees itself as your partner in maintaining email deliverability. Our filtering catches the bad traffic. Our Monitors tell you where it's coming from. And our pricing model ensures that you have every reason to act on that information. We charge for rejected messages not to penalize you, but because this model produces the best outcomes for everyone: for you, for your users, for recipients, and for the health of the email ecosystem as a whole. *— Ken Simpson, CEO, MailChannels* # File attachments blocked by MailChannels Source: https://docs.mailchannels.com/outbound/blocked-attachments Understand which file attachment types MailChannels blocks on inbound and outbound mail, why they are blocked, and how to share those files safely. To protect against malware, phishing, and other security risks, MailChannels blocks certain high-risk file attachment types on both inbound and outbound mail. These file types can contain executable code or system components that could harm recipients if opened. MailChannels' blocked attachment policy is consistent with the policies of major providers such as Gmail, Microsoft Outlook, Yahoo, and others. For reference, see [Gmail's blocked attachments policy](https://support.google.com/mail/answer/6590). ## Blocked file extensions The following attachment file extensions are blocked on both inbound and outbound mail: | | | | | | | - | - | - | - | - | | `ade` | `adp` | `apk` | `appx` | `appxbundle` | | `bat` | `cab` | `chm` | `cmd` | `com` | | `cpl` | `diagcab` | `diagcfg` | `diagpkg` | `dll` | | `dmg` | `ex` | `ex_` | `exe` | `hta` | | `img` | `ins` | `iso` | `isp` | `jar` | | `jnlp` | `js` | `jse` | `lib` | `lnk` | | `mde` | `mjs` | `msc` | `msi` | `msix` | | `msixbundle` | `msp` | `mst` | `nsh` | `pif` | | `ps1` | `scr` | `sct` | `shb` | `sys` | | `vb` | `vbe` | `vbs` | `vhd` | `vxd` | | `wsc` | `wsf` | `wsh` | `xll` | | ## Why these file types are blocked These extensions are associated with files that can run code or alter system behavior: | Category | Extensions | | - | - | | **Executables and installers** | `exe`, `msi`, `appx`, `apk`, `dmg`, `iso`, `cab` | | **Scripts and macros** | `js`, `vbs`, `ps1`, `jse`, `vb`, `wsf` | | **Shortcuts and control files** | `lnk`, `cpl`, `msc`, `pif` | | **Virtual disks and images** | `iso`, `img`, `vhd` | | **Java-related files** | `jar`, `jnlp` | | **Diagnostic and config packages** | `diagcab`, `diagcfg`, `diagpkg` | | **Libraries and system files** | `dll`, `sys`, `lib`, `xll`, `vxd` | These file types are frequently abused in attacks, may bypass simple scanning, and can execute code without clear user awareness. ## How blocking works When MailChannels detects a blocked file type as an attachment, the message is rejected during the SMTP transaction and the sender receives a bounce (NDR) containing an error such as: ``` 550 5.7.1 [FEB] Your message could not be delivered because it contained an attachment type that is not allowed. ``` Archives such as ZIP or RAR files may also be blocked if they contain one or more of the blocked file types. ## Safe alternatives Instead of emailing blocked file types, use one of the following approaches: * **File-sharing services** — use enterprise cloud storage, managed SFTP, or a secure portal to share files with intended recipients. * **Code and scripts** — share as text snippets via a developer platform, repository, or collaboration tool rather than attaching executables. When sharing via a link: * Restrict access to intended recipients only. * Set an expiration date on the shared link. * Enable download notifications where available. * Avoid public links unless necessary. ## Troubleshooting If your message was rejected due to a blocked attachment: * Do not attempt to bypass the block by renaming the file extension or placing it in a weakly-protected archive — archives may still be scanned and blocked. * Use an approved file-sharing method instead. * If you believe the file was blocked in error, contact support with the original file name, timestamp, sender and recipient addresses, and the full bounce message text. ## FAQ **Why was my attachment blocked even though it's legitimate?** The policy is based on file type risk, not individual intent. These formats are widely abused regardless of the content of any specific file. **Will zipping or encrypting the file help?** Not reliably. Archives may still be blocked if they contain blocked file types, and encrypted archives cannot be scanned. Use an approved file-sharing service instead. **Can an exception be made for my account?** No exceptions are made to the blocked attachment policy. # Configure your mail server for Outbound Filtering Source: https://docs.mailchannels.com/outbound/configure-mail-server Set up your MTA to relay outbound mail through MailChannels as a smart host, with connection details, SMTP AUTH, and required TLS encryption. To use MailChannels Outbound Filtering, you configure your mail transfer agent (MTA) to use MailChannels as a smart host—also called an SMTP relay. Instead of delivering email directly to recipients, your mail server hands every outbound message to MailChannels, which filters it, manages IP reputation, and delivers it on your behalf. `smtp.mailchannels.net` is a relay endpoint for MTAs such as Postfix, Exim, Exchange, and the mail servers behind cPanel or DirectAdmin. It is not intended as an outgoing SMTP server for desktop email clients like Thunderbird or Outlook. Point your MTA at MailChannels and continue submitting mail from clients to your own mail server. ## Before you begin You need: * A MailChannels Outbound Filtering account * Your SMTP username (same as "Account ID" in the Host Console under **Settings > Account > SMTP Passwords**) * An SMTP password (create one in the Host Console if you haven't already) * Access to your mail server's configuration TLS encryption is required for all connections from your mail server to MailChannels. Do not configure your MTA to use legacy SSL-mode encryption. ## Connection details Use these settings when configuring your MTA: | Setting | Value | | - | - | | Relay host | `smtp.mailchannels.net` | | Port | `25` (preferred), `587` (STARTTLS), or `2525` | | Encryption | TLS (STARTTLS) | | Authentication | SMTP AUTH with your MailChannels username and password | If port 25 is blocked in your network or by your hosting provider, use port **587** with STARTTLS or port **2525** as a fallback. ## Set up your mail server Log in to the [Host Console](https://console.mailchannels.net) and go to **Settings > Account > SMTP Passwords**. Your SMTP username (Account ID) is shown on this page. If you don't have a password yet, create one here. In your MTA configuration, set the relay or smart host to `smtp.mailchannels.net`. The exact setting name varies by MTA — refer to the guide for your specific mail server below. Configure your MTA to authenticate to `smtp.mailchannels.net` using the username and password from the Host Console. Use PLAIN or LOGIN SASL mechanisms over a TLS-encrypted connection. Set your MTA to require TLS when connecting to the MailChannels relay. Connections without TLS are rejected. Update your domain's SPF records to authorize MailChannels to send on your behalf. Without this step, receiving mail servers may reject your messages. See [Set up SPF records](/outbound/spf-records) for instructions. After your mail server is relaying through MailChannels, configure monitors in the Host Console so you're notified when a compromised sender is detected. See [Monitors and alerts](/outbound/monitors-alerts). Monitors are only available in Standard Plans and above ## Sender identification headers To help MailChannels track individual senders accurately, your MTA should add identification headers to each message. These headers let the system attribute email to a specific user, application, or script — which is essential for isolating compromised accounts without blocking everyone else. Add **X-AuthUser** header This X-AuthUser header often contains the authenticated sender address but can contain any unique identifier which you can use to locate the responsible sending entity. The MTA-specific configuration guides contain details on how to insert the X-AuthUser header. ## MTA-specific setup guides Choose the guide that matches your mail server: Configure Postfix relayhost and SASL authentication for MailChannels. Set up MailChannels as the smart host in WHM and cPanel environments. Advanced Exim configuration for custom routing and per-domain relay settings. Configure Exchange 2019 to send outbound mail through MailChannels. Configure Sendmail with SASL authentication and smart host routing. Route outbound mail through MailChannels and add X-AuthUser headers in Zimbra. Configure Exim on DirectAdmin servers to relay through MailChannels. Set up an outgoing gateway and authenticated sender headers in SmarterMail. For a full list of supported mail servers, see the **MTA Setup** section in the sidebar. If your MTA is not listed, look for "smart host" or "relay host" configuration options in your mail server's documentation, or contact [MailChannels support](mailto:support@mailchannels.com) for guidance. # Create an EML file Source: https://docs.mailchannels.com/outbound/create-eml-file How to export a raw email message as an EML file from Gmail, Outlook, or Mac Mail to submit with a false positive or false negative report. When reporting a false positive or false negative to MailChannels Support, you may be asked to provide the complete message as an EML file. This file contains the full headers and body of the message, which allows our analysts to quickly review the raw content and determine whether the classification was correct. When saving, choose **unformatted** or **raw** if the option is available. The EML file must be a plain-text representation of the message including all headers. ## Gmail In Gmail, open the message. Click the **⋮** menu (top right of the message) and select **Show original**. Gmail Show Original menu option On the Original Message page, click **Download Original** in the bottom-left corner. Save the file with a descriptive name and the `.eml` extension (e.g. `sender@example.com-2024-06-01.eml`). If the dialog appends `.txt`, change the file type to **All Files**. Attach the `.eml` file to your support ticket or email it to [support@mailchannels.com](mailto:support@mailchannels.com) with your ticket number. ## Outlook Open the message in Outlook. In the **Message** window, locate the **Tags** group and click the small anchor icon at the bottom-left to open **Message Properties**. In the **Internet Headers** section at the bottom of the dialog, select all text (Ctrl+A) and copy it (Ctrl+C). Close the dialog. Open a text editor, paste the headers, and save the file with the `.eml` extension. If the dialog appends `.txt`, change the file type to **All Files**. Attach the `.eml` file to your support ticket or email it to [support@mailchannels.com](mailto:support@mailchannels.com) with your ticket number. ## Mac Mail In Mac Mail, click the message to highlight it. Go to **File > Save As**. Set the **Format** to **Raw** and click **Save**. The file is saved to your desktop. Attach the `.eml` file to your support ticket or email it to [support@mailchannels.com](mailto:support@mailchannels.com) with your ticket number. # Outbound Filtering Dashboard Source: https://docs.mailchannels.com/outbound/dashboard Use the MailChannels Outbound Filtering Dashboard to monitor current usage, deliverability, and sending volume across your account. When you log in to the [MailChannels Host Console](https://console.mailchannels.net), you are presented with a Dashboard that gives you a summary of what's interesting about your data: * **Current Usage** * **Deliverability** * **Volume by IP Address, Sender ID, and Sender Domain** MailChannels Dashboard ## Current Usage You can keep track of your current email usage and how many days are left in your billing cycle so there are no surprises on your next billing statement. The top row of cards displays the following: * **Progress bar** — a quick visual representation of how fast your monthly budget is being spent against the message limits of your subscription plan. * Dark green represents email consumption for the current billing period. * Light green represents the projected consumption. * The entire progress bar represents 100% of your allotted emails per month. * **Billing Cycle** — your billing cycle start and end dates, and your progress into the billing period as a percentage. * **Current Usage** — the cost of your current usage and your progress through your subscription allowance as a percentage. * **Projected Usage** — the cost of your projected usage including overages, and your projected overage progress as a percentage. ## Deliverability The Deliverability card is second from the top of the Dashboard and includes three columns: | Column | Description | | - | - | | **You've sent** | The number of messages you have attempted to send through MailChannels. | | **We've queued** | The number of messages MailChannels has accepted from your network or users. Messages that are poorly addressed or from blocklisted senders are dropped at connection and do not appear here. | | **And we've delivered** | The number of delivery attempts made by MailChannels to deliver the queued messages. This number can exceed **You've sent** when receivers temporarily fail and MailChannels must retry. | The date range selector is at the top right of the Deliverability card. Select from the following predefined ranges: | Range | Data available | | - | - | | **Hour** | Last 60 minutes | | **Day** | Current calendar date up to the time of selection | | **Week** | Previous 7 calendar days | # Dictionary attack protection Source: https://docs.mailchannels.com/outbound/dictionary-attacks How MailChannels protects downstream mail servers from dictionary attacks using real-time SMTP multiplexing and behavioral rate limiting. A dictionary attack occurs when a sender — typically a spammer — connects to a mail server and issues a large number of `RCPT TO` commands to identify which email addresses are valid. This floods the server with validation requests that consume CPU and memory, often degrading or disabling the server for legitimate traffic. ## How traditional servers handle dictionary attacks Most mail servers defend against dictionary attacks by rate-limiting SMTP responses or disconnecting clients that exceed a threshold of invalid recipient checks. However, this approach has limits: each new connection from a banned client still consumes server resources just to issue the rejection. At high attack volumes, even rejecting connections becomes costly. ## How MailChannels protects downstream servers MailChannels places an SMTP proxy between senders on the internet and your downstream mail servers. This proxy uses asynchronous I/O — it can handle a large number of concurrent connections efficiently, without spawning a heavyweight process per connection. The proxy only relays SMTP commands to your downstream server when necessary. During a dictionary attack, the proxy can absorb and reject connection attempts without those attempts ever reaching your mail server. Your server is shielded from the flood. ## Behavioral rate limiting MailChannels tracks sender behavior in a time-domain database that records events — such as invalid recipient checks — with timestamps. The policy system queries this database to detect senders that are exceeding normal thresholds. When a sender breaches a configured limit, MailChannels rejects further connections from that sender — temporarily or permanently — without involving your downstream server. Because the proxy handles the rejection, no TCP connection attempt reaches your mail server, conserving its resources entirely. This approach stops dictionary attacks at the perimeter, before they can affect the systems behind MailChannels. # DKIM failure when Message-ID is added after signing Source: https://docs.mailchannels.com/outbound/dkim-failure-message-id Why a message can fail DKIM authentication when your server oversigns the Message-ID header and one is added downstream, and how to fix it. If your mail server DKIM-signs messages but a receiving server reports `dkim=fail` even though your configuration hasn't changed, the cause may be header oversigning: your server declared `Message-ID` as a signed header while it was absent, and MailChannels inserted one afterward — invalidating the signature. ## What causes this DKIM signatures include an `h=` tag that lists every header covered by the signature. Some mail servers — Exim in particular — include `Message-ID` in this list by default, even when the header is not present in the message at signing time. This practice is called **oversigning**: by declaring a header as "signed" while it is absent, the server ensures that any later attempt to inject that header will invalidate the signature, which protects against header injection attacks. The problem arises when a message genuinely has no `Message-ID` at the time of signing, and one is legitimately added downstream. Because `Message-ID` was declared in the `h=` tag, inserting it after signing invalidates the signature. Here is the sequence that triggers the failure: 1. Your application sends a message to your mail server **without a `Message-ID` header**. 2. Your mail server DKIM-signs the message. The signature's `h=` tag includes `Message-ID` — oversigned, even though the header isn't there yet. 3. Your server relays the signed message to MailChannels. 4. MailChannels detects the missing `Message-ID` and inserts one. 5. The receiving server validates the DKIM signature. Because `Message-ID` is now present but wasn't there when the signature was computed, the signature fails. Adding a `Message-ID` to a message that has none is normal message-handling behavior, not tampering. Although `Message-ID` is syntactically optional under [RFC 5322](https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.4), the RFC says every message SHOULD have one. In practice, major receivers expect it: [Google's Email Sender Guidelines](https://support.google.com/a/answer/81126) state "Make sure every message includes a valid Message-ID," and [Yahoo's sender requirements](https://senders.yahooinc.com/best-practices/) require senders to comply with RFC 5321 and RFC 5322. Messages without a valid `Message-ID` are more likely to be rejected or treated as suspicious by receiving mail servers. Nothing was tampered with. The failure is a side effect of oversigning a header that was absent at the time of signing. ## Will this affect email delivery? A DKIM failure is not harmless, even when DMARC still passes through SPF alignment. DKIM is one of the authentication signals receivers use when deciding whether to accept, filter, or classify a message. A failed DKIM signature does not automatically require rejection — [RFC 6376](https://datatracker.ietf.org/doc/html/rfc6376) notes that verifying MTAs may apply local policy to unverifiable mail — but receivers may still treat it as a negative signal. It also means the message is no longer authenticated through that DKIM signature, which matters if SPF is unavailable, unaligned, or breaks in the future. A failed DKIM signature is also worth fixing even if DMARC currently passes through SPF alignment. It leaves delivery dependent on SPF alignment and removes DKIM as a reliable authentication path. If SPF is missing, fails, or is not aligned, and no other aligned DKIM signature passes, this DKIM failure can cause DMARC to fail as well. Under `p=quarantine` or `p=reject`, receivers that consider the domain's DMARC policy may be more likely to filter, quarantine, or reject the message as part of their local policy. ## How to fix it ### Option 1: Generate Message-ID before signing (recommended) Configure your sending application or mail server to always include a `Message-ID` header before the message reaches the DKIM signing step. Once `Message-ID` is present at signing time, it is included in the signed content and no downstream insertion is needed. Most modern mail clients and application mail libraries generate `Message-ID` automatically. Check whether your application's mail library has an option to generate the header, and ensure it is enabled. If you are sending through your own SMTP stack, add `Message-ID` generation before the message is handed to the MTA. The important detail is ordering: generate `Message-ID` before DKIM signing, not after it. ### Option 2: Stop signing the absence of Message-ID If you cannot control `Message-ID` generation at the application layer, adjust your DKIM signing configuration so the signer does not sign the absence of `Message-ID`. For Exim, check the `dkim_sign_headers` option in the transport block that handles MailChannels delivery (for example, `mailchannels_smtp`). Exim's header list has three modes per entry: a bare name oversigns by default when the header is absent, a `+` prefix explicitly oversigns, and an `=` prefix signs only the header instances that are actually present — so a header added later, like a downstream `Message-ID` insertion, no longer breaks the signature. See the [Exim DKIM documentation](https://www.exim.org/exim-html-current/doc/html/spec_html/ch-dkim_spf_srs_and_dmarc.html) for the full `dkim_sign_headers` option reference. If your config lists `Message-ID` with no prefix or a `+` prefix, switching it to `=Message-ID` is the fix. If your Exim version does not support prefixes, remove `Message-ID` from the list entirely. See [DKIM configuration for cPanel](/outbound/mta/cpanel-dkim) for how to edit the Exim transport configuration. Using `=Message-ID` is the better compromise: it still signs an existing `Message-ID`, so changes to that signed header will break the signature, but it does not sign the absence of `Message-ID`. Removing `Message-ID` from the list entirely means the header is unprotected by that signature. ## Confirm this is the cause Check the following to verify this specific issue before making changes: 1. **Was `Message-ID` missing from the original message?** Look at the raw message as sent from your server (before MailChannels received it). If no `Message-ID` header was present, that confirms one required condition for this failure. 2. **Does the DKIM signature sign the absence of `Message-ID`?** Inspect the `DKIM-Signature` header in the delivered message. If `Message-ID` was missing before signing and `message-id` appears in the `h=` tag, the server signed the absence of that header. 3. **Does the `Message-ID` in the delivered message differ from your application's normal format?** If the value looks different from what your application normally generates, or uses infrastructure-specific formatting, it may have been inserted downstream. Confirm by checking outbound logs or the raw message before it reached MailChannels. 4. **Do SPF and DMARC pass alongside `dkim=fail`?** If `spf=pass` and `dmarc=pass` appear alongside `dkim=fail`, the message may have been delivered because SPF alignment, or another aligned DKIM signature, satisfied DMARC. This pattern is consistent with the issue, but the decisive evidence is still: `Message-ID` was missing before signing, `message-id` appears in `h=`, and `Message-ID` is present after relay. 5. **Is this a header failure rather than a body hash failure?** This issue affects the signed header set, not the message body. If the receiver reports a body hash mismatch, look for body modification after signing instead. # Prevent domain spoofing with MailChannels Domain Lockdown Source: https://docs.mailchannels.com/outbound/domain-lockdown Publish a DNS TXT record to restrict which MailChannels accounts and sender IDs are authorized to send email from your domain through Outbound Filtering. When multiple customers share a MailChannels Outbound Filtering account, any sender authenticated to the service could potentially submit mail that claims to be from your domain. Standard SPF records protect your domain on the open internet, but they do not distinguish between different customers within a shared MailChannels deployment. **Domain Lockdown** closes this gap by letting you declare — in DNS — exactly which MailChannels account IDs and sender IDs are permitted to send from your domain. Any other account that attempts to send from your domain is rejected with an error. ## How Domain Lockdown works You publish a DNS TXT record at `_mailchannels.yourdomain.com`. MailChannels checks this record when processing outbound messages. If the sending account or sender ID is not listed in the record, the message is rejected. If no record exists, MailChannels applies no additional restriction beyond standard authentication. ## Lockdown identifiers Domain Lockdown supports two identifier types for Outbound Filtering: | Identifier | Description | Example | | - | - | - | | `auth` | Identifies a MailChannels customer (hosting provider) by their authentication username | `myhostingcompany` | | `senderid` | Identifies a specific sender entity — such as an authenticated webmail user or a PHP script — by specifying the provider, identity type, and identity in one string | `myhostingcompany\|x-authuser\|joe@example.com` | The `cfid` identifier for Cloudflare Workers is deprecated and will be discontinued. If you are using Outbound Filtering via SMTP relay, use `auth` and `senderid` only. ## DNS TXT record syntax Create a TXT record at `_mailchannels.yourdomain.com` using the following format: ```text theme={null} v=mc1 auth= senderid= ``` You can specify any number of `auth` and `senderid` fields in a single record. Each field must carry exactly one value. ### Examples **Allow a single hosting provider to send from your domain:** ```text theme={null} v=mc1 auth=myhostingcompany ``` **Allow two different providers:** ```text theme={null} v=mc1 auth=myhostingcompany auth=anotherprovider ``` **Lock down to a specific sender ID:** ```text theme={null} v=mc1 senderid=myhostingcompany|x-authuser|joe@example.com ``` **Allow a provider and a specific sender simultaneously:** ```text theme={null} v=mc1 auth=myhostingcompany senderid=myhostingcompany|x-authuser|joe@example.com ``` **Block MailChannels from sending any email from your domain entirely:** ```text theme={null} v=mc1 ``` Publishing a record with only the version string and no `auth` or `senderid` fields instructs MailChannels to reject all messages from your domain, regardless of the sending account. ## Find your auth and sender ID Your MailChannels account ID (the value used in `auth=`) is shown in the panel footer when you log in to the [Host Console](https://console.mailchannels.net). Sub-account IDs are listed on the sub-accounts page. You can also read both identifiers from the headers of any message that was successfully sent through MailChannels from the domain you want to lock down: * `X-MailChannels-Auth-Id` — carries the `auth` value (your hosting provider's account ID) * `X-MailChannels-Sender-Id` — carries the `senderid` value (the specific sender entity) ### Example message headers ```text theme={null} X-MailChannels-Sender-Id: goodhost|x-authuser|joe@example.com X-MailChannels-Auth-Id: goodhost Content-Type: text/plain; charset="utf-8" Date: Mon, 22 Aug 2022 14:15:57 -0500 From: joe@example.com To: shoshanna@example.net Subject: Your pineapples have shipped ``` In this example, you would use `auth=goodhost` and/or `senderid=goodhost|x-authuser|joe@example.com` in your `_mailchannels` TXT record. ## Test your lockdown record MailChannels provides a Python testing script that reads your DNS TXT record and simulates whether a given sender would be accepted or rejected. To use it: 1. Install the Python DNS library: ```bash theme={null} pip install dnspython==2.3.0 ``` 2. Download the script from the [GitHub Gist](https://gist.github.com/ttulttul/8950d390218e2ee281b91dce72e787fd). 3. Run the script against your domain or a test record string. **Test with a hypothetical record string:** ```bash theme={null} python domain-lockdown-explainer.py --text "v=mc1 auth=example" --test auth=example ``` ```text output theme={null} MailChannels Domain Lockdown Record Summary: Version: mc1 Authorized Authenticators: - example Interpretation: This record authorizes emails from: - Senders authenticated by the specified authentication mechanisms Simulation Result: Message would be accepted Reason: Accepted: Auth ID matched. ``` **Test against a real DNS record:** ```bash theme={null} python domain-lockdown-explainer.py --domain yourdomain.com --test auth=myhostingcompany ``` The script fetches the TXT record from `_mailchannels.yourdomain.com` and simulates whether the specified `auth` or `senderid` value would be accepted. **Test a sender ID:** Sender IDs contain the `|` (pipe) character, which most shells treat as a pipe operator. Wrap the entire `--test` argument in quotes so the shell passes it to the script unchanged: ```bash theme={null} python domain-lockdown-explainer.py --domain yourdomain.com --test "senderid=myhostingcompany|x-authuser|joe@example.com" ``` Without the quotes, `zsh` or `bash` splits the value at each `|` and reports errors such as `command not found: x-authuser` before the script runs. After publishing your lockdown record, test it thoroughly before relying on it in production. A misconfigured record can cause legitimate outbound mail to be rejected. Use the testing script to validate your record against every `auth` and `senderid` value that should be permitted. If you manage email for multiple domains, create a `_mailchannels` TXT record for each domain separately. The record at `_mailchannels.example.com` applies only to `example.com` and does not affect `example.net` or any other domain. # GDPR compliance Source: https://docs.mailchannels.com/outbound/gdpr-compliance How MailChannels handles personal data and how signing a Data Processing Addendum (DPA) can strengthen your GDPR compliance program. MailChannels processes email on behalf of customers worldwide, including customers who send messages to or on behalf of individuals in the European Union. This page summarizes how MailChannels approaches GDPR and how to obtain the documents you need to satisfy your own compliance obligations. ## Is MailChannels GDPR compliant? MailChannels complies with the GDPR requirements that apply to its processing activities. MailChannels also complies with applicable Canadian privacy laws, including the British Columbia Personal Information Protection Act (PIPA) and the Personal Information Protection and Electronic Documents Act (PIPEDA). MailChannels acts as both a **data controller** (for account, billing, and support data it collects directly) and a **data processor** (for personal data contained in messages that customers submit for delivery). The obligations that apply to a given piece of data depend on which role MailChannels is performing at the time. ## Data handling * MailChannels does **not** store copies of delivered email messages. Messages are queued briefly during delivery (seconds to at most a few hours) and are not retained after delivery completes. * Email delivery **logs are retained for 35 days**. Older delivery records are not available. * Personal data collected to operate customer accounts is handled as described in the [Privacy Policy](https://www.mailchannels.com/privacy-policy/). * MailChannels transfers data internationally between its data centers and cloud regions. For personal data covered by the DPA, it describes safeguards for these transfers, including the European Commission's 2021 Standard Contractual Clauses (SCCs) when an adequacy decision does not apply. For details on message metadata that is available for investigating a specific delivery, see [Law enforcement requests](/outbound/law-enforcement). ## Contractual commitments MailChannels' contractual commitments related to GDPR are in the following documents: * [Privacy Policy](https://www.mailchannels.com/privacy-policy/) * [Terms of Service](https://www.mailchannels.com/terms-of-service/) * Data Processing Addendum (DPA), available on request (see below) ## Strengthen your GDPR compliance with a DPA Signing the MailChannels DPA can strengthen your GDPR compliance program when MailChannels processes personal data on your behalf. The DPA documents: * The controller and processor relationship between you and MailChannels * The nature, purpose, and duration of processing * Technical and organizational security measures * Security incident notification and assistance with data subject requests * Requirements for subprocessors and advance notice of changes * Deletion or return of personal data * Safeguards for international transfers, including SCCs for EEA and Swiss transfers when required Signing the DPA does not make your organization GDPR compliant on its own. You remain responsible for your obligations as a controller or processor, including establishing a lawful basis for processing and providing documented instructions to MailChannels. ## Request a signed DPA If your use of MailChannels involves processing personal data subject to GDPR, email [privacy@mailchannels.com](mailto:privacy@mailchannels.com) to request a DPA for e-signature. A DPA can only be executed on top of an existing agreement with MailChannels, such as acceptance of the [Terms of Service](https://www.mailchannels.com/terms-of-service/). Allow up to two business days for the privacy team to respond. ## GDPR contact For any other GDPR or privacy question, including questions about international data transfers, subprocessors, or MailChannels' role as controller or processor, email [privacy@mailchannels.com](mailto:privacy@mailchannels.com). # MailChannels Insights: email delivery reports for users Source: https://docs.mailchannels.com/outbound/insights Give end users a personalized delivery report page linked from bounce emails, so they can self-serve delivery issues without contacting your support team. MailChannels Insights is a self-service delivery reporting feature for the end users on your network. When a message is not delivered, Insights gives that user a personalized page showing what MailChannels knows about the bounced message — the rejection reason, metadata from the message, and a view into all of their recent sending history. Users can diagnose most common delivery problems themselves, which reduces the volume of support tickets your team receives. ## What users see on an Insights page An Insights page is tailored to the specific user and the specific bounce. It shows: * **Why the message was not delivered** — the rejection reason is displayed prominently at the top of the page, making it easy for users to understand what went wrong without any technical background. * **Message metadata** — headers and other details from the bounced message are displayed in a table so users can confirm they are looking at the correct message. * **Full sending history** — the Insights Log Search page gives users a personalized view of all messages they have sent through MailChannels, including delivery status for each one. Users can search this history to find specific messages or narrow down a pattern of failures. ## How Insights links appear in bounce emails When a message is not delivered, MailChannels generates a bounce (non-delivery report) email and sends it back to the sender. If you have enabled Insights for your account, the bounce message contains a **Get Insights** button or link. Clicking it takes the user directly to their personalized Insights page without requiring them to create an account or log in. To enable Insights links in bounce messages: 1. Log in to the [MailChannels Host Console](https://console.mailchannels.net). 2. Go to **Settings > Account**. 3. Find the **MailChannels Insights** configuration panel and enable the option to include Insights links in bounce messages. Once this setting is enabled, every bounce message generated by MailChannels for your users will include an Insights link automatically. You do not need to take any action on a per-user or per-message basis. ## Enroll a user manually You can also invite a specific user to Insights directly from the Host Console without waiting for a bounce event: Navigate to **Outbound > Log Search** in the Host Console. Search for a message sent by the user you want to invite. Click the **envelope icon** in the top-right corner of the log entry, or click the **Invite to MailChannels Insights** link in the expanded view of the entry. MailChannels sends the user an email containing a link to their personalized Insights log search page. Use manual enrollment proactively when a user contacts support about a delivery issue. Sending them an Insights invitation lets them verify the problem and monitor their sending going forward, reducing the chance they contact support again for the same issue. ## White-label Insights with your logo Insights branding is available on Standard plans and above. You can replace the default MailChannels branding on Insights pages with your own logo, favicon, and a custom support message, so the experience feels native to your hosting platform. ### Image requirements | Element | Format | Max dimensions | Max file size | | - | - | - | - | | Logo | PNG | 50 × 410 px | 10 MB | | Favicon | PNG | — | 10 MB | Insights branding example ### Upload your branding Log in to the [Host Console](https://console.mailchannels.net) as an administrator and navigate to **Settings > Account > Insights Branding**. Insights Branding settings panel Under **Logo**, click **Browse** and select your PNG logo file. Under **Favicon**, click **Browse** and select your PNG favicon file. Both fields are required. Under **Support Text**, enter a plain-text message to display on Insights pages — for example, contact details or a support URL. Leave blank to show no text. Click **Upload** to apply your branding. The Insights URL continues to point to `console.mailchannels.net` regardless of branding. Sub-accounts have separate branding from their parent — log in as a sub-account console user to update it. Do not use the MailChannels logo in your custom branding. ## How Insights reduces support burden Without Insights, a user who receives a bounce message typically contacts your support team to ask what went wrong. Your team must then look up the message in Log Search, interpret the rejection reason, and relay that information back to the user. Insights short-circuits that loop by putting the same information directly in the hands of the user the moment they receive the bounce. Users who can self-diagnose and see their full sending history are less likely to generate follow-up tickets. # Invalid recipient blocks Source: https://docs.mailchannels.com/outbound/invalid-recipients Understand why MailChannels blocks accounts with high invalid-recipient rates and how to resolve an IRR block. If you receive a bounce with the code `[IRR]`, your account was temporarily blocked because MailChannels detected an unusually high rate of messages addressed to invalid recipients — addresses that don't exist at the receiving mail server. ## Common causes * **Mailing list hygiene** — the list contains old or mistyped addresses that have never been cleaned. * **Compromised account** — a spammer has taken over the account and is sending dictionary-attack spam to randomly generated addresses. ## How to resolve an IRR block Check whether you recently sent to a mailing list, or whether the account may have been compromised. If you did not initiate the sending, treat the account as compromised. Change the account password immediately and run a full antivirus and malware scan. Contact your hosting provider if you need help securing the account. Go through your bounce messages and remove all invalid recipient addresses from the list. Once the invalid-recipient rate returns to normal, the temporary block will lift automatically. IRR blocks are temporary. The system monitors your invalid-recipient rate and removes the block automatically once the rate drops to acceptable levels. No manual intervention is required once the root cause is addressed. # Law enforcement requests Source: https://docs.mailchannels.com/outbound/law-enforcement How law enforcement officials can investigate emails sent through MailChannels, and how to submit a legal request for information. MailChannels is a general-purpose email delivery service used by hundreds of companies across 82 countries, processing email for millions of end users across over 600 web hosting networks. ## What MailChannels does and does not store * MailChannels does **not** store copies of email messages. Messages are queued briefly (seconds to up to 6 hours) during delivery and are not retained after delivery completes. * MailChannels does **not** have information about the identity of individual end users who send email through the service. * Email delivery logs are retained for **35 days**. We have no information about deliveries that occurred more than 35 days ago. * MailChannels adheres to privacy laws and regulations in the jurisdictions where it operates. ## Investigating email headers To determine the origin of an email sent through MailChannels, examine the following headers in the message: ### X-MailChannels-SenderId Identifies the MailChannels customer and the end user who sent the email. * Format: `customername|identifier-type|enduser-identifier` * Example: `customername|x-authuser|enduser123` This indicates that `enduser123` sent the email as a user of `customername`. ### Received Shows the IP addresses of machines from which the email originated. ```text theme={null} Received: from [192.0.2.1] (unknown [203.0.113.40]) by smtp.mailchannels.net (Postfix) with ESMTPA id 1234ABCD for ; Wed, 11 Sep 2024 12:34:56 +0000 (UTC) ``` In this example, `smtp.mailchannels.net` received the email from `192.0.2.1`. To investigate further, contact the owner of that IP address. Combining the IP address owner from the `Received` headers with the customer name from `X-MailChannels-SenderId` identifies the company with a direct relationship to the end user responsible for the message. ## Submitting a legal request If header analysis does not provide sufficient information, law enforcement officials should contact MailChannels directly: 1. Email [legal@mailchannels.com](mailto:legal@mailchannels.com) 2. Include the **full headers** of any messages under investigation — without headers, MailChannels cannot assist 3. Provide details of the investigation and the specific information you are seeking MailChannels will assist with investigations to the best of its ability while respecting privacy obligations to customers and end users under applicable law. For questions about data protection, GDPR, or to request a Data Processing Addendum (DPA), see [GDPR compliance](/outbound/gdpr-compliance). # Search email delivery logs in MailChannels Outbound Source: https://docs.mailchannels.com/outbound/log-search Search months of email delivery logs by sender, recipient, or SMTP code, and use the Log Search API to pull delivery data into your own dashboards. Log Search lets you look up any message that passed through MailChannels Outbound Filtering and find out exactly what happened to it. You can search by sender, recipient, subject, delivery status, SMTP response code, and more. Results appear in reverse chronological order, with the most recent messages at the top, and each entry shows the message status, sender, subject, and the time MailChannels processed it. Providers can also access the same data programmatically through the **Log Search API**, which supports filtering, pagination, and time-interval aggregation — making it straightforward to build delivery dashboards inside your own control panel or support tooling. ## Search from the web interface To open Log Search, log in to the [MailChannels Host Console](https://console.mailchannels.net) and navigate to **Outbound > Log Search**. ### Basic search Type any term in the search box at the top of the page. MailChannels searches all indexed fields — including the from address, recipient addresses, and subject line — for that term. For example, entering `joe@example.com` returns all messages where that address appears in any field. ### Advanced search You can narrow results by searching a specific field using the syntax `field:value`. Enclose multi-word values in double quotes. Combine multiple field searches with a space (logical AND) or the `AND` / `OR` keywords (which must be uppercase). ```text example queries theme={null} sender:joe@example.com subject:"account suspended" sender:hotmail AND rcpt:example sender_id:testuser AND spam_disposition:bulk AND delivered:N sender:joe@example.com AND rcpt_bad:y ``` ### Searchable fields | Field | Description | Example | | - | - | - | | `date` | Date the message was processed (YYYY-MM-DD) | `date:2024-08-15` | | `delivered` | Whether the message was delivered (`Y` or `N`) | `delivered:Y` | | `lastcmd` | Last SMTP command received (`CONN`, `MAIL`, `RCPT`, `DATA`) | `lastcmd:DATA` | | `rcpt` | Recipient address | `rcpt:bob@example.com` | | `rcpt_bad` | Message addressed to a non-existent recipient (`y` or `n`) | `rcpt_bad:y` | | `sender` | Envelope sender address | `sender:joe@example.com` | | `sender_id` | Sender ID as carried in the `X-AuthUser` header | `sender_id:testuser` | | `smtp_code` | SMTP response code returned by the receiving server | `smtp_code:550` | | `spam_disposition` | Spam classification (`bulk`, `spam`, etc.) | `spam_disposition:bulk` | | `subject` | Message subject line | `subject:"invoice due"` | | `to_date` | Upper date bound — no entries newer than this date are returned | `to_date:2024-09-01` | ### Escaping control characters Certain characters must be escaped with a backslash when used inside a search value: | Character | Escaped form | | - | - | | `&` | `\&` | | `"` | `\"` | | `:` | `\:` | For example: `subject:"This is important\: foo"`. ## Sender Profiles From any Log Search result, you can open a **Sender Profile** for a specific sender by clicking the Sender Profile icon to the left of the sender ID. A Sender Profile shows: * Every message that sender has submitted * Any alert notifications generated for that sender * A graphical ham-to-spam ratio with current counts Sender Profiles are also accessible from the **Top Senders** view, giving you a consistent place to investigate any individual sender's behavior regardless of how you discover them. ## Log Search API The Log Search API provides programmatic access to the same delivery data available in the web interface. Providers typically use it to embed delivery diagnostics directly into their hosting control panels or support ticketing systems. With the Log Search API you can: * **Search logs by any criteria** — recipient address, SMTP response code, subject, sender, spam disposition, and more. * **Identify delivered and undelivered messages** to pinpoint deliverability problems. * **Detect spam and virus dispositions** to audit the compliance of your users' sending. * **Aggregate results by time interval** — days, hours, or weeks — to identify volume trends and detect when an abuse campaign started. * **Paginate through large result sets** for systematic analysis. The API returns JSON responses and is designed to integrate with any language or framework. Full endpoint and schema documentation is available in the [Log Search API reference](/outbound/api-reference-introduction). ### Sender Profile API The Log Search API is complemented by the **Sender Profile API**, which exposes the per-sender behavioral data shown in the Host Console. Use it to: * Search and filter transaction logs for a specific sender * Retrieve detailed sender behavior information to build a sender profile * Retrieve alert notification history for a sender Together, the Log Search API and Sender Profile API let you build hourly, daily, and weekly deliverability dashboards inside your own tooling, using the same data your support team would review in the Host Console. If your support team frequently looks up delivery status for specific customers, embedding Log Search API results in your control panel can dramatically reduce the time spent switching between systems. Contact the MailChannels sales team to enable API access for your account. Once enabled, refer to the [Log Search API reference](/outbound/api-reference-introduction) for authentication details and endpoint reference. # Prepare customer DNS for an Outbound Filtering migration Source: https://docs.mailchannels.com/outbound/migration-center/prepare-customer-dns Audit customer domains in bulk, group them by nameserver and SPF status, and identify which DNS changes you can make and which need customer action. Before you move customer email to MailChannels, check which sending domains authorize MailChannels in SPF. Group the results by authoritative nameserver so you can update DNS you manage and contact customers who manage DNS elsewhere. For Outbound Filtering, the SPF authorization is `include:relay.mailchannels.net`. The audit below reads DNS and creates a CSV report. It does not change DNS records. ## Decide which domains need changes Prioritize domains with `-all` that do not already authorize MailChannels. The `-` qualifier means **hard fail**: a receiving server can reject messages from an unauthorized sending IP. A hyphen inside a domain name, such as `include:spf-example.net`, does not indicate strict enforcement. For a valid policy without a hard-fail mechanism, updating SPF is **optional for this migration**, but recommended to explicitly authorize MailChannels. This includes `~all` (soft fail), `?all` (neutral), and no SPF record. These results are distinct from SPF pass. See the [SPF result definitions](https://www.rfc-editor.org/rfc/rfc7208.html#section-2.6). Optional does not guarantee delivery. If a domain relies on SPF for DMARC, it needs an SPF pass aligned with the visible From domain, or a passing, aligned DKIM signature. A soft-fail or neutral SPF result does not satisfy DMARC. Check [DMARC authentication and alignment](https://www.rfc-editor.org/rfc/rfc7489.html#section-3.1) before deferring an update. The report uses these statuses: | SPF status | Meaning | Your next step | | - | - | - | | `required` | The policy uses `-all` and has no direct MailChannels include or other include to inspect. | Authorize MailChannels before moving this domain's email. Verify any existing IP-based authorization before deciding an edit is unnecessary. | | `optional` | No SPF record, or a policy without hard-fail mechanisms and without a direct MailChannels include. | Recommend adding MailChannels; check DMARC before deferring. | | `include_present` | A positive `include:relay.mailchannels.net` appears before any `all` mechanism. | Validate the complete policy and test delivery; the include alone is not proof of SPF pass. | | `review` | DNS failed, multiple SPF records exist, or the policy needs further inspection. | Resolve the issue before deciding whether an update is required or optional. | The script is an inventory tool, not a full SPF evaluator. It does not expand includes, evaluate sending IP addresses, or count recursive DNS lookups. A strict policy with another provider's include goes to `review`: that include might already authorize MailChannels. Policies with `redirect=`, macros, unusual qualifiers, or unrecognized syntax also go to `review`. An `all` or `+all` policy authorizes every sender and needs review. ## Prepare your domain and nameserver lists Use a macOS, Linux, or Windows WSL terminal with Python 3.10 or later, `pip`, and virtual environment support. The commands below use Bash-compatible syntax and [dnspython](https://dnspython.readthedocs.io/en/stable/). Your machine must be able to query DNS through its configured resolver. Create `domains.txt` with one sending domain per line. Replace these examples with your customer domains: ```bash theme={null} cat > domains.txt <<'EOF' example.com example.net mail.example.org EOF ``` Include the domains used in the SMTP envelope sender, usually shown as the Return-Path on delivered messages. They can differ from the visible From address. Audit sending subdomains separately: SPF does not inherit from the parent domain. You can also include your SMTP HELO domain for messages with an empty envelope sender. If you forward customer mail, include the envelope domains your MTA uses for SRS rewriting. Those domains must authorize MailChannels for the forwarded hop. See [Configure SRS for forwarded mail](/outbound/migration-center/test-and-roll-out#configure-srs-for-forwarded-mail). Create `provider-nameservers.txt` with the exact names of **all nameservers for zones you manage**: ```bash theme={null} cat > provider-nameservers.txt <<'EOF' ns1.hosting-provider.example ns2.hosting-provider.example EOF ``` The script compares whole names, ignoring case and trailing dots. It labels a domain `provider` only when every nameserver is in your list. Partial matches become `review`; no matches become `customer`. Leave this file empty if you only want grouping without identifying your nameservers. Nameservers identify DNS hosting, not account ownership. Confirm that you can edit each zone before treating it as provider-managed. Shared services such as Cloudflare can host both your accounts and your customers' accounts on the same nameservers. ## Run the bulk audit Create an isolated Python environment and install the DNS library: ```bash theme={null} python3 -m venv .spf-audit-venv . .spf-audit-venv/bin/activate python3 -m pip install 'dnspython>=2.6,<3' ``` Copy this entire block into your terminal to create `spf-audit.py`: ```bash theme={null} cat > spf-audit.py <<'PY' import csv import ipaddress from pathlib import Path import re import sys import dns.exception import dns.name import dns.resolver def read_names(path): names = set() for line in Path(path).read_text(encoding="utf-8-sig").splitlines(): name = line.split("#", 1)[0].strip().rstrip(".") if not name: continue name = name.encode("idna").decode("ascii").lower() if len(name) > 253 or any( not re.fullmatch(r"[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?", label) for label in name.split(".") ): raise ValueError(f"Invalid domain in {path}: {name}") names.add(name) return sorted(names) def classify(records): if not records: return "optional", "No SPF record" if len(records) != 1: return "review", "Multiple SPF records" if any(c.isspace() and c != " " for c in records[0]): return "review", "Invalid SPF whitespace" terms = records[0].lower().split()[1:] # Recognize common literal syntax; send complex policies for review. host = r"[a-z0-9_](?:[a-z0-9_.-]*[a-z0-9])?\.?" for term in terms: body = term[1:] if term[:1] in "+-~?" else term if body.startswith(("ip4:", "ip6:")): try: network = ipaddress.ip_network(body[4:], strict=False) if network.version != int(body[2]): raise ValueError("IP version mismatch") except ValueError: return "review", "Invalid IP mechanism" elif not re.fullmatch(rf"(?:all|a|mx|include:{host})", body): return "review", "Redirect, macro, or syntax requiring review" all_terms = [t for t in terms if t.lstrip("+-~?") == "all"] if len(all_terms) > 1: return "review", "Multiple all mechanisms" end = terms.index(all_terms[0]) if all_terms else len(terms) active = terms[:end] if any(t.startswith(("-", "~", "?")) for t in active): return "review", "Qualified mechanism needs evaluation" if all_terms and all_terms[0] in ("all", "+all"): return "review", "Policy authorizes every sender" includes = [t.lstrip("+").rstrip(".") for t in active] if "include:relay.mailchannels.net" in includes: return "include_present", "Direct include found; validate full SPF" if all_terms == ["-all"]: if any(t.startswith("include:") for t in includes): return "review", "Strict policy; check indirect authorization" return "required", "Strict policy without MailChannels include" return "optional", "No hard-fail mechanism; adding MailChannels recommended" def audit(domain, managed, resolver): row = dict(domain=domain, dns_zone="", nameservers="", dns_owner="review", spf_status="review", spf_record="", notes="") notes = [] try: zone = dns.resolver.zone_for_name(domain + ".", resolver=resolver, lifetime=10) if zone == dns.name.root: raise ValueError("No customer DNS zone found") row["dns_zone"] = zone.to_text().rstrip(".") answer = resolver.resolve(zone, "NS", lifetime=10, search=False) nameservers = {r.target.to_text().lower().rstrip(".") for r in answer} row["nameservers"] = ";".join(sorted(nameservers)) if nameservers and nameservers <= managed: row["dns_owner"] = "provider" elif nameservers and not nameservers & managed: row["dns_owner"] = "customer" else: notes.append("Mixed or missing nameservers; confirm zone access") except (dns.exception.DNSException, ValueError) as error: reason = str(error) if isinstance(error, ValueError) else type(error).__name__ notes.append("NS lookup: " + reason) try: # NoAnswer means the name exists but has no TXT records. # NXDOMAIN, SERVFAIL, and timeouts must remain review items. answer = resolver.resolve(domain + ".", "TXT", lifetime=10, search=False, raise_on_no_answer=False) records = [b"".join(r.strings).decode("ascii") for r in answer] spf = [r for r in records if re.match(r"^v=spf1(?:\s|$)", r, re.I)] row["spf_record"] = " | ".join(spf) if answer.canonical_name != dns.name.from_text(domain + "."): notes.append("TXT follows a DNS alias; review the target zone") else: row["spf_status"], reason = classify(spf) notes.append(reason) except (dns.exception.DNSException, UnicodeError) as error: notes.append("TXT lookup: " + type(error).__name__) row["notes"] = "; ".join(notes) return row def main(): if len(sys.argv) != 3: raise SystemExit("Usage: python3 spf-audit.py domains.txt provider-nameservers.txt") domains = read_names(sys.argv[1]) managed = set(read_names(sys.argv[2])) resolver = dns.resolver.Resolver() resolver.cache = dns.resolver.Cache() rows = [] for index, domain in enumerate(domains, 1): print(f"[{index}/{len(domains)}] {domain}", file=sys.stderr) rows.append(audit(domain, managed, resolver)) rows.sort(key=lambda r: (r["nameservers"], r["spf_status"], r["domain"])) fields = ["nameservers", "spf_status", "dns_owner", "domain", "dns_zone", "spf_record", "notes"] with open("spf-audit.csv", "w", newline="", encoding="utf-8") as output: writer = csv.DictWriter(output, fieldnames=fields) writer.writeheader() writer.writerows(rows) print(f"Wrote {len(rows)} domains to spf-audit.csv", file=sys.stderr) if __name__ == "__main__": main() PY ``` Run the audit: ```bash theme={null} python3 spf-audit.py domains.txt provider-nameservers.txt ``` Open `spf-audit.csv` in a spreadsheet. The report sorts by the full nameserver set, then SPF status, then domain. It deduplicates input domains, joins split TXT strings, and finds the containing DNS zone for sending subdomains. Each DNS operation has a 10-second timeout; large lists or repeated DNS failures can take several minutes. Re-running the audit replaces the report. Example rows, with the record and notes columns omitted: | nameservers | spf\_status | dns\_owner | domain | dns\_zone | | - | - | - | - | - | | ns1.customer-dns.example;ns2.customer-dns.example | required | customer | example.net | example.net | | ns1.hosting-provider.example;ns2.hosting-provider.example | optional | provider | mail.example.org | example.org | | ns1.hosting-provider.example;ns2.hosting-provider.example | required | provider | example.com | example.com | ## Apply changes by DNS owner ### Domains you manage Filter for `dns_owner=provider` and prioritize `spf_status=required`. Resolve `review` items before cutover. In your authoritative DNS system, edit each domain's existing SPF TXT record to add `include:relay.mailchannels.net` before `all`. Keep existing senders and the existing `all` qualifier. ```text Before theme={null} v=spf1 ip4:192.0.2.10 -all ``` ```text After theme={null} v=spf1 ip4:192.0.2.10 include:relay.mailchannels.net -all ``` Publish only one SPF record at each name. Keep the full policy within SPF's [10 DNS-querying-term limit](https://www.rfc-editor.org/rfc/rfc7208.html#section-4.6.4), including nested includes. Follow [SPF setup](/outbound/spf-records) and configure [Domain Lockdown™](/outbound/domain-lockdown) for your MailChannels account. If customers already include an SPF record you control, inspect that shared record. Adding MailChannels there may cover those customers without individual DNS edits. Verify the include chain, mechanism order, and lookup budget before relying on this approach. ### Domains managed by customers Filter for `dns_owner=customer`. Confirm who controls DNS, then send required changes before moving that domain's mail. Give customers the exact record name and their complete proposed SPF value, preserving existing senders. Adapt this message for each customer: ```text theme={null} We are moving your outbound email to MailChannels. Your sending domain: [domain] Your DNS nameservers: [nameservers] SPF update: [required before migration / optional but recommended] In your DNS provider's control panel, edit the existing TXT record: Record name: [sending domain or the provider's equivalent host field] Current value: [current SPF record] New value: [complete SPF record with include:relay.mailchannels.net before all] Keep one SPF record at this name. Do not remove other sending services. If you have no SPF record, create the TXT record with the value we supply. Also configure the Domain Lockdown TXT record we provide: Record name: _mailchannels.[domain] Value: v=mc1 auth=[our MailChannels account ID] If a Domain Lockdown record already exists, contact us to confirm the combined value so existing authorized accounts remain permitted. Please confirm when you have saved these changes so we can verify DNS and test delivery before moving your email. ``` For `optional` domains, explain that the existing policy does not impose an SPF hard fail, but adding MailChannels improves explicit authorization. Confirm aligned DKIM works if the customer will rely on it for DMARC. ## Verify before moving traffic Allow the previous DNS TTL to expire, then run the audit again. Check `review` rows and confirm expected includes appear. For indirect authorization, validate the referenced policy separately because this audit will still flag it for review. Send real messages from a domain you control, using a workload with little impact if delivery fails, such as noncritical cron reports or internal notifications. Repeated messages such as "Testing 1 2 3" are likely to be classified as spam and blocked. Inspect the receiving server's `Authentication-Results` header for SPF and DMARC results. Test domains in each DNS and SPF group before expanding the rollout. Continue with [mail server configuration](/outbound/configure-mail-server) and use [Log Search](/outbound/log-search) to investigate delivery issues. For a complete test plan, pilot stages, and rollback criteria, see [Testing and phased rollout](/outbound/migration-center/test-and-roll-out). # Test and roll out an Outbound Filtering migration Source: https://docs.mailchannels.com/outbound/migration-center/test-and-roll-out Build a representative test environment, check common migration failures, and move customer email to MailChannels in phases with clear rollback criteria. Use a dedicated test environment to verify your customer sending paths, then move production traffic in small groups. Start with domains you control, follow with a representative customer pilot, and expand when delivery and operational checks pass. A successful SMTP connection is only the first check. Your tests should also establish that messages authenticate correctly, remain attributable to the right sender, reach recipients, and produce useful evidence when delivery fails. ## Build a representative test environment Match the parts of your customer setup that affect mail delivery. Keep an inventory of differences between the test environment and production so you can cover them during the pilot. | Area | What to mirror | | - | - | | Mail servers | MTA and control panel versions, MailChannels plugin or manual configuration, routing rules, queue settings, and per-customer sending limits. | | Sending sources | Authenticated SMTP, webmail, website forms, PHP or local mail submission, scheduled jobs, and any other path your customers use. | | Sender identity | The mapping from each authenticated user or local application to its sender ID, including how you distinguish identical usernames on different servers. | | DNS and authentication | Provider-managed and customer-managed DNS, sending subdomains, strict SPF policies, indirect SPF includes, DKIM signing, and DMARC policies. | | Network | DNS resolution, firewall rules, outbound SMTP ports, NAT, and TLS settings. Repeat connectivity checks from each production network before its migration. | | Message types | Representative transactional messages, plain text and HTML, attachments, non-ASCII content, forwarding, and delivery failure notifications. | ### Use real mail with a low cost of failure Test with real email messages from a domain you control, using a workload where a delayed or missing message has little impact. Good starting points include routine cron reports from a server you operate or noncritical internal notifications from a company subdomain. Choose recipients on your team who can tolerate failures, so real customers are not affected. Include controlled mailboxes at the recipient providers your customers commonly use. Avoid repeatedly sending messages with a simple subject or body such as "Testing 1 2 3". Sending many messages like this is likely to trigger spam classification and blocking. Use the workload's normal subjects, useful message content, and natural sending cadence. Do not clone a live outgoing queue or enable copied scheduled jobs that still address real customers. ### Arrange a test account For larger customers that need a test account during onboarding, MailChannels provisions a special **Outbound Filtering sub-account**. This feature is specific to Outbound Filtering and is separate from the Email API sub-account feature. MailChannels provides these test accounts free of charge to qualifying customers above a certain scale. Contact [MailChannels support](https://support.mailchannels.com/hc/en-us/requests/new) with your expected sending volume and migration plan to confirm eligibility and arrange provisioning. Use the test credentials and account identifiers supplied by MailChannels. Keep distinct test sender identities; creating a separate SMTP password alone does not provide account or reputation isolation. Test-account messages are still subject to normal filtering. Agree on expected volume and any load or abuse-simulation testing with support before running those tests. Configure the test MTA using the relevant [mail server setup guide](/outbound/configure-mail-server). Send through the same client-to-MTA path your customers use. This exercises local routing, signing, and sender identification as well as the connection to MailChannels. ## Prepare DNS and a delivery baseline Run the [customer SPF audit](/outbound/migration-center/prepare-customer-dns) before selecting a pilot group. Resolve required changes and manual-review items for each group before moving its traffic. If a customer defers an optional SPF update, verify that their authentication plan still meets their DMARC requirements. Configure [Domain Lockdown™](/outbound/domain-lockdown) for the account and sender identities used in each environment. A record that authorizes your production account can reject a separate test account. Use the intended values on your test domains, then verify production values during the pilot. Capture a baseline from your current delivery route over a period that includes normal busy hours and scheduled sending. Record: * Submission volume and delivery outcomes by customer group and recipient provider. * Queue depth, oldest queued message, and typical delivery delay. * Temporary deferrals, permanent rejections, and authentication failures. * Sender attribution, abuse reports, and delivery-related support tickets. Define acceptable deviations and who can pause the rollout before you start. Use rates as well as counts, and compare similar traffic periods. A test domain with little sending history cannot reproduce every production reputation or recipient-filtering decision. ## Configure SRS for forwarded mail Customers may host `www.example.com` with you while forwarding mail addressed to `info@example.com` to `customer@gmail.com`. Configure **Sender Rewriting Scheme (SRS)** on the forwarding MTA before relaying this traffic through MailChannels. This applies to every hosting setup that forwards mail, whether or not you use cPanel. SRS rewrites the SMTP envelope sender (`MAIL FROM`) to an address under a domain your forwarding service controls. SPF checks that domain against the sending IP. Without SRS, forwarding normally retains the original sender's envelope domain, which may not authorize MailChannels. The final recipient's domain does not determine which SPF record is checked. For example, suppose `alice@gmail.com` sends to the hosted address `info@example.com`, which forwards to `customer@gmail.com`: | Forwarding stage | Envelope sender (`MAIL FROM`) | Recipient (`RCPT TO`) | | - | - | - | | Original message arrives | `alice@gmail.com` | `info@example.com` | | Forwarded without SRS | `alice@gmail.com` | `customer@gmail.com` | | Forwarded with SRS | `SRS0=...@example.com` | `customer@gmail.com` | The SRS address above is illustrative; your MTA generates the encoded local part. Without rewriting, SPF checks `gmail.com`, which does not authorize MailChannels to send its mail. With SRS, SPF checks `example.com`, which you can authorize with `include:relay.mailchannels.net`. Your setup can use a dedicated forwarding subdomain instead; publish and audit SPF at that exact name. For cPanel, follow the [SRS forwarding configuration](/outbound/mta/setup-cpanel). For other platforms, configure your MTA's supported SRS implementation, including decoding return addresses so delivery failures can reach the original sender. The [Exim SRS documentation](https://www.exim.org/exim-html-current/doc/html/spec_html/ch-dkim_spf_srs_and_dmarc.html) describes this mechanism for Exim. SRS leaves the visible From address unchanged. An SPF pass for the rewritten domain does not by itself provide DMARC alignment with the original sender. Preserve the original DKIM signature and test DMARC at the final recipient. Before moving a group that uses forwarding, verify the rewritten `MAIL FROM` in your MTA logs, SPF results at the destination, and bounce handling back through the forwarding service. Include the SRS domain in your [DNS audit](/outbound/migration-center/prepare-customer-dns). ## Run the test matrix Record the result for each sending source and MTA configuration you plan to migrate. Keep the real message's subject and content, and use its Message-ID and local queue ID to correlate results. Save the UTC timestamp, sender ID, SMTP response, and received headers where available. | Test | What to do | Passing result | | - | - | - | | Relay connection | Submit from your test MTA using the configured relay hostname, port, credentials, and TLS settings. | The MTA authenticates over TLS and the logs show the intended MailChannels route. | | Customer sending paths | Send from webmail, authenticated SMTP, local scripts, and scheduled jobs. | Every intended path uses the relay; local delivery and excluded routes still behave as intended. | | Sender attribution | Send from at least two distinct test users and a local application. | [Log Search](/outbound/log-search) and [Sender Profiles](/outbound/sender-profiles) distinguish the senders. Originated mail has the expected `X-AuthUser` identity, rather than a shared placeholder. | | Email authentication | Inspect the receiving server's `Authentication-Results` and the message's envelope sender and visible From address. | SPF, DKIM, and DMARC match your intended configuration. Required alignment passes, and no unexplained DKIM failure remains. | | Domain Lockdown | Send with the authorized test identity. On a domain you control, test an unauthorized identity if your test arrangement permits it. | Authorized traffic succeeds and the unauthorized case is rejected. | | Content and recipients | Send real cron reports or noncritical internal notifications, including the attachments those workloads normally produce, to your controlled recipient mailboxes. | Messages arrive intact; inbox or junk placement is recorded separately from SMTP acceptance. | | Forwarding and bounces | Exercise aliases, external forwarding with SRS, and delivery failure notifications used by your customers. | The rewritten envelope domain authorizes MailChannels in SPF. Routing completes without loops, original DKIM signatures remain valid, and failure notifications reach the original sender. | | Failure recovery | Use a controlled SMTP test server to return temporary and permanent failures. Separately interrupt the test MTA's relay connection and restore it. | The MTA handles retries and failures correctly, retains deferred mail, and recovers without an uncontrolled queue flush. | | Operational response | Trace a failed test message and rehearse the support or abuse response. | Your team can identify the sender, explain the outcome, and reach the correct escalation path. | For failure recovery, verify that temporary `4xx` replies are deferred and retried, while permanent `5xx` replies are surfaced as failures rather than retried indefinitely. These reply classes are defined in the [SMTP specification](https://www.rfc-editor.org/rfc/rfc5321.html#section-4.2.1). Use a controlled test server for repeatable error cases; avoid generating large numbers of invalid recipients or spam messages through the live relay. Check both your MTA logs and MailChannels [Log Search](/outbound/log-search). A final SMTP acceptance response transfers responsibility to the accepting server; it does not prove inbox placement. Confirm arrival and received headers in your test mailboxes. See [SMTP mail transactions](https://www.rfc-editor.org/rfc/rfc5321.html#section-3.3). Set up [monitors and alerts](/outbound/monitors-alerts) if your plan includes them. Verify that the responsible team receives notifications and can act on them. Otherwise, schedule log and queue reviews during each migration window. ## Watch for common migration pitfalls | Pitfall | How to avoid it | | - | - | | Testing only one message from one mailbox | Cover each sending source, MTA variant, DNS arrangement, and major recipient provider. A website script can use a different transport from webmail. | | Moving mail before DNS is ready | Confirm authoritative records and allow cached records to expire. If you lower a TTL, do it at least the previous TTL before the change. Keep existing authorized senders while both routes are in use. | | Treating optional SPF changes as guaranteed delivery | A policy without hard fail still may not produce SPF pass. Follow the [SPF audit guidance](/outbound/migration-center/prepare-customer-dns) and verify DMARC alignment before deferring changes. | | Publishing duplicate SPF records or exceeding lookup limits | Edit the existing SPF record, put the MailChannels include before `all`, and validate the complete include chain. See [SPF setup](/outbound/spf-records). | | Missing or inconsistent sender IDs | Verify attribution for local scripts as well as authenticated users. Preserve stable identities when you move a customer between servers. | | Losing DKIM signing on the new transport | Verify signing on the relay path. Generate `Message-ID` before signing and investigate any [DKIM failure caused by a missing Message-ID](/outbound/dkim-failure-message-id). | | Overlooking forwarded mail | Configure and test [SRS on every forwarding MTA](#configure-srs-for-forwarded-mail), including non-cPanel setups. Verify SPF for the rewritten envelope domain and test bounce handling. | | Combining plugin and manual routing rules | Choose the supported configuration method for your platform. Check for duplicate routers, unexpected exclusions, and bypassed customer sending limits. | | Assuming every rejection is a migration defect | Inspect the SMTP response and sender history. Follow [false-positive reporting](/outbound/report-false-positives) or [sender remediation](/outbound/unblock-senders) as appropriate. | | Repeating messages such as "Testing 1 2 3" | Repetitive test messages are likely to be classified as spam and blocked. Use real mail from a domain you control, with a natural cadence and little impact if delivery fails. Agree on load-test parameters with support. | ## Roll out in phases Choose groups you can route and roll back independently, such as a server pool or a set of sending domains if your MTA supports that selection. Confirm whether a rule matches the sender domain or the recipient domain before relying on it to select customers. Keep each customer's route stable during an observation window so you can interpret the results. The following sequence is a starting point. Adjust group sizes to your traffic volume, customer mix, and support capacity. | Phase | Scope | Advance when | | - | - | - | | 1. Dedicated test environment | Domains you control sending real mail from workloads such as noncritical cron reports. | The applicable test matrix passes, logs are available, and you have rehearsed rollback. | | 2. Internal production pilot | A company subdomain or small set of internal accounts sending noncritical notifications, with a higher tolerance for failure. | Real application traffic meets the acceptance criteria through a normal sending cycle, including busy hours and scheduled jobs, without exposing customers to test failures. | | 3. Customer pilot | A small, agreed group covering different sending sources and DNS owners. | Customer DNS is verified, attribution and authentication are correct, and support has resolved unexpected delivery issues. | | 4. Staged expansion | Progressively larger groups, adding new configurations deliberately. | Each group remains within its agreed queue, delivery, and error thresholds before you add the next. | | 5. Completion | Remaining customers whose readiness checks pass. | Queues and support volume are stable, outstanding exceptions have owners, and the previous route can be retired safely. | For a daily sending pattern, an initial observation window of 24–48 hours can be useful. Extend it to cover weekly jobs, peak periods, low-volume customers, or unresolved issues. This is a planning example, not a MailChannels activation or warm-up requirement. Advance based on evidence, not elapsed time alone. Before each phase, confirm the customer list, DNS readiness, routing change, operator, observation window, and rollback decision-maker. Tell affected customers when the change will happen and how to report a problem. Keep customers with unresolved prerequisites on their existing route. ### Decide whether to advance or pause Review these checks for each group, rather than only for the account as a whole: * All intended sending paths use the correct route and retain usable sender attribution. * Authentication results match the agreed configuration, including aligned DKIM where needed. * Queue age and depth return to their expected range after normal bursts. * Deferral, rejection, and delivery-delay rates stay within the limits you set against the baseline. * Controlled recipient tests arrive as expected, and unexpected customer reports have been investigated. * Support can trace a message and the rollback route remains available. Pause expansion for unexplained authentication failures, missing sender identities, persistent queue growth, routing loops, duplicate delivery, or a material rise in legitimate-mail failures. Investigate a change concentrated at one recipient provider even if aggregate delivery looks healthy. ## Prepare and rehearse rollback Save the previous MTA routing configuration and confirm that the previous delivery service remains usable during the rollout. Retain the DNS authorization and credentials needed for both paths until you have completed the observation period and accounted for outstanding mail. If a group needs rollback: 1. Stop adding customers and record the affected group, change time, and symptoms. 2. Determine which messages remain in your local queue and which MailChannels has already accepted. Preserve logs and queue state. 3. Restore the previous route for that group. Confirm its authentication and connectivity before releasing locally queued mail at a controlled rate. 4. Track mail already accepted by MailChannels through Log Search. Avoid manually resubmitting accepted messages through the old route, which can cause duplicates. Investigate ambiguous timeout outcomes before replaying messages. 5. Repeat the delivery checks on the restored route and notify affected customers of the outcome. Keep abuse controls active during rollback. A spam or sender-block rejection should trigger investigation and remediation, rather than automatic delivery through an unfiltered fallback. After the migration is stable, retire the previous route only when its queues are clear and it is no longer needed for rollback. Remove obsolete DNS authorizations without removing other services your customers still use. ## Coordinate with MailChannels support For a migration review, share your MTA and control panel versions, sending-source inventory, expected daily and peak volume, test-account arrangement, DNS audit results, and proposed rollout windows. Include your acceptance and rollback criteria so support can review the plan in context. When reporting a test failure, provide the UTC timestamp, Message-ID, sender and recipient, sender ID, full SMTP response, relevant headers, and affected server or group. Use the [false-positive reporting guide](/outbound/report-false-positives) when a legitimate message is blocked. Exclude SMTP passwords and other credentials from diagnostic material. # Set up monitors and alerts in MailChannels Outbound Source: https://docs.mailchannels.com/outbound/monitors-alerts Configure threshold-based monitors to get email or webhook alerts when MailChannels detects spam, blocklist events, or compromised senders on your network. Monitors are threshold-based alert rules that notify you when MailChannels detects suspicious or abusive sending activity on your network. You can receive alerts by email or by webhook, and configure how frequently each alert fires. Setting up monitors is one of the most effective ways to stay ahead of compromised accounts and outbound spam before they affect your deliverability or billing. ## Monitor types Monitors are available on Standard plan subscriptions and above. You can configure alerts for the following event types: | Event | Description | | - | - | | Sender ID is sending spam | A sender is submitting messages flagged as spam | | Sender ID is sending to invalid recipients | A sender is addressing messages to non-existent addresses | | Sender ID is being rejected by destination | Receivers are rejecting messages from this sender | | Sender ID is being rate limited | MailChannels is throttling the sender due to abuse signals | | Sender ID is on MailChannels blocklist | The sender has been permanently blocked due to sustained abuse | | Sender ID blocked due to FBL reports | Receivers have complained about this sender, triggering a temporary block | | Envelope Sender Address is on MailChannels Blocklist | The envelope sender address is blocklisted | | Envelope Sender Domain is on MailChannels Blocklist | The envelope sender domain is blocklisted | | Envelope Sender Domain is sending spam | A sender domain is submitting spam | | Envelope Sender Domain is sending to invalid recipients | A sender domain is addressing non-existent recipients | | Envelope Sender Domain is listed in Spamhaus DBL | The sender domain appears in the [Spamhaus Domain Blocklist](https://www.spamhaus.org/blocklists/domain-blocklist/) | | IP Address has suddenly increased volume | An IP connecting to your account has sharply increased its sending rate | | IP Address is generating a large number of NDRs | An IP is producing an abnormal volume of non-delivery receipts | | IP Address is sending messages without Sender ID in headers | An IP is submitting messages with no user identity in the headers | ## Set up a monitor In the Host Console, navigate to **Outbound > Activity > Monitors**. If you have not created any monitors yet, the list will be empty. Click **Add Monitor** in the top-right corner of the page. This opens the monitor creation dialog. Select the event you want to track from the dropdown menu. Each option corresponds to a specific spam or abuse signal described in the table above. Choose how often you want to receive alerts. Intervals range from **1 to 24 hours**. The default is once per hour. Choosing a longer interval reduces noise; choosing a shorter interval gives you faster detection. Select **Email** or **Webhook**: * **Email** — enter one or more comma-separated email addresses. MailChannels sends an alert message to each address when the monitor condition is met. * **Webhook** — enter the full HTTP or HTTPS URL of your endpoint. MailChannels sends a JSON POST to that URL. Use `https://` to ensure the payload is encrypted in transit. For details on configuring webhook endpoints and automating responses, see [Automate compromised account response with webhooks](/outbound/webhooks). Check **Enabled** to activate the monitor immediately, then click the save button. You can also create several monitors first and then click **Enable All** at the top of the page to activate them in bulk. When you first log in to the Host Console, MailChannels suggests a set of starter monitors based on your account. Accepting these suggestions creates monitors using your login email as the alert destination. If you skip that screen, follow the steps above to configure monitors manually. Monitors are available on Standard plan subscriptions and above. If the Monitors section is not visible in your console, upgrade your subscription to unlock it. ## Manage existing monitors After creating monitors, you can edit the interval or delivery method for any monitor by returning to **Outbound > Activity > Monitors**. Use the individual **Enabled** toggle to pause a specific monitor without deleting it, or click the trash icon to remove it permanently. # DKIM configuration for cPanel Source: https://docs.mailchannels.com/outbound/mta/cpanel-dkim Add DKIM signing to outbound mail on cPanel servers by extending the MailChannels Exim transport configuration. DKIM signing is optional for MailChannels to function on cPanel, but it adds an authentication layer that improves deliverability. MailChannels does not modify the parts of messages you sign with DKIM, so existing signatures remain valid. This guide assumes you have already completed the base [cPanel/WHM setup](/outbound/mta/setup-cpanel). ## Add DKIM to the Exim transport In WHM, navigate to **Service Configuration > Exim Configuration Manager > Advanced Editor** and locate the `mailchannels_smtp` transport block in the **TRANSPORTSTART** section. ### Exim 4.93 and earlier Append the following lines to the end of the `mailchannels_smtp` transport block: ```exim theme={null} dkim_domain = $sender_address_domain dkim_selector = default dkim_canon = relaxed dkim_private_key = "/var/cpanel/domain_keys/private/${dkim_domain}" ``` The completed transport block will look like this: ```exim Section: TRANSPORTSTART theme={null} mailchannels_smtp: driver = smtp hosts_require_auth = * tls_tempfail_tryclear = true headers_add = X-AuthUser: ${if match {$authenticated_id}{.*@.*}\ {$authenticated_id} {${if match {$authenticated_id}{.+}\ {$authenticated_id@$primary_hostname}{$authenticated_id}}}} dkim_domain = $sender_address_domain dkim_selector = default dkim_canon = relaxed dkim_private_key = "/var/cpanel/domain_keys/private/${dkim_domain}" ``` ### Exim 4.94 and later Exim 4.94 introduced tainted filename restrictions that affect how `${dkim_domain}` is expanded. Use the `get_dkim_domain` Perl function instead: ```exim Section: TRANSPORTSTART theme={null} mailchannels_smtp: driver = smtp hosts_require_auth = * tls_tempfail_tryclear = true headers_add = X-AuthUser: ${if match {$authenticated_id}{.*@.*}\ {$authenticated_id} {${if match {$authenticated_id}{.+}\ {$authenticated_id@$primary_hostname}{$authenticated_id}}}} dkim_domain = ${perl{get_dkim_domain}} dkim_selector = default dkim_canon = relaxed dkim_private_key = "/var/cpanel/domain_keys/private/${dkim_domain}" ``` ## Apply the changes After saving the Exim configuration, restart Exim to activate the new settings: ```bash theme={null} /scripts/restartsrv_exim ``` Send test messages to confirm that outbound mail is being signed and that signatures validate correctly at the receiving end. # cPanel Exim SSL verify error: certificate name mismatch Source: https://docs.mailchannels.com/outbound/mta/cpanel-ssl-error The SSL verify error certificate name mismatch in cPanel Exim logs has no impact on email delivery and can be safely suppressed. ## Symptom The following error appears in Exim logs on cPanel servers: ``` SSL verify error: certificate name mismatch: "/OU=Domain Control Validated/OU=EssentialSSL Wildcard/CN=*.mailchannels.net" ``` ## Impact None. This error does not affect email delivery. Exim logs the verification status but does not reject the connection or fail the message. ## Cause Exim 4.86 introduced certificate name checking on outbound TLS connections by default. The verification status is now logged for all outbound TLS connections — even when it is not required. The log entry is informational only. ## Suppress the error If you want to stop this message from appearing in your logs, disable `tls_verify_cert_hostnames` for the MailChannels relay host in the Exim transport configuration via the WHM Advanced Editor. For reference, see the [cPanel community thread on this error](https://forums.cpanel.net/threads/ssl-verify-error-certificate-name-mismatch.514501/). # cPanel Exim: unroutable address error Source: https://docs.mailchannels.com/outbound/mta/cpanel-unroutable-address Fix the Exim unroutable address error caused by a missing space in the route_list directive. ## Symptom Exim returns an "unroutable address" error when attempting to relay mail through MailChannels. ## Cause The error is caused by a missing space in the `route_list` directive in the Exim configuration. Without the space, Exim cannot parse the destination correctly. ## Fix In WHM's **Exim Advanced Editor**, locate the `send_via_mailchannels` router in the **ROUTERSTART** section and check the `route_list` line. **Incorrect — missing space:** ```exim theme={null} route_list = *smtp.mailchannels.net::25 randomize byname ``` **Correct — space between `*` and the hostname:** ```exim theme={null} route_list = * smtp.mailchannels.net::25 randomize byname ``` The space between `*` and `smtp.mailchannels.net` is required. The `*` means "route all destinations" and must be separated from the smarthost address by a space. Save the configuration and restart Exim: ```bash theme={null} /scripts/restartsrv_exim ``` # Setup Qmail Source: https://docs.mailchannels.com/outbound/mta/mailchannels-setup-qmail Configure qmail to relay outbound mail through MailChannels Outbound Filtering using SMTP authentication, X-AuthUser sender identification headers, and smtproutes smarthost configuration. Configuring qmail to relay through MailChannels routes all outbound mail through Outbound Filtering before delivery, protecting your sending reputation. Due to qmail's modular structure, setup requires patching, script creation, and smarthost configuration. Modifying qmail configuration files, applying patches, and creating scripts incorrectly can disrupt email services. Back up your configuration files and qmail source before making changes. Test in a non-production environment if possible. **Audience:** Server administrators managing qmail servers. Familiarity with qmail configuration, control files, and patching processes is essential. ## Prerequisites * Root access to your qmail server * Your MailChannels SMTP username and password * qmail patched to enable SMTP authentication (AUTH SMTP) — consult qmail documentation to apply the SMTPAUTH patch if not already done * qmail patched with the qmailqueue patch (e.g., Bruce Guenter's patch) to allow modifying headers before messages are queued ## Configuration Steps The `X-AuthUser` header identifies the responsible sender in your system. MailChannels uses it to isolate compromised accounts without blocking other senders. This step uses Bruce Guenter's qmailqueue patch and qmail-qfilter. **Install qmail-qfilter:** Apply Bruce Guenter's qmail-qfilter patch if not already applied. Download the patch (search for "qmailqueue-patch") and apply it to your qmail source, then recompile qmail. **Modify your qmail-smtpd run script:** Locate your run script (e.g., `/service/qmail-smtpd/run` or `/var/qmail/supervise/qmail-smtpd/run`). Add or modify the `QMAILQUEUE` environment variable before the `exec qmail-smtpd` line: ```bash theme={null} export QMAILQUEUE="/var/qmail/bin/qmail-authuser-filter.sh" ``` **Create the filter shell script:** Create `/var/qmail/bin/qmail-authuser-filter.sh` with the following content, then make it executable: ```sh /var/qmail/bin/qmail-authuser-filter.sh theme={null} #!/bin/sh # Passes the email through qmail-qfilter with the header injection script. exec /usr/local/bin/qmail-qfilter /var/qmail/bin/qmail-authuser-header.pl ``` ```bash theme={null} chmod +x /var/qmail/bin/qmail-authuser-filter.sh ``` Adjust the path to `qmail-qfilter` if it is installed elsewhere. **Create the Perl header script:** Create `/var/qmail/bin/qmail-authuser-header.pl` with the following content, then make it executable: ```perl /var/qmail/bin/qmail-authuser-header.pl theme={null} #!/usr/bin/perl # Adds the X-AuthUser header using the TCPREMOTEINFO environment variable. # TCPREMOTEINFO should be set by tcpserver to the authenticated user. my $auth_user = $ENV{'TCPREMOTEINFO'}; if ($auth_user) { print "X-AuthUser: $auth_user\n"; } else { my $fallback_user = $ENV{'SMTPREMOTEIP'} || "unknown_sender"; print "X-AuthUser: $fallback_user\n"; } while() { print; } exit 0; ``` ```bash theme={null} chmod +x /var/qmail/bin/qmail-authuser-header.pl ``` For this script to capture the authenticated user, your tcpserver setup for qmail-smtpd must correctly set `TCPREMOTEINFO` upon successful SMTP authentication. Edit or create `/var/qmail/control/smtproutes`. To route all external email through MailChannels, add the following line — replacing the placeholders with your actual MailChannels SMTP credentials: ```text /var/qmail/control/smtproutes theme={null} :smtp.mailchannels.net:25 YourMailChannelsUsername YourMailChannelsPassword ``` If outbound TCP port 25 is blocked in your network, use port `587` or `2525` instead: `:smtp.mailchannels.net:587 YourMailChannelsUsername YourMailChannelsPassword` The leading colon `:` means this rule applies to all domains not matched by more specific entries in `smtproutes` and not listed in `/var/qmail/control/locals`. Restart the relevant qmail services for all changes to take effect. The exact command varies depending on your installation: ```bash theme={null} qmailctl restart ``` Or, if managing services individually with daemontools: ```bash theme={null} svc -t /service/qmail-smtpd svc -t /service/qmail-send ``` Consult your qmail installation documentation for the precise commands. ## Verify the configuration Send emails from an account on your server to a few external addresses (e.g., Gmail, Outlook.com) to confirm delivery. Examine your qmail logs (often under `/var/log/qmail/`, or managed by multilog to directories like `/var/log/qmail/qmail-send/current` and `/var/log/qmail/qmail-smtpd/current`). * Look for lines indicating successful delivery via `smtp.mailchannels.net`. * Check for any authentication or relay errors. Log in to your [MailChannels Host Console](https://console.mailchannels.net) and check **Log Search** to confirm your test messages are being processed. Verify that the `X-AuthUser` header is present and correctly identifies the sender — the third field of the `senderID` value will contain the value qmail inserted into `X-AuthUser`. ## Optional: advanced qmail settings These settings are configured by creating or editing files in `/var/qmail/control/`. Restart qmail-send (or run a full `qmailctl restart`) after making changes. * **Queue lifetime (`queuelifetime`)** — how long in seconds a message stays in the queue before bouncing. Default is 604,800 seconds (7 days). ```bash theme={null} echo "172800" > /var/qmail/control/queuelifetime ``` * **Server identity (`me`)** — sets the FQDN of your mail server, used in HELO and other operations. ```bash theme={null} echo "mail.yourserver.example.com" > /var/qmail/control/me ``` * **HELO hostname (`helohost`)** — overrides the HELO name used by qmail-remote. If not set, the value from `me` is used. ```bash theme={null} echo "smtp-out.yourserver.example.com" > /var/qmail/control/helohost ``` * **Remote delivery concurrency (`concurrencyremote`)** — maximum simultaneous remote SMTP delivery processes. Default is 20. ```bash theme={null} echo "50" > /var/qmail/control/concurrencyremote ``` * **Local delivery concurrency (`concurrencylocal`)** — maximum simultaneous local delivery processes. Default is 10. ```bash theme={null} echo "20" > /var/qmail/control/concurrencylocal ``` * **Connection timeout (`timeoutconnect`)** — seconds qmail-remote waits for a connection. Default is 60 seconds. ```bash theme={null} echo "30" > /var/qmail/control/timeoutconnect ``` * **Remote response timeout (`timeoutremote`)** — seconds qmail-remote waits for each response after connecting. Default is 1,200 seconds (20 minutes). ```bash theme={null} echo "600" > /var/qmail/control/timeoutremote ``` # Setup network printers and SMTP devices Source: https://docs.mailchannels.com/outbound/mta/printer-setup Configure network-enabled printers and other SMTP devices to send email through MailChannels Outbound Filtering. MailChannels Outbound Filtering provides a standard SMTP gateway that works with any device capable of sending email over SMTP — including network printers, scanners, and other embedded systems. ## Prerequisites * Your MailChannels SMTP username and password * Network access from the device to `smtp.mailchannels.net` on port `2525` (recommended for devices that do not support port 25) ## SMTP settings Configure your device with the following values: | Field | Value | | - | - | | **SMTP hostname** | `smtp.mailchannels.net` | | **Port** | `2525` | | **SSL/TLS** | Enabled | | **SMTP authentication** | Enabled | | **Username** | Your MailChannels SMTP username | | **Password** | Your MailChannels SMTP password | ## Example: HP OfficeJet Pro The following steps apply to the HP OfficeJet Pro 8600. Other HP models and printer brands have a similar process — consult your device documentation for exact navigation. On the printer's control panel, open the network settings and note the IP address. Enter the printer's IP address in a browser on your computer to open the EWS configuration page. Navigate to **Scan** > **Scan to E-mail** > **Outgoing Email Profiles** and click **New**. Enter your email address and a display name, then click **Next**. Fill in the SMTP settings from the table above and click **Next**. Follow the remaining prompts to the Summary screen and click **Save and Test** to confirm the configuration works. # Setup Azure Source: https://docs.mailchannels.com/outbound/mta/setting-up-for-azure Configure Postfix on an Azure VM to relay outbound mail through MailChannels Outbound Filtering, including SASL authentication and TLS on port 2525. This guide walks you through installing and configuring Postfix on an Azure VM to relay all outbound mail through MailChannels Outbound Filtering. Azure blocks outbound port 25 on VMs. This guide uses port 2525, which MailChannels configures identically to port 25. ## Prerequisites * Root or `sudo` access to your Azure VM * Your MailChannels SMTP username and password ## Configure the relay Connect to your VM over SSH and install Postfix along with the SASL authentication modules. When prompted during installation, select **Local Only** as the configuration type. **Debian/Ubuntu:** ```bash theme={null} sudo apt-get update && sudo apt-get install postfix libsasl2-modules -y ``` **RHEL/CentOS:** ```bash theme={null} sudo yum install postfix cyrus-sasl-plain cyrus-sasl-md5 -y ``` Open `/etc/postfix/main.cf` in a text editor. Find and comment out the following lines if present: ```text /etc/postfix/main.cf theme={null} #default_transport = error #relay_transport = error ``` Then append the following configuration to the end of the file: ```text /etc/postfix/main.cf theme={null} relayhost = [smtp.mailchannels.net]:2525 smtp_sasl_auth_enable = yes smtp_sasl_password_maps = hash:/etc/postfix/password smtp_sasl_security_options = noanonymous smtpd_sasl_authenticated_header = yes smtp_tls_security_level = encrypt ``` Create `/etc/postfix/password` with your MailChannels SMTP credentials. The file is space-delimited — do not use tabs: ```text /etc/postfix/password theme={null} smtp.mailchannels.net : ``` Secure the file and generate the Postfix hash map: ```bash theme={null} chown root:root /etc/postfix/password chmod 0600 /etc/postfix/password postmap hash:/etc/postfix/password ``` **Debian/Ubuntu:** ```bash theme={null} /etc/init.d/postfix restart ``` **RHEL/CentOS:** ```bash theme={null} postfix reload ``` ## Verify the configuration Install a mail utility and send a test message to an address you control. **Debian/Ubuntu:** ```bash theme={null} sudo apt-get install mailutils -y ``` **RHEL/CentOS:** ```bash theme={null} sudo yum install mailx -y ``` Then send the test: ```bash theme={null} echo 'Test Message' | mail -s 'Relay Config Test' you@example.com ``` Tail the mail log to confirm the message was accepted and cleared from the queue without errors: ```bash theme={null} tail -f /var/log/maillog ``` Log in to your [MailChannels Host Console](https://console.mailchannels.net) and check **Outbound > Activity > Log Search** to confirm your test message was processed and delivered. # Setup DirectAdmin Source: https://docs.mailchannels.com/outbound/mta/setting-up-for-directadmin Configure Exim in DirectAdmin to relay outbound mail through MailChannels Outbound Filtering using SMTP authentication and smart host settings. This guide walks you through configuring Exim on a DirectAdmin server to relay all outbound mail through MailChannels Outbound Filtering. ## Prerequisites * Root or `sudo` access to your DirectAdmin server * Your MailChannels SMTP username and password ## Exim 4.5.7 and later Create `/etc/exim.routers.pre.conf` with the following content: ```text /etc/exim.routers.pre.conf theme={null} smart_route: driver = manualroute domains = ! +local_domains ignore_target_hosts = 127.0.0.0/8 condition = "${perl{check_limits}}" transport = auth_relay route_list = * smtp.mailchannels.net::25 no_more ``` Create `/etc/exim.transports.pre.conf` with the following content: ```text /etc/exim.transports.pre.conf theme={null} auth_relay: driver = smtp headers_add = X-AuthUser: $authenticated_id hosts_require_auth = $host_address hosts_require_tls = $host_address ``` The `X-AuthUser` header identifies the authenticated sender. MailChannels uses it to track per-sender reputation. Messages without this header are treated as forwarded mail and subject to different policies. Create `/etc/exim.authenticators.post.conf` with the following content, replacing the placeholders with your actual MailChannels SMTP credentials: ```text /etc/exim.authenticators.post.conf theme={null} auth_login: driver = plaintext public_name = LOGIN hide client_send = : smtpusername : smtppassword ``` ```bash theme={null} service exim restart ``` ## Exim prior to 4.5.7 Exim versions prior to the current major release are considered obsolete by the Exim development team. Upgrading to a current release is strongly recommended. With older DirectAdmin installations, Exim configuration is stored in a single file at `/etc/exim.conf`. Make the following changes to that file. In the `begin authenticators` section, add the following block, replacing the placeholders with your actual MailChannels SMTP credentials: ```text /etc/exim.conf theme={null} auth_login: driver = plaintext public_name = LOGIN hide client_send = : smtpusername : smtppassword ``` At the top of the `begin routers` section, add the following block: ```text /etc/exim.conf theme={null} send_via_mailchannels: driver = manualroute domains = ! +local_domains senders = !*@domain1.to.exclude.com : !*@domain2.to.exclude.com ignore_target_hosts = 127.0.0.0/8 condition = "${perl{check_limits}}" transport = auth_relay route_list = * smtp.mailchannels.net no_more ``` If you do not need to exclude any senders or domains from the relay, leave the `senders =` line blank or remove it entirely. Do not remove or comment out the `lookuphost:` router. It handles delivery for any domains excluded from the MailChannels relay and routes them directly from the DirectAdmin server. In the `begin transports` section, add the following block: ```text /etc/exim.conf theme={null} auth_relay: driver = smtp port = 25 headers_add = X-AuthUser: $authenticated_id hosts_require_auth = $host_address hosts_require_tls = $host_address ``` ```bash theme={null} service exim restart ``` # Setup hMailServer Source: https://docs.mailchannels.com/outbound/mta/setting-up-for-hmailserver Configure hMailServer to relay outbound mail through MailChannels Outbound Filtering using SMTP authentication and X-AuthUser sender identification headers. This guide walks you through configuring hMailServer to relay all outbound mail through MailChannels Outbound Filtering. ## Prerequisites * Access to the hMailServer administration interface * Your MailChannels SMTP username and password ## Configure the relay In the hMailServer administration interface, navigate to **Settings** > **Protocols** > **SMTP** > **Delivery of e-mail** > **SMTP Relayer**. Configure the relay with the following values: | Field | Value | | - | - | | **Remote host name** | `smtp.mailchannels.net` | | **Remote TCP/IP port** | `25` | | **Server requires authentication** | Checked | | **User name** | Your MailChannels SMTP username | | **Password** | Your MailChannels SMTP password | hMailServer SMTP relay configuration MailChannels uses the `X-AuthUser` header to identify the authenticated sender and track per-sender reputation. Messages without this header are treated as forwarded mail and subject to different policies. Locate `hmailserver.ini` in your hMailServer installation folder and add the following lines: ```ini theme={null} [Settings] AddXAuthUserHeader=1 ``` Restart hMailServer for the change to take effect. # Setup IceWarp Source: https://docs.mailchannels.com/outbound/mta/setting-up-for-icewarp Configure IceWarp to relay outbound mail through MailChannels Outbound Filtering using SMTP authentication and smart host settings. This guide walks you through configuring IceWarp to relay all outbound mail through MailChannels Outbound Filtering. ## Prerequisites * Access to the IceWarp administration interface * Your MailChannels SMTP username and password ## Configure the relay In the IceWarp administration interface, navigate to **Mail** > **General** > **Delivery** and enable **Use Relay Server**. Enter the following in the relay server field, replacing the placeholders with your actual MailChannels credentials: ```text theme={null} :@smtp.mailchannels.net:25 ``` IceWarp relay server configuration MailChannels uses the `X-AuthUser` header to identify the authenticated sender. Messages without this header are treated as forwarded mail and subject to different policies. Navigate to **Mail** > **Rules** and click **Add**. Configure the rule as follows: | Field | Value | | - | - | | **Conditions** | All messages | | **Actions** | Edit message header | | **Header** | `X-AuthUser:` | | **Value** | `%%auth_email%%` | Enable the rule and click **Apply**. IceWarp X-AuthUser rule configuration # Setup IIS Source: https://docs.mailchannels.com/outbound/mta/setting-up-for-iis Configure IIS SMTP to relay outbound mail through MailChannels Outbound Filtering using smart host settings and SMTP authentication. This guide walks you through configuring IIS SMTP to relay all outbound mail through MailChannels Outbound Filtering. ## Prerequisites * A Windows host with IIS SMTP installed and configured * Your MailChannels SMTP username and password ## Configure the relay Open **IIS Manager**, right-click on the SMTP virtual server, and choose **Properties** from the menu. IIS virtual server properties Click the **Delivery** tab, then click **Advanced**. In the **Smart host** field, enter: ```text theme={null} smtp.mailchannels.net ``` Click **OK** to save. Still on the **Delivery** tab, click the **Security** tab. Select **Basic Authentication** if it is not already the default. Enter your MailChannels SMTP username and password into the **User name** and **Password** fields, then click **OK**. ## Troubleshooting **Port 25 is blocked** — MailChannels supports connections on ports `2525`, `465`, and `587` as alternatives to port 25. To change the outbound port: 1. Open **IIS Manager** and right-click on the SMTP virtual server. Choose **Properties**. 2. Click the **Delivery** tab, then click **Outbound Connections**. IIS alternate port configuration 3. In the **Outbound Connections** dialog, change the **TCP port** value to your desired port and click **OK**. # Setup Kerio Connect Source: https://docs.mailchannels.com/outbound/mta/setting-up-for-kerio-connect Configure Kerio Connect to relay outbound mail through MailChannels Outbound Filtering using smart host settings and SMTP authentication. This guide walks you through configuring Kerio Connect to relay all outbound mail through MailChannels Outbound Filtering. ## Prerequisites * Access to the Kerio Administration interface * Your MailChannels SMTP username and password * SMTP installed and configured on your Kerio Connect system — see the [GFI/Kerio Connect documentation](https://manuals.gfi.com/en/kerio/connect/content/server-configuration/) if not already set up ## Configure the relay In the Kerio Administration interface, select **Configuration** > **SMTP** to open the main configuration screen. Select the **Relay Control** tab. If not already set, check **Allow relay only for:** and then check **Users authenticated through SMTP for outgoing mail**. Relay Control tab Select the **SMTP Delivery** tab. Under **SMTP client options**, uncheck **Use SSL/TLS if supported by remote SMTP server** if it is checked. MailChannels uses opportunistic TLS outbound after content scanning, so this setting is not needed on the client side. SMTP Delivery tab Do not remove the default profile from the **SMTP delivery** list. Move it to the last position after adding your MailChannels relay, so it acts as a last-resort destination. Click **Add** to create a new SMTP relay item and enter the following values: | Field | Value | | - | - | | **Description** | `MailChannels Outbound Filtering` | | **Condition** | Always use this relay server | | **Relay server hostname** | `smtp.mailchannels.net` | | **Relay server port** | `25` (or `2525`, `465`, or `587` if port 25 is blocked) | | **Relay server requires authentication** | Checked | | **User** | Your MailChannels SMTP username | | **Password** | Your MailChannels SMTP password | | **Authentication** | SMTP AUTH Command | | **Enable rule** | Checked | SMTP relay configuration Click **OK** to save. Select your new relay item and use the arrows to move it to the top of the list so it is evaluated before the default settings. ## Troubleshooting **Alternate SMTP ports** — If port 25 is blocked, configure the relay to use port `2525`, `465`, or `587` instead. **Excluding a domain or sender from the relay** — Add a new SMTP relay item with the exclusion condition and place it *above* any rule that uses **Always use this relay server**. Kerio Connect evaluates rules in order and stops at the first match. # Setup Lotus Domino Source: https://docs.mailchannels.com/outbound/mta/setting-up-for-lotus-domino Configure Lotus Domino to relay outbound mail through MailChannels Outbound Filtering using SMTP authentication and smart host routing. This guide walks you through configuring Lotus Domino to relay all outbound mail through MailChannels Outbound Filtering. ## Prerequisites * Access to **Domino Administrator** * Your MailChannels SMTP username and password ## Configure the relay Open **Domino Administrator**, click **Administration**, and select the **Configuration** tab. Click **Configurations**, then double-click the name of your Domino server. At the top of the window, click **Edit Server Configuration**. Select the **Router/SMTP** tab in the first row (this also selects the **Basics** tab in the second row). Under **Relay host for messages leaving the local internet domain**, enter: ``` smtp.mailchannels.net ``` In the **Use authentication when sending messages to the relay host** field, select: **Required** This checks whether the server can authenticate. If the server cannot authenticate, no attempt is made to try a non-authenticated session. Enter your MailChannels credentials: | Field | Value | | - | - | | **Name** | Your MailChannels SMTP username | | **Password** | Your MailChannels SMTP password | Select the **Restrictions and Controls** tab from the second row, then the **Transfer Controls** tab from the third row. Set **Initial Transfer Retry Interval** to `1 minute` or higher. Click **Save & Close** to apply the configuration. Send a test message to confirm that outbound mail is relaying through MailChannels as expected. # Setup Outlook 2010 Source: https://docs.mailchannels.com/outbound/mta/setting-up-for-outlook-2010 Configure Outlook 2010 to send mail through MailChannels Outbound Filtering by setting smtp.mailchannels.net as the outgoing mail server with SMTP authentication. This guide walks you through configuring Outlook 2010 to relay outbound mail through MailChannels Outbound Filtering. ## Prerequisites * Outlook 2010 installed and configured with your email account * Your MailChannels SMTP username and password ## Configure the relay Go to the **File** menu and click the **Info** tab to open the Account Information section. Outlook 2010 File menu Info tab Click **Account Settings**. Outlook 2010 Account Settings Select your email account from the list and click **Change**. Outlook 2010 account list Change **Outgoing mail server (SMTP)** to: ```text theme={null} smtp.mailchannels.net ``` Click **More Settings...**. Outlook 2010 outgoing mail server Select the **Outgoing Server** tab in the email settings dialog. * Check **My outgoing server (SMTP) requires authentication**. * Select **Log on using**. * Enter your MailChannels SMTP username and password. * Check **Remember password**. Outlook 2010 outgoing server authentication Select the **Advanced** tab. * Set **Outgoing server (SMTP)** port to `25`. * Set **Use the following type of encrypted connection** to `TLS`. Outlook 2010 advanced port and TLS settings Click **Next**, then click **Finish** to save your settings. # Setup Sendmail Source: https://docs.mailchannels.com/outbound/mta/setting-up-for-sendmail Configure Sendmail to relay outbound mail through MailChannels Outbound Filtering using SMTP authentication and smart host routing. This guide walks you through configuring Sendmail to relay all outbound mail through MailChannels Outbound Filtering. Sendmail requires `libsasl` to send authentication requests to MailChannels servers. The `saslauthd` daemon is not required unless you plan to receive authentication requests on the same server. See the [Sendmail SASL support page](http://www.sendmail.com/sm/open_source/tips/compiling_sendmail/#SASL) for more information. ## Prerequisites * Root or `sudo` access to your Sendmail server * Your MailChannels SMTP username and password * `libsasl` installed on your server ## Configure the relay Sendmail stores SMTP credentials in `/etc/mail/access`. Add the following line to the file, replacing the placeholders with your actual MailChannels SMTP username and password: ```text /etc/mail/access theme={null} AuthInfo:smtp.mailchannels.net "U:MailChannelsSMTPUsername" "P:MailChannelsSMTPPassword" "M:PLAIN" ``` Do not use your MailChannels Console login username here. The SMTP authentication username is different — it is the account ID provided in your activation email. Edit `/etc/mail/sendmail.mc` and add the following lines to set MailChannels as the smart host. Add lines 1, 3, and 4 around the existing `FEATURE` line (line 2): ```text /etc/mail/sendmail.mc theme={null} define(`SMART_HOST', `smtp.mailchannels.net')dnl FEATURE(`access_db')dnl define(`RELAY_MAILER_ARGS', `TCP $h 25')dnl define(`ESMTP_MAILER_ARGS', `TCP $h 25')dnl ``` Also comment out the following line in the same file if it is present: ```text /etc/mail/sendmail.mc theme={null} # FEATURE(`access_db', , `skip')dnl ``` Append the following lines just before the `MAILER DEFINITIONS` section in `/etc/mail/sendmail.mc`. This adds an authenticated sender header that MailChannels uses to track per-sender reputation: ```text /etc/mail/sendmail.mc theme={null} LOCAL_CONFIG HX-AuthUser: ${auth_authen} ``` The `X-AuthUser` header is only added when the sender is authenticated. Messages without this header are treated as forwarded mail and subject to different policies. Run the following commands as root to compile the updated configuration files: ```bash theme={null} cd /etc/mail m4 sendmail.mc > sendmail.cf makemap hash access < access ``` Then restart Sendmail. On newer distributions: ```bash theme={null} service sendmail restart ``` On older distributions: ```bash theme={null} /etc/init.d/sendmail restart ``` # Setup SmarterMail Source: https://docs.mailchannels.com/outbound/mta/setting-up-for-smartermail Configure SmarterMail to relay outbound mail through MailChannels Outbound Filtering using SMTP authentication and X-AuthUser sender identification headers. This guide walks you through configuring SmarterMail to relay all outbound mail through MailChannels Outbound Filtering. Instructions are provided for SmarterMail 16.x and the older SmarterMail 10/11 releases. ## Prerequisites * System administrator access to SmarterMail * Your MailChannels SMTP username and password ## SmarterMail 16.x Log in as the system administrator. Navigate to **Settings** > **Gateways / Failover** > **Outgoing** and click **New**. Configure the gateway with the following values: | Field | Value | | - | - | | **Server address** | `smtp.mailchannels.net` | | **Port** | `25` (or `587` / `2525` if port 25 is blocked) | | **Encryption** | TLS | | **Status** | Enabled | | **Priority** | Normal | | **Enable authentication** | On | | **SMTP username** | Your MailChannels SMTP username | | **Password** | Your MailChannels SMTP password | Click **Save**. SmarterMail 16 outgoing gateway configuration Navigate to **Settings** > **Protocols** > **SMTP Out**. Enable the option **Append X-SmarterMail-Authentication as header**. MailChannels uses this header to identify the authenticated sender and track per-sender reputation. SmarterMail 16 SMTP Out header setting ## SmarterMail 11 Navigate to **Settings** > **Routing** > **Outgoing Gateway** and create a new gateway with the following values: | Field | Value | | - | - | | **Server address** | `smtp.mailchannels.net` | | **Port** | `25` | | **Encryption** | TLS | | **Authentication enabled** | Checked | | **Auth username** | Your MailChannels SMTP username | | **Auth password** | Your MailChannels SMTP password | SmarterMail 11 outgoing gateway configuration Navigate to **Settings** > **Protocol Settings** > **SMTP Out**. Enable the option **Append authenticated as header for outgoing messages**. SmarterMail 11 SMTP Out header setting ## SmarterMail 10 SmarterMail 10 does not support adding X-headers, which means MailChannels cannot identify individual authenticated senders. Upgrading to SmarterMail 11 or later is recommended. Navigate to **Settings** > **Routing** > **Outgoing Gateway** and create a new gateway with the following values: | Field | Value | | - | - | | **Server address** | `smtp.mailchannels.net` | | **Port** | `25` | | **Encryption** | TLS | | **Authentication enabled** | Checked | | **Auth username** | Your MailChannels SMTP username | | **Auth password** | Your MailChannels SMTP password | SmarterMail 10 outgoing gateway configuration # Setup cPanel/WHM Source: https://docs.mailchannels.com/outbound/mta/setup-cpanel Route outbound email from cPanel/WHM through MailChannels using the official plugin for automated setup or the Exim Advanced Editor for manual Exim setup. cPanel & WHM servers run Exim as their MTA. You can connect them to MailChannels Outbound Filtering in two ways: by installing the official MailChannels cPanel plugin (recommended), or by manually adding configuration blocks in WHM's Exim Advanced Editor. This guide covers both paths. The MailChannels cPanel plugin is the recommended method. It automates most of the configuration and handles cPanel-specific requirements such as hourly email limits and SRS forwarding. Use the manual method only if you cannot install the plugin. ## Path 1: Install the cPanel plugin (recommended) The MailChannels cPanel plugin configures Exim automatically and stays compatible with cPanel updates. If you previously configured Exim manually, you must remove those manual settings before installing the plugin. The same plugin manages both Outbound Filtering and [Inbound Filtering](/inbound/cpanel-plugin). To use it for Outbound Filtering only, complete the steps below and leave the plugin's **Inbound** configuration section unconfigured. ### Requirements * cPanel & WHM version 68 or higher * PHP 5.6 or higher ### Installation Log in to your server as root and run the following command: ```bash theme={null} wget https://s3.amazonaws.com/mailchannels-reseller/plugins/mailchannels-cpanel-v3-installer \ && bash mailchannels-cpanel-v3-installer ``` If you are upgrading from the v2 plugin, the installer will prompt you to uninstall it first. If you choose not to, the installer will exit — uninstall v2 manually before proceeding. Log in to the cPanel/WHM interface and search for **MailChannels** using the search box in the top-left navigation bar. Click the result to open the plugin in a new tab. MailChannels plugin in WHM navigation In the plugin, click **Configuration** in the left navigation bar and expand the **API** section. Enter your MailChannels API key and click **Update**. Your API key can be created or retrieved from the [Host Console](https://console.mailchannels.net/settings/accountSettings#APIKeys) under **Settings > Account > API Keys**. API key configuration Still in **Configuration**, expand the **Outbound** section. Outbound configuration section Enter the following values: * **MailChannels Account ID** — found in the top-right corner of the [Host Console](https://console.mailchannels.net). Account ID location in Host Console * **SMTP Password** — create or retrieve one in the Host Console under **Settings > Account > SMTP Passwords**. ### Uninstalling To uninstall the plugin, run the following as root: ```bash theme={null} /usr/local/mailchannels/bin/uninstall.sh ``` Use the `--purge` flag to also remove log files (`/var/log/mailchannels`) and configuration files (`/etc/mailchannels`): ```bash theme={null} /usr/local/mailchannels/bin/uninstall.sh --purge ``` If MailChannels Outbound is active, the uninstaller will ask whether to disable it and revert your Exim settings to their previous state. Choosing yes is recommended. ### Known issues * SPF records are not automatically updated for cPanel sub-domains. * cPanel servers with 1,000 or more domains may time out before all SPF records are updated. *** ## Path 2: Manual Exim configuration If you cannot use the plugin, you can configure Exim manually through WHM's Exim Advanced Editor. All of the changes below are made within **Service Configuration > Exim Configuration Manager > Advanced Editor**. Editing the Exim configuration incorrectly can disrupt email services. Consider exporting your current configuration from the Advanced Editor before making changes. ### Step 1 — Add authentication (Section: AUTH) This block tells Exim how to authenticate with MailChannels when it connects to `smtp.mailchannels.net`. Scroll to **Section: AUTH** and add the following block. If `begin authenticators` is already present, do not add it a second time — add only the `mailchannels_login` stanza below it. ```exim Section: AUTH theme={null} begin authenticators mailchannels_login: driver = plaintext public_name = LOGIN client_send = : YOUR_USERNAME : YOUR_PASSWORD ``` Replace `YOUR_USERNAME` and `YOUR_PASSWORD` with your actual MailChannels SMTP credentials. ### Step 2 — Add a router (choose one section) The router tells Exim to send outgoing mail through MailChannels instead of delivering it directly. Choose **one** of the following sections depending on your requirements. Use this section if you need to respect cPanel's **Max hourly emails per domain** limits. Scroll to **Section: POSTMAILCOUNT** and add the appropriate block for your cPanel version. **For cPanel v108 or later** (includes SRS support for forwarded mail): ```exim Section: POSTMAILCOUNT — cPanel v108+ theme={null} send_via_mailchannels: driver = manualroute domains = ! +local_domains : ! +manualmx_domains .ifdef SRSENABLED transport = ${if eq {$local_part@$domain} \ {$original_local_part@$original_domain} \ {mailchannels_smtp} {mailchannels_forwarded_smtp}} .else transport = mailchannels_smtp .endif route_list = * smtp.mailchannels.net::25 randomize byname ``` **For cPanel v106 or earlier**: ```exim Section: POSTMAILCOUNT — cPanel v106 and earlier theme={null} send_via_mailchannels: driver = manualroute transport = mailchannels_smtp domains = ! +local_domains : ! +manualmx_domains route_list = * smtp.mailchannels.net::25 randomize byname ``` Use this section for a simpler setup. Note that this option bypasses cPanel's hourly email limits for relayed mail. Scroll to **Section: ROUTERSTART** and add the following block: ```exim Section: ROUTERSTART theme={null} send_via_mailchannels: driver = manualroute domains = ! +local_domains : ! +manualmx_domains transport = mailchannels_smtp route_list = * smtp.mailchannels.net::25 randomize byname host_find_failed = defer no_more ``` The `domains = ! +local_domains` condition ensures that email addressed to domains hosted locally on the server is delivered directly and not sent to MailChannels. ### Step 3 — Add a transport (Section: TRANSPORTSTART) The transport defines how Exim connects to MailChannels — including authentication, TLS requirements, the `X-AuthUser` header for sender identification, and DKIM signing. Scroll to **Section: TRANSPORTSTART** and add the following block. If you are on cPanel v108 or later and chose the POSTMAILCOUNT router above, also add the `mailchannels_forwarded_smtp` block that follows. **For all cPanel versions — base transport:** ```exim Section: TRANSPORTSTART theme={null} mailchannels_smtp: driver = smtp hosts_require_auth = * hosts_require_tls = * tls_tempfail_tryclear = true headers_add = X-AuthUser: ${if match {$authenticated_id}{.*@.*}\ {$authenticated_id} {${if match {$authenticated_id}{.+}\ {$authenticated_id@$primary_hostname}{$authenticated_id}}}} dkim_domain = ${perl{get_dkim_domain}} dkim_selector = default dkim_canon = relaxed dkim_private_key = "/var/cpanel/domain_keys/private/${dkim_domain}" message_linelength_limit = 2048 ``` **Additional transport for cPanel v108+ with SRS forwarding:** ```exim Section: TRANSPORTSTART — SRS forwarded transport (v108+) theme={null} .ifdef SRSENABLED mailchannels_forwarded_smtp: driver = smtp hosts_require_auth = * hosts_require_tls = * max_rcpt = 1 return_path = ${srs_encode {SRS_SECRET} {$return_path} {$original_domain}} tls_tempfail_tryclear = true headers_add = X-AuthUser: ${if match {$authenticated_id}{.*@.*}\ {$authenticated_id} {${if match {$authenticated_id}{.+}\ {$authenticated_id@$primary_hostname}{$authenticated_id}}}} dkim_domain = ${perl{get_dkim_domain}} dkim_selector = default dkim_canon = relaxed dkim_private_key = "/var/cpanel/domain_keys/private/${dkim_domain}" message_linelength_limit = 2048 .endif ``` The `X-AuthUser` header gives MailChannels the identity of the sending cPanel user. The `dkim_*` lines sign outgoing messages with cPanel's standard DKIM keys. The `max_rcpt = 1` setting on the forwarded transport is required for SRS to rewrite the return path correctly for each recipient. ### Step 4 — Save the configuration Scroll to the bottom of the Advanced Editor page and click **Save**. WHM will validate the configuration and restart Exim. Watch for any error messages — if the save fails, review the error output and correct the relevant section. *** ## Optional configurations ### Exclude or restrict specific domains Modify the `send_via_mailchannels` router to control which senders and recipients are routed through MailChannels. ```exim Route only specific sender domains theme={null} # Add inside the send_via_mailchannels router block senders = *@example.com : *@another.com ``` ```exim Exclude specific sender domains theme={null} # Add inside the send_via_mailchannels router block senders = !*@exclude-this.com : !*@exclude-that.net ``` ```exim Exclude specific recipient domains theme={null} # Modify the domains line inside the send_via_mailchannels router block domains = !dont-relay-to-this.com : !dont-relay-to-that.org : ! +local_domains ``` For exclusion rules covering many domains, use Exim's file-based domain lists. See the [advanced Exim configuration guide](/outbound/mta/setup-exim) for details. ### Exim queue optimization Add these settings in the appropriate sections of the Advanced Editor to tune queue and retry behavior. **Section: RETRYSTART** — MailChannels recommended retry timing: ```exim Section: RETRYSTART theme={null} * data_4xx F,4h,1m * rcpt_4xx F,4h,1m * timeout F,4h,1m * refused F,1h,5m * lost_connection F,1h,1m * * F,6h,5m ``` **Section: main** — queue runner limits and frozen message handling: ```exim Section: main theme={null} queue_run_max = 50 timeout_frozen_after = 12h ignore_bounce_errors_after = 1h ``` **Queue runner frequency** — edit `/etc/sysconfig/exim` (CentOS/RHEL/CloudLinux) or `/etc/default/exim` (Debian/Ubuntu): ```bash theme={null} QUEUE=60s ``` After making any changes outside the WHM Advanced Editor, restart Exim manually: ```bash theme={null} /scripts/restartsrv_exim ``` # Set up Microsoft Exchange Source: https://docs.mailchannels.com/outbound/mta/setup-exchange Configure a Send Connector in Exchange 2016 or 2019 to route outbound email through smtp.mailchannels.net using Basic Authentication over TLS. Microsoft Exchange routes outbound email through Send Connectors. To relay through MailChannels, you create a new Send Connector that points to `smtp.mailchannels.net` as a smart host, authenticates using your MailChannels SMTP credentials over TLS, and applies to all external recipient domains. This guide covers Exchange 2016 and 2019, which share a nearly identical configuration workflow through the Exchange Admin Center (EAC). These instructions apply to Exchange 2016 and Exchange 2019. If you are running Exchange 2010, the configuration is done through the Exchange Management Console rather than the EAC. See the [Exchange 2010 note](#exchange-2010) at the bottom of this page. ## Prerequisites * Administrator access to the Exchange Admin Center * Your MailChannels SMTP username and password * A working Exchange server configured for client access and able to route messages to the internet *** ## Configure the Send Connector Log in to the Exchange Admin Center (EAC). * **Exchange 2016:** Access the EAC at `https://localhost/ecp` from the Exchange server, or `https:///ecp` from another machine. * **Exchange 2019:** Access the EAC from the Exchange Admin Center in your browser. Navigate to **Mail flow > Send connectors**, then click the **Add** (+) icon to open the New Send Connector wizard. Enter a descriptive name for the connector — for example, **MailChannels-Relay** or **MC**. For **Type**, select **Internet**. This grants the connector the default permissions required to route messages through an external host outside your domain forest. Click **Next**. Select **Route mail through smart hosts**, then click **Add** (+). In the **Add smart host** dialog, select **Fully qualified domain name (FQDN)** and enter: ``` smtp.mailchannels.net ``` Click **Save**, then click **Next**. On the smart host authentication page, select **Basic authentication** and check the box labeled **Offer basic authentication only after starting TLS**. Enter your MailChannels SMTP credentials in the **Username** and **Password** fields. Click **Next**. You must select "Offer basic authentication only after starting TLS" to ensure credentials are never transmitted in plain text. MailChannels requires TLS for all authenticated connections. In the **Address space** section, click **Add** (+). In the **Add domain** dialog, enter the following: | Field | Value | | - | - | | **Type** | SMTP | | **Fully Qualified Domain Name (FQDN)** | `*` | | **Cost** | 1 | Using `*` as the FQDN means this connector applies to all external recipient domains. A cost of `1` makes this the preferred route. Click **Save**, then click **Next**. In the **Source server** section, click **Add** (+). In the **Select a Server** dialog, select the Mailbox server that will route mail through MailChannels. Click **Add**, click **OK**, then click **Finish**. Send a test email from an account on your Exchange server to an external address (for example, a Gmail or Outlook.com account) to confirm that the connector is working. Log in to your MailChannels Host Console and check **Activity > LogSearch** to verify that the message was received and processed. *** ## Alternate SMTP ports MailChannels accepts connections on multiple ports. If outbound TCP port 25 is blocked by your network or ISP, configure your Send Connector smart host to use one of the following alternatives: | Port | Use case | | - | - | | 25 | Default SMTP | | 587 | SMTP submission (commonly allowed) | | 465 | SMTPS (implicit TLS) | | 2525 | Alternate if 25 and 587 are both blocked | To change the port, edit the Send Connector's smart host entry and append the port number — for example, `smtp.mailchannels.net:587`. *** ## Exchange 2010 Exchange 2010 uses the **Exchange Management Console** rather than the web-based EAC. The setup follows the same general approach — create a new Send Connector with type **Internet**, set the smart host to `smtp.mailchannels.net`, and enable **Basic Authentication over TLS** with your MailChannels credentials — but the steps are navigated through the console UI under **Organization Configuration > Hub Transport > New Send Connector**. Exchange 2010 reached end of life in October 2020 and is no longer supported by Microsoft. Adding `X-AuthUser` headers in Exchange 2010 requires building a custom transport agent using an SDK that Microsoft has since retired. If you are running Exchange 2010 and need help configuring it to work with MailChannels Outbound Filtering, [contact MailChannels support](https://support.mailchannels.com/hc/en-us/requests/new) and include the Exchange version in your request. # Advanced Exim configuration for MailChannels relay Source: https://docs.mailchannels.com/outbound/mta/setup-exim Configure Exim routers and transports for MailChannels relay, exclude domains from routing, and rate-limit individual users to control outbound volume. This guide covers advanced Exim configuration for servers running Exim outside cPanel, or for administrators who manage the Exim configuration files directly. It includes the full router and transport setup for routing mail through MailChannels, file-based domain exclusions, per-user rate limiting, the `X-AuthUser` header for sender identification, and queue management settings. Always back up your Exim configuration files before making changes. Errors in the Exim configuration prevent the mail service from starting. ## Prerequisites * Root-level access to your Linux server * Your MailChannels SMTP username and password * Exim 4.x or newer installed and running Exim uses either a monolithic configuration file (`/etc/exim.conf`) or a split configuration under `/etc/exim4/conf.d/` (common on Debian/Ubuntu). The snippets below apply to either format — place each block in the corresponding section of your configuration. *** ## Core configuration ### Authentication Add the following block to the `begin authenticators` section of your Exim configuration. This tells Exim how to log in to `smtp.mailchannels.net` using your credentials. ```exim Authenticators section theme={null} # --- MailChannels Authentication --- mailchannels_login: driver = plaintext public_name = LOGIN client_send = : MailChannelsUsername : MailChannelsPassword ``` Replace `MailChannelsUsername` and `MailChannelsPassword` with your actual MailChannels SMTP credentials. For systems that store credentials in a separate file (such as `/etc/exim4/passwd.client` on Debian-based systems), add the following line to that file and restrict its permissions. ```text /etc/exim4/passwd.client theme={null} *:MailChannelsUsername:MailChannelsPassword ``` ```bash theme={null} sudo chmod 640 /etc/exim4/passwd.client sudo chown root:Debian-exim /etc/exim4/passwd.client ``` ### Router Add the following block to the routers section. Place it early in the router list so it intercepts outgoing mail before other rules. It routes all non-local mail to `smtp.mailchannels.net`. ```exim Routers section theme={null} # --- MailChannels Outbound Router --- send_via_mailchannels: driver = manualroute # Route all domains except those hosted locally on this server domains = ! +local_domains : ! +manualmx_domains transport = mailchannels_smtp # Distribute load across MailChannels servers route_list = * smtp.mailchannels.net::25 randomize byname # Defer delivery if the host cannot be resolved host_find_failed = defer # Do not fall through to subsequent routers no_more ``` If you are on a cPanel server and applying these settings manually, place the router block in **Section: POSTMAILCOUNT** in WHM's Exim Advanced Editor. This ensures cPanel's hourly email limits are enforced before mail is handed off to MailChannels. ### Transport Add the following block to the transports section. It defines how Exim connects to MailChannels, including TLS requirements, authentication, the `X-AuthUser` sender identification header, and DKIM signing. ```exim Transports section theme={null} # --- MailChannels SMTP Transport --- mailchannels_smtp: driver = smtp # Require authentication for all outbound connections hosts_require_auth = * # Require TLS encryption for all outbound connections hosts_require_tls = * # If TLS fails temporarily, retry immediately without TLS (fallback) tls_tempfail_tryclear = true # Add the X-AuthUser header so MailChannels can track the originating account headers_add = X-AuthUser: ${if match {$authenticated_id}{.*@.*}\ {$authenticated_id} {${if match {$authenticated_id}{.+}\ {$authenticated_id@$primary_hostname}{$authenticated_id}}}} # DKIM signing — adjust the key path to match your setup dkim_domain = $sender_address_domain dkim_selector = default dkim_canon = relaxed dkim_private_key = "/etc/dkim/keys/$dkim_domain/$dkim_selector.private" dkim_hash = sha256 # Optional: helps compatibility with older receiving mail servers # message_linelength_limit = 2048 ``` The `X-AuthUser` header value is derived from `$authenticated_id`, which Exim populates with the email address or username of the authenticated sender. MailChannels uses this value to attribute messages to individual users for reputation and abuse tracking. The third field of the `senderID` (`sid=`) in the MailChannels console reflects this value. Adjust the `dkim_private_key` path to match where your DKIM private keys are stored. Systems managed by cPanel typically keep keys under `/var/cpanel/domain_keys/private/`. OpenDKIM installations often use `/etc/dkim/keys/`. *** ## Reload Exim After editing the configuration, restart Exim for changes to take effect. ```bash theme={null} sudo update-exim4.conf sudo systemctl restart exim4 ``` ```bash theme={null} sudo systemctl restart exim ``` ```bash theme={null} /scripts/restartsrv_exim ``` *** ## Exclude domains from MailChannels routing You can exclude specific sender or recipient domains so their mail bypasses MailChannels and is delivered directly. The method depends on your Exim version. ### Exclude recipient domains (file-based) Create `/etc/excludereceiverdomains` and list each domain to exclude, one per line with a trailing colon. ```text /etc/excludereceiverdomains theme={null} example.com: partner.net: ``` Add the following line to **Section: CONFIG** (or the `main` section of your `exim.conf`): ```exim Section: CONFIG theme={null} domainlist exclude_receiver_domains = lsearch;/etc/excludereceiverdomains ``` Update the `domains` line in your `send_via_mailchannels` router to reference the exclusion list: ```exim Updated router — recipient domain exclusion theme={null} send_via_mailchannels: driver = manualroute domains = !+exclude_receiver_domains : ! +local_domains transport = mailchannels_smtp route_list = * smtp.mailchannels.net::25 randomize byname host_find_failed = defer no_more ``` ```bash theme={null} sudo systemctl restart exim4 # or: sudo systemctl restart exim ``` ### Exclude sender domains (file-based) Create `/etc/excludesenderdomains` and list each domain to exclude, one per line without a trailing colon. ```text /etc/excludesenderdomains theme={null} domain1.tld domain2.tld domain3.tld ``` Add the following line to **Section: CONFIG** (or the `main` section of your `exim.conf`): ```exim Section: CONFIG theme={null} domainlist exclude_sender_domains = lsearch;/etc/excludesenderdomains ``` Add a `senders` line to your `send_via_mailchannels` router: ```exim Updated router — sender domain exclusion theme={null} send_via_mailchannels: driver = manualroute domains = ! +local_domains senders = !*@+exclude_sender_domains transport = mailchannels_smtp route_list = * smtp.mailchannels.net::25 randomize byname host_find_failed = defer no_more ``` ```bash theme={null} sudo systemctl restart exim4 # or: sudo systemctl restart exim ``` On Exim 4.89, the file-based `lsearch` method for sender domain exclusion may not work as expected. Use the inline syntax instead: ```exim Exim 4.89 inline sender exclusion theme={null} senders = !: !*@domain1.tld : !*@domain2.tld ``` ### Exclude mailer-daemon bounces To prevent `mailer-daemon` bounce messages from being relayed through MailChannels, add the following `senders` condition to your router: ```exim Exclude mailer-daemon bounces theme={null} senders = !: !^mailer-daemon@.* ``` ### Inline domain-specific routing (without files) For a small number of domains, you can specify inclusions and exclusions directly in the router without creating external files. ```exim Route only specific sender domains theme={null} # Add to the send_via_mailchannels router senders = *@example.com : *@another.com ``` ```exim Exclude specific sender domains inline theme={null} # Add to the send_via_mailchannels router senders = !*@exclude-this.com : !*@exclude-that.net ``` ```exim Exclude specific recipient domains inline theme={null} # Modify the domains line in the send_via_mailchannels router domains = !dont-relay-to-this.com : !dont-relay-to-that.org : ! +local_domains ``` *** ## Rate-limit individual users Limiting how many messages an authenticated user can send per hour is one of the most effective ways to contain the damage when an account is compromised. Add the following condition to your Exim ACL (access control list) for SMTP DATA or RCPT: ```exim Rate limit per authenticated sender theme={null} ratelimit = 50 / 1h / strict / $authenticated_sender ``` This limits each authenticated user to 50 messages per hour. Adjust the count to match your policy. Exim will reject messages that exceed the limit with a temporary error, prompting the sending client to retry later. For a more comprehensive approach to blocking compromised accounts in Exim — including detecting dictionary attacks and cracking attempts — see the community-maintained [BlockCracking guide on the Exim wiki](https://github.com/Exim/exim/wiki/BlockCracking). *** ## Queue management These settings control how Exim handles the mail queue and retries. They typically belong in the `main` section and the `begin retry` section of your configuration. ### Retry intervals Add or update the following in the `begin retry` section. These values are recommended by MailChannels to balance timely retries with queue health. ```exim Retry section theme={null} # Recommended MailChannels retry rules * data_4xx F,4h,1m * rcpt_4xx F,4h,1m * timeout F,4h,1m * refused F,1h,5m * lost_connection F,1h,1m * * F,6h,5m ``` If you see `retry time not reached for any host` errors caused by Exim caching a stale MailChannels IP, run the following commands to clear the retry and wait databases immediately: ```bash theme={null} sudo /usr/sbin/exim_tidydb -t 0d /var/spool/exim retry sudo /usr/sbin/exim_tidydb -t 0d /var/spool/exim wait-remote_smtp ``` To prevent this from recurring, add a weekly cron job: ```bash theme={null} # Run as root — cleans the retry database every Sunday at midnight 0 0 * * 0 /usr/sbin/exim_tidydb -t 1d /var/spool/exim retry > /dev/null 2>&1 ``` ### Queue runner frequency For `systemd`-based systems, create a service override to run the queue every 60 seconds: ```bash theme={null} sudo systemctl edit exim.service ``` Add the following to the override file: ```ini systemd service override theme={null} [Service] ExecStart= ExecStart=/usr/sbin/exim -bd -q60s ``` For older `init.d` systems, edit `/etc/default/exim` (Debian/Ubuntu) or `/etc/sysconfig/exim` (CentOS/RHEL/CloudLinux): ```bash theme={null} QUEUE=60s ``` ### Queue runner limits and frozen messages Add or modify these in the `main` section of `exim.conf`: ```exim Main section — queue settings theme={null} # Limit simultaneous queue runner processes queue_run_max = 50 # Cancel frozen messages after 12 hours timeout_frozen_after = 12h # Discard undeliverable bounce messages after 1 hour ignore_bounce_errors_after = 1h ``` *** ## Verify the setup After restarting Exim, confirm that mail is routing correctly. 1. **Monitor the mail log.** Tail the main log and look for entries showing `R=send_via_mailchannels` and `T=mailchannels_smtp` with a destination of `smtp.mailchannels.net`. ```bash theme={null} tail -f /var/log/exim_mainlog # or on Debian/Ubuntu: tail -f /var/log/exim4/mainlog ``` 2. **Send test emails.** Send from several local accounts to external recipients and verify successful delivery. 3. **Check received headers.** Examine the headers of delivered messages to confirm `smtp.mailchannels.net` appears in the `Received:` chain and that the `X-AuthUser` header is present with the correct value. 4. **Review the MailChannels console.** Log in to your MailChannels Host Console to confirm that messages from your server appear in Outbound > Activity > LogSearch and that sender attribution is correct. # Setup MailEnable Source: https://docs.mailchannels.com/outbound/mta/setup-mailenable Configure MailEnable to relay outbound mail through MailChannels Outbound Filtering using smart host settings, SASL authentication, and sender identification headers. ## Prerequisites * Supported Versions: MailEnable v8+ * Launch the MailEnable Administration Console from your server * You should see a hierarchical view on the left-hand side * An active subscription to MailChannels and your SMTP Username & Password[(Host Console)](https://console.mailchannels.net) ## Step 1: Access MailEnable Administration Console ### 1. Navigate to SMTP Properties: * In the left-hand tree, navigate to `Servers` > `localhost` > `Connectors` > `SMTP` * Right-click on `SMTP` and select `Properties` ## Step 2: Configure SMTP Connector ### 1. Setup the Smarthost: * Click on the `Smart Host` tab Selection_052.jpg * Check the box for `Smart Host Enabled` * Enter `smtp.mailchannels.net` in the `IP/Domain:` field. * Set the `Port` to `587`. *(25 & 2525 available if needed)* * Check the box for `The remote server requires authentication`. * Enter your MailChannels SMTP Username and Password into the `Account:` and `Password:` fields * `Apply` and `OK` your changes ## Step 3: Add Authentication Headers * Click on the `Advanced SMTP` tab * Check the box under Header Fixing for `Add Required Headers for Authenticated Senders if needed` * `Apply` and `OK` your changes ## Step 4: Configure DNS Records * Verify that your domain’s DNS settings include the correct MX records pointing to your MailEnable server * Update or add [SPF](https://support.mailchannels.com/hc/en-us/articles/200262610-Set-up-SPF-Records), [DKIM](https://www.mailenable.com/documentation/10.0/Standard/DKIM.html), [Domain Lockdown](https://support.mailchannels.com/hc/en-us/articles/16918954360845-Secure-your-domain-name-against-spoofing-with-Domain-Lockdown) and [DMARC](https://support.mailchannels.com/hc/en-us/articles/360045526931-How-does-MailChannels-enforce-DMARC-policy) records as recommended by MailChannels. *(SPF and Domain Lockdown records are required before relaying message traffic through MailChannels and should be in place before you perform any test sending. DKIM and Dmarc are highly recommended before going live)* ## Step 5: Send a Test Message ### 1. Send a Test Email: * Use any email client to send a test email from a mailbox hosted on your MailEnable server * Verify if the email is successfully delivered without errors * Check the `Outbound` > `Activity` > `LogSearch` area of your [Host Console](https://console.mailchannels.net/) for status, response codes and other useful details about your message traffic ### 2. Check Logs for Errors: * If the email fails to send, check the SMTP logs * Logs are located in `Program Files\Mail Enable\Logging\SMTP`. * If logs are unclear or you require some additional help, please reach out to our Support Team: [support@mailchannels.com](mailto:support@mailchannels.com) ## Optimizations, Resources and Best Practices * [Official MailEnable SMTP Connector Guide](https://www.mailenable.com/documentation/10.0/Standard/SMTP_Connector.html) # Configure Postfix as a MailChannels outbound relay Source: https://docs.mailchannels.com/outbound/mta/setup-postfix Configure Postfix as a MailChannels outbound relay using SASL authentication, TLS encryption, and smart host routing to smtp.mailchannels.net. Postfix is one of the most widely deployed MTAs on Linux servers. By configuring it to relay through MailChannels, all outbound mail is filtered for spam before delivery, protecting your sending reputation. This guide walks you through setting up the relay host, configuring SASL authentication, adding a sender identification header, and verifying the configuration. Modifying Postfix configuration files can disrupt email delivery if done incorrectly. Back up `/etc/postfix/main.cf` and any related files before making changes. ## Prerequisites * Root or `sudo` access to your Postfix server * Your MailChannels SMTP username and password * Postfix installed and running * The `libsasl2-modules` package (or the equivalent for your OS) installed to support SASL authentication ## Configure the relay Open `/etc/postfix/main.cf` in a text editor and add or update the following lines. These settings tell Postfix to relay all outbound mail through MailChannels and to authenticate with your credentials over TLS. ```ini /etc/postfix/main.cf theme={null} # Relay all non-local mail through MailChannels relayhost = smtp.mailchannels.net:25 # Enable SASL authentication for the SMTP client smtp_sasl_auth_enable = yes smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd smtp_sasl_security_options = noanonymous # Enable TLS encryption (required by MailChannels) smtp_use_tls = yes ``` If outbound TCP port 25 is blocked on your network, substitute port `587` or `2525` in the `relayhost` value (for example, `smtp.mailchannels.net:587`). Create `/etc/postfix/sasl_passwd` and add the following line, replacing the placeholder values with your actual MailChannels credentials. ```text /etc/postfix/sasl_passwd theme={null} smtp.mailchannels.net:25 YOUR_USERNAME:YOUR_PASSWORD ``` If you used an alternate port in `relayhost`, use the same host-and-port combination here — for example, `smtp.mailchannels.net:587 YOUR_USERNAME:YOUR_PASSWORD`. Run `postmap` to compile the password file into a Berkeley database that Postfix can query at runtime. ```bash theme={null} sudo postmap /etc/postfix/sasl_passwd ``` Limit read access to the password file and its compiled database to the root user only. ```bash theme={null} sudo chown root:root /etc/postfix/sasl_passwd /etc/postfix/sasl_passwd.db sudo chmod 600 /etc/postfix/sasl_passwd /etc/postfix/sasl_passwd.db ``` MailChannels uses the `X-AuthUser` header to track which authenticated user or script sent a message. This is important for abuse mitigation and sender reputation management. First, enable header checks in `main.cf`: ```ini /etc/postfix/main.cf theme={null} header_checks = regexp:/etc/postfix/header_checks ``` Then create `/etc/postfix/header_checks` with the following content. The regular expression prepends the `X-AuthUser` header using the SASL-authenticated username whenever a `From:` line is detected. ```text /etc/postfix/header_checks theme={null} /^From:/ PREPEND X-AuthUser: ${sasl_username} ``` This works when `${sasl_username}` is populated — typically for mail submitted over port 587 with SASL authentication. For local scripts that inject mail without authenticating, you may need a milter or a custom submission wrapper to populate the header reliably. Validate the configuration for syntax errors, then reload Postfix to apply all changes. ```bash theme={null} sudo postfix check sudo postfix reload ``` If `reload` is not sufficient (for example, after major structural changes), perform a full restart instead. ```bash theme={null} sudo systemctl restart postfix ``` ## Verify the configuration After reloading Postfix, confirm that mail is flowing through MailChannels correctly. 1. **Send test emails.** Send messages from an address on your server to a few external destinations (Gmail, Outlook, and so on). 2. **Check your mail logs.** Look at `/var/log/mail.log` or `/var/log/maillog` (or use `journalctl -u postfix`) for lines like `relay=smtp.mailchannels.net` and `status=sent`. Authentication errors or TLS failures will also appear here. 3. **Review the MailChannels console.** Log in to your MailChannels Host Console and go to **Outbound > Activity > LogSearch** to confirm your test messages appear and are being processed. The third field of the `senderID` (or `sid=`) header reflects the value you placed in `X-AuthUser`. ## Optional: domain-specific routing If you want to relay only certain sender domains through MailChannels — or exclude specific domains — you can use `sender_dependent_relayhost_maps` instead of a global `relayhost`. Comment out or remove the global `relayhost` line and add the following: ```ini /etc/postfix/main.cf theme={null} # sender_dependent_relayhost_maps replaces the global relayhost setting sender_dependent_relayhost_maps = hash:/etc/postfix/sender_relay ``` Create `/etc/postfix/sender_relay`. Use square-bracket notation around the host to prevent MX lookups. ```text Relay specific domains only theme={null} # Only these domains relay through MailChannels example.com [smtp.mailchannels.net]:25 another-domain.net [smtp.mailchannels.net]:25 ``` ```text Exclude a domain, relay everything else theme={null} # Exclude one domain; route all others through MailChannels exclude-this.com DUNNO @ [smtp.mailchannels.net]:25 ``` ```bash theme={null} sudo postmap /etc/postfix/sender_relay sudo postfix reload ``` ## Optional: DKIM signing Postfix does not sign DKIM natively. To sign outbound mail before it is handed off to MailChannels, run a signing milter such as [OpenDKIM](http://www.opendkim.org/) alongside Postfix. MailChannels does not modify the parts of a message covered by a DKIM signature, so signatures added before relay remain valid on delivery. Install OpenDKIM from your distribution's package repository, generate a key pair for each sending domain, and configure `/etc/opendkim.conf` with your selector, domain, and key file location. See the [OpenDKIM documentation](http://www.opendkim.org/docs.html) for full instructions. Publish the public key as a TXT record at `._domainkey.` in your DNS. OpenDKIM writes the record to a `.txt` file next to the generated key. Add the milter socket to `/etc/postfix/main.cf`, then reload Postfix. Adjust the socket path to match the `Socket` setting in your `opendkim.conf`. ```ini /etc/postfix/main.cf theme={null} smtpd_milters = inet:localhost:8891 non_smtpd_milters = inet:localhost:8891 milter_default_action = accept ``` ```bash theme={null} sudo postfix reload ``` Send a test message to an external mailbox and inspect the received headers. A successful signature produces an `Authentication-Results` header containing `dkim=pass` and a `DKIM-Signature` header with your selector and domain. MailChannels' managed DKIM signing is only available when sending through the [Email API](/email-api/configuring-dkim). It does not apply to SMTP relay traffic through `smtp.mailchannels.net`, so Postfix installations need to sign locally with a milter. ## Optional: queue lifetime tuning Adjust how long Postfix holds messages in the queue before bouncing them. Add or modify these parameters in `main.cf`, then run `sudo postfix reload`. ```ini /etc/postfix/main.cf theme={null} # Maximum time a message stays in the queue before bouncing (default: 5d) maximal_queue_lifetime = 2d # Maximum time a bounce notification stays in the queue (default: 5d) bounce_queue_lifetime = 1d ``` # Setup Oracle Hospitality Suite8 Source: https://docs.mailchannels.com/outbound/mta/setup-suite8 Configure Oracle Hospitality Suite8 (formerly Fidelio Suite 8) to relay outbound mail through MailChannels Outbound Filtering using SMTP authentication and smart host settings. This guide walks you through configuring Oracle Hospitality Suite8 (formerly Fidelio Suite 8) to relay all outbound mail through MailChannels Outbound Filtering. ## Prerequisites * Administrative access to the Suite8 server * Your MailChannels SMTP username and password * Outbound firewall access to `smtp.mailchannels.net` on port `587` or `465` ## Configure the relay Right-click the **Fidelio Server** icon in the system tray and select **Properties** to open the Fidelio Server configuration window. In the configuration window, select the **Mail Server** tab and click **Setup** to open the Mail Server Setup dialog. Enter the following values in the Mail Server Setup dialog: | Field | Value | | - | - | | **SMTP Server** | `smtp.mailchannels.net` | | **Port** | `587` (TLS) or `465` (SSL) | | **Username** | Your MailChannels SMTP username | | **Password** | Your MailChannels SMTP password | | **SSL** | Enabled | Click **OK** to save the Mail Server settings, then click **Close** to exit the Fidelio Server configuration window. ## Verify the configuration Send a test email from Suite8 to an address you control and confirm it is delivered successfully. Review `fideliosmtplog.txt` in the Suite8 log directory for any errors or status messages related to email sending. # Setup Zimbra Source: https://docs.mailchannels.com/outbound/mta/setup-zimbra Configure Zimbra to relay outbound mail through MailChannels using SMTP authentication, smart host routing, and the X-AuthUser sender header. This guide walks you through configuring Zimbra to relay all outbound mail through MailChannels Outbound Filtering and adding the `X-AuthUser` header for per-sender reputation tracking. ## Prerequisites * Root or administrative access to your Zimbra server * Your MailChannels SMTP username and password ## Configure the relay Navigate to `https://:7071` and log in with your admin credentials. Go to **Configure** > **Global Settings** and select the **MTA** tab. Configure the following values: | Field | Value | | - | - | | **Relay MTA for external delivery** | `smtp.mailchannels.net` | | **SMTP Port** | `587` | | **TLS encryption** | Enabled | | **Relay Authentication Username** | Your MailChannels SMTP username | | **Relay Authentication Password** | Your MailChannels SMTP password | Click **Save**. ## Add the X-AuthUser header MailChannels uses the `X-AuthUser` header to identify the authenticated sender and track per-sender reputation. Messages without this header are treated as forwarded mail and subject to different policies. Zimbra uses Postfix as its MTA, so the header is added via a Postfix sender access map. Open `/opt/zimbra/conf/main.cf`: ```bash theme={null} sudo nano /opt/zimbra/conf/main.cf ``` Add the following line to include the custom header access map: ```text /opt/zimbra/conf/main.cf theme={null} smtpd_sender_restrictions = check_sender_access hash:/opt/zimbra/conf/custom_header ``` Create `/opt/zimbra/conf/custom_header`: ```bash theme={null} sudo nano /opt/zimbra/conf/custom_header ``` Add the following line: ```text /opt/zimbra/conf/custom_header theme={null} PREPEND X-AuthUser: ${authenticated_user} ``` Postfix requires a compiled hash database from the text file: ```bash theme={null} sudo postmap /opt/zimbra/conf/custom_header ``` Apply all changes by restarting the Zimbra MTA service: ```bash theme={null} sudo /opt/zimbra/libexec/zmmtactl restart ``` ## Resources * [Zimbra Admin Guide — Zimbra Mail Transfer Agent](https://zimbra.github.io/documentation/zimbra-10/adminguide.html#_zimbra_mail_transfer_agent) ## Verify the configuration Send a test email from a Zimbra user account to an address you control and confirm it is delivered successfully. Inspect the headers of the received email and confirm that `X-AuthUser` is present and correctly populated with the authenticated user value. Log in to your [MailChannels Host Console](https://console.mailchannels.net) and navigate to **Outbound > Activity > Log Search**. In the transaction log, the `senderID` (`sid=`) field should contain your `${authenticated_user}` value in the third position. # MailChannels Outbound Filtering: SMTP relay overview Source: https://docs.mailchannels.com/outbound/overview MailChannels Outbound Filtering is an SMTP relay that accepts all your email, filters spam, and detects compromised senders to protect your reputation. MailChannels Outbound Filtering is an SMTP relay service that identifies spammers within your network so you can protect your email reputation and keep delivery reliable for every user. Unlike conventional email relay services, Outbound Filtering accepts 100% of your outbound email — including spam — and then filters it. This approach means you never lose visibility into what your senders are doing, even when something goes wrong. ## How it works Outbound Filtering works like a credit score for email senders. Every user, application, or script that sends mail through MailChannels builds a reputation score over time. The system tracks each sender individually, so a compromised WordPress plugin or a single hijacked user account can be isolated and shut down without affecting everyone else on your server. When you point your mail server at MailChannels as a smart host, every outbound message passes through the filter before it reaches the recipient. MailChannels delivers the message, removes spam, and manages IP blocklisting on your behalf. ## Compromised account detection Spammers constantly search for new resources to exploit — through phishing attacks, malware, and web application vulnerabilities. Because you cannot reliably prevent every compromise, Outbound Filtering watches the email these compromised accounts generate and shuts them down automatically. The system uses several techniques to identify compromised senders: * **Spam signature matching** — Each message is checked against a real-time database of known spam signatures. A sudden uptick in matched messages can trigger rate-limiting or blocking for that sender. * **Volume and behavioral statistics** — MailChannels tracks dozens of per-minute statistics for every sender: message volume, suspicious content patterns, recipient validation failures, and more. Unusual trends surface spammers before significant damage occurs. * **External reputation databases** — The system consults domain and IP reputation services to flag messages connected to known spam operations. * **Receiver feedback analysis** — MailChannels monitors responses from recipient mail servers. Negative feedback can identify a compromised sender even before other signals appear. ## Notifications When Outbound Filtering detects a compromised sender, the message is rejected automatically during the SMTP transaction. The sender receives a non-delivery report containing an error code that can be used for troubleshooting. If you have configured Monitors, a notification is also sent to alert you — so you can investigate and remediate the root cause, for example by disabling a user account or deactivating a vulnerable plugin. Notifications can be delivered as human-readable emails or as webhook calls. ## Multi-password support You can create multiple SMTP passwords for a single account — one per server if you choose. If a server is compromised, you retire its password with a single click without disrupting any other servers. ## Multi-user support Outbound Filtering supports unlimited web console logins. You can assign regular or administrator access levels, making it straightforward to give your support team read access while reserving administrative controls for senior staff. ## Email management features * **Log Search** — Every delivery attempt is logged. You can search months of logs in seconds through the Host Console to diagnose delivery problems or answer questions from your users. * **Insights** — When a message is rejected, Outbound Filtering generates a non-delivery report (NDR) with a link to an Insights page. Users can review their own sending history and often resolve delivery issues without contacting your support team. ## Domain Lockdown Domain Lockdown is an optional but strongly recommended DNS-based security feature. Adding a `_mailchannels` TXT record to your DNS ties your domain to your MailChannels account, preventing other MailChannels customers from sending email that appears to come from your domain. See [Domain Lockdown](/outbound/domain-lockdown) for setup instructions. ## What to do next Point your MTA at MailChannels as a smart host and configure SMTP authentication. Add MailChannels to your SPF record to authorize outbound delivery from your domains. Configure email or webhook notifications for compromised account events. Automate account shutdown by sending real-time alerts to your own systems. Restrict which MailChannels accounts are authorized to send from your domain. # Report false positives and false negatives Source: https://docs.mailchannels.com/outbound/report-false-positives How to report a legitimate message that was blocked (false positive) or spam that was delivered (false negative) to MailChannels. Spam filters are not perfect. When the system makes a mistake in either direction, you can report it so MailChannels can correct the behavior. ## Report a false positive (legitimate mail blocked) A false positive occurs when a legitimate message is blocked by MailChannels. The sender receives a non-delivery report (NDR) with an error message. For self-service remediation, see [Unblock senders](/outbound/unblock-senders). To submit a false positive report directly to support, email [support@mailchannels.com](mailto:support@mailchannels.com) with the following details: * Sender email address * Authenticated username of the sender (for example, the `X-AuthUser` value) * Recipient email address * Full error message from the NDR * Copy of any compromised account notification, if received Include as much detail as possible to help the team resolve the issue quickly. If you have the raw message, attach it as an [EML file](/outbound/create-eml-file). ## Report a false negative (spam delivered) A false negative occurs when a spam message passes through MailChannels and is delivered to the recipient. Forward the spam message as an attachment to: **[spam@feedback.mailchannels.net](mailto:spam@feedback.mailchannels.net)** MailChannels also accepts abuse reports at the standard address: **[abuse@mailchannels.com](mailto:abuse@mailchannels.com)** Both addresses automatically create a case with the Support team. The attached message is processed to improve detection for similar campaigns. # Sender Profiles Source: https://docs.mailchannels.com/outbound/sender-profiles Use Sender Profiles in MailChannels Outbound Filtering to view per-sender message history, alert notifications, and ham-to-spam ratios. From **Top Senders** or from **Log Search**, click the sender profile icon icon next to a sender to open their **Sender Profile**. A Sender Profile shows every message that sender has submitted, any alert notifications generated for that account, and a graphical ham-to-spam ratio — all in one place. Sender Profiles ## Deliverability chart The top section of a Sender Profile displays a **Deliverability** chart for the selected sender, showing a breakdown of: * **Delivered** — messages successfully delivered to the recipient. * **Spam** — messages identified as spam. * **Rejected** — messages rejected during the SMTP transaction. * **Sent** — total messages submitted by this sender. * **Alerts** — the number of monitor alerts triggered for this sender. The chart is plotted over time and can be viewed by **Hours**, **Days**, or **Weeks** using the selector in the top-right corner. Use the date navigation arrows to move backwards or forwards through the selected time range. ## Alerts and Sender Logs Below the chart, the profile is split into two tabs: * **Alerts** — lists any monitor alerts triggered for this sender within the selected timeframe. Use the **All Alerts** dropdown to filter by alert type. * **Sender Logs** — lists individual messages submitted by this sender, including the timestamp, delivery status, sender address, recipient address, and subject line. # Fix MailChannels outbound SMTP bounce errors Source: https://docs.mailchannels.com/outbound/smtp-errors Diagnose and resolve 550 5.7.1 bounce codes from MailChannels Outbound Filtering, including spam, blocklist, and sender block errors. When MailChannels Outbound Filtering rejects a message, your mail server returns a non-delivery report (NDR) containing a `550 5.7.1` bounce code with a bracketed tag — for example, `[SS]`, `[RBL]`, or `[ESA]`. Each tag identifies the specific reason your message was blocked. This page explains what each code means and the steps you can take to resolve it. The `[SS]` code means MailChannels detected that your message matches a known spam signature. Common triggers include subject lines or body content containing pharmaceutical terms, prescription medication names, lewd or obscene language, or other keywords strongly associated with unsolicited email. Because this is a `5xx` permanent failure, your message was not delivered and will not be retried automatically. **If you are the sender:** 1. Review your message content — subject line, body text, images, and any embedded links — for keywords or patterns commonly used in spam. 2. Check that any URLs in your message point to reputable domains with no history of spam or phishing. 3. Change your account password to a strong passphrase (at least eight characters, mixing uppercase, lowercase, numbers, and special characters). 4. Verify that your operating system has the latest security updates applied. 5. Run a full antivirus and malware scan to confirm that no malware is sending email on your behalf. **If you are a service provider and believe this is a false positive:** First confirm the sending account is not compromised before requesting a review. See [Report false positives and false negatives](/outbound/report-false-positives) for instructions on submitting a report. The `[RBL]` code means the IP address or domain used to send your message appears on a reputable global blocklist — an indication that the sending infrastructure has been associated with high-volume spam activity. This can happen because of compromised accounts on a shared server, or because the domain owner has a history of sending abusive mail. **To diagnose the issue:** Check the IP address and domain listed in the bounce message against public blocklist lookup tools: * [MXToolbox Blacklist Check](https://mxtoolbox.com/blacklists.aspx) — tests against many common RBLs simultaneously. * [Spamhaus Domain Lookup](https://www.spamhaus.org/lookup/) — checks whether your domain is listed by Spamhaus. **To resolve the issue:** 1. Identify which blocklist has listed the IP or domain. 2. Follow that blocklist's delisting process. For Spamhaus, use the lookup tool linked above to request removal. 3. Secure the sending account: change passwords, apply OS security updates, and run a full malware scan. 4. Contact your hosting provider to confirm that other accounts on the same server are not sending spam. Even after a successful delisting request, propagation to all receivers can take time. If mail continues to bounce after delisting, contact your hosting provider. The `[CS]` code means MailChannels' content filter determined that the body of your message contains spam-like or malicious content. Blocked content typically includes malware, phishing language, known-bad URLs, or patterns associated with unsolicited bulk mail. **If you are the sender:** File a support request with your hosting provider and include the full bounce message you received. **If you are a service provider and believe this is a false positive:** You can remediate a `[CS]` block directly from your Host Console without opening a support ticket: 1. Log in to the [Host Console](https://console.mailchannels.net) and navigate to **Outbound > Log Search**. 2. Locate the blocked message using the search bar or advanced filters. 3. Click **Not Spam** on the message details line to immediately clear the spam classification. Alternatively, click the Flag icon to open the **Report an Issue** dialog, describe the false positive, and submit a support ticket. You can also send the Insights bounce link to the sender so they can click **Not Spam** directly. For full instructions on submitting a false positive report, see [Report false positives and false negatives](/outbound/report-false-positives). The `[IRR]` code means the sending account attempted to deliver email to an unusually high number of invalid (non-existent) recipient addresses. Sending to a large number of bad addresses is a strong indicator of spam activity and damages the IP reputation of your sending infrastructure. MailChannels temporarily blocks the account to limit the reputational impact while you clean up. **What causes this:** * Stale mailing lists that have not been purged of bounced or lapsed addresses * A compromised account being used by a spammer to blast a "dirty" list with many invented addresses * Contact lists that were never confirmed through an opt-in process **How to fix it:** 1. Go through your bounce reports (NDRs) and remove every address that returned a permanent delivery failure. 2. Use an address verification service to validate your list before the next send. 3. Once the rate of invalid recipients drops, MailChannels will automatically lift the temporary block — no manual intervention is required. **Best practices to avoid recurrence:** * Use double opt-in (confirmed subscription) when adding contacts to your list. This guarantees each address is valid and the owner consented. * Remove invalid addresses from your list immediately after receiving a bounce. * Only send to subscribers who explicitly opted in. Contacts who did not opt in are more likely to mark your email as spam, which can trigger additional blocks. If you did not send to a mailing list recently and still received this error, your account may have been compromised. Contact your hosting provider, change your password immediately, and run a complete malware scan. The `[FBL]` code means one or more recipients clicked the **Report Spam** (or **Junk**) button on a message you sent. MailChannels participates in feedback loops operated by major email providers — including Microsoft Outlook/Hotmail, Gmail, and AOL Mail — which route these complaints back to the sending infrastructure. When complaint rates exceed an acceptable threshold, MailChannels automatically blocks the sender. **Why this can happen to legitimate senders:** Recipients sometimes click **Report Spam** instead of **Delete** out of convenience, even for messages they originally requested. This means you can receive an FBL block even if you believe you sent nothing wrong. **Actions to take:** * Review your sending practices against the [M3AAWG Sender Best Common Practices](https://www.m3aawg.org/sites/default/files/legacy/m3aawg-sendingdomains10102019nk-2.pdf). * Only send messages that recipients would reasonably expect to receive. * Do not resume contact with subscribers after many months of silence without a re-engagement campaign. * Keep the purpose of each message clear and concise. * Place unsubscribe links prominently at both the top and bottom of every message. **How long will the block last?** MailChannels uses an adaptive approach. Once complaint rates drop back to acceptable levels, the block lifts automatically. MailChannels does not generally accept requests to expedite the removal of an FBL block. MailChannels cannot reveal which recipients filed complaints. Disclosing that information would allow spammers to identify and remove complainers from their lists, undermining the feedback loop system. The `[ESA]` code is the most serious sender-level block. It is issued after a sending account accumulates multiple temporary blocks and continues to generate problematic traffic. Unlike ratio-based blocks that lift automatically, an ESA block is permanent and must be manually removed before the account can send again. Common causes include sustained spam activity, high complaint rates, large numbers of invalid recipients, or other repeated abuse from the same sending account. **If you are the end user:** Secure your account before anything else: 1. Change your password to a strong, unique passphrase (minimum eight characters, mixing upper and lowercase letters, numbers, and special characters). 2. Apply all pending operating system security updates. 3. Run a full antivirus and malware scan to eliminate any malware that may have been sending spam without your knowledge. After securing your account, file a support request with your hosting provider so they can initiate the unblock process on your behalf. **If you are an administrator:** You can delist a sender blocked with ESA directly from the Host Console without contacting MailChannels Support: 1. Log in to the [Host Console](https://console.mailchannels.net) and navigate to **Outbound > Log Search**. 2. Locate the blocked message for the affected sender. 3. Click the **Flag** icon to open the **Report an Issue** panel. 4. Click **Delist Sender** to immediately remove the block. Do not delist the sender until you have confirmed the account is secured and is no longer sending spam. Delisting a compromised account without fixing the root cause will result in the account being blocked again. Automatic self-service delisting is limited to once every 30 days per account. You can also receive ESA block notifications by email. Enable **Receive Notifications** in your Host Console settings. Each notification includes a direct link to unblock the account, and you can automate the unblock step by POSTing to that URL programmatically. The ESA block severity reflects the cumulative abuse level (Security Levels table below) observed from the sending account, and determines the urgency with which the issue should be investigated. The `[NS]` code appears in bounces such as `550 5.7.1 [NS] Our system has detected an unusual amount of unsolicited email originating from your address.` If you are receiving this error, it means MailChannels has detected possible abuse originating from your email account. **To resolve the issue:** Ensure that all of your mails have subject lines. Sending excessive emails without a subject line is not best practice. It is also possible that your account has been compromised. Secure your account against compromise by performing the following: 1. Change your password. Ensure your new password has a minimum length of eight characters and uses a mixture of upper and lower case letters, numbers, and special characters. 2. Verify that your operating system is running with the latest security updates. 3. Update your anti-virus software and conduct a full scan of your system to ensure that a spammer has not installed spam-sending malware on your computer. If these steps do not resolve the problem, please file a support request with your web hosting provider. The `[STF]` code means the sending account exceeded its allowed send rate. MailChannels throttles bursts that are much larger than the account's normal pattern to protect shared IP reputation, especially on small and free plans. **If you are the sender:** 1. Spread the send out over a longer window instead of releasing the full batch at once. 2. Retry the deferred messages after a short delay. `[STF]` throttling is temporary, not a permanent block. 3. If you have a legitimate use case that requires a sustained high burst rate, contact MailChannels in advance so the account can be reviewed. See [Sending policies and best practices](/email-api/sending-policies). If bursts appear unexpectedly, treat the account as potentially compromised: change the password, apply operating system security updates, and run a full malware scan before resuming. The `[FEB]` code means your message contained an attachment with a file type that MailChannels blocks to protect recipients from malware, phishing, and other security risks. This policy applies to both inbound and outbound mail and is consistent with the attachment restrictions enforced by Gmail, Microsoft Outlook/Exchange, Yahoo Mail, AOL, and other major providers. **Blocked file types include:** | Category | Extensions | | - | - | | Executables and installers | `exe`, `msi`, `appx`, `appxbundle`, `apk`, `dmg`, `cab` | | Scripts and macros | `js`, `mjs`, `jse`, `vbs`, `vbe`, `vb`, `ps1`, `wsf`, `wsh`, `wsc`, `bat`, `cmd`, `sct` | | Shortcuts and control files | `lnk`, `cpl`, `msc`, `pif`, `shb` | | Virtual disks and images | `iso`, `img`, `vhd` | | Java-related files | `jar`, `jnlp` | | Diagnostic and config packages | `diagcab`, `diagcfg`, `diagpkg` | | Libraries and system files | `dll`, `sys`, `lib`, `xll`, `vxd` | | Other high-risk types | `ade`, `adp`, `chm`, `com`, `ex`, `ex_`, `hta`, `ins`, `isp`, `mde`, `msp`, `mst`, `nsh`, `scr` | Archives (ZIP, RAR, and similar) may also be blocked if they contain any of the above file types. **What to do instead:** * Share files using an approved file-sharing or cloud storage service. Restrict access to the intended recipients, set expiration dates on shared links, and avoid public links unless necessary. * Share code samples or scripts as plain text snippets in a developer tool, repository, or collaboration platform rather than as email attachments. * Do not attempt to bypass the block by renaming the file extension or using weak archive encryption. These methods are not reliable and may result in additional filtering action. No exceptions to the attachment block policy are possible. If you believe a file was misclassified, contact support with the original file name, timestamp, sender and recipient addresses, and the full bounce message text. ## Bounce codes not listed above If your non-delivery report contains a `550 5.7.1` bounce with a bracketed tag that is not documented above (for example, `[XM]`, `[SHF]`, or `[ZLQ]`), the message was rejected by a content, header, or policy control that is not covered on this page. Take the following steps to identify and resolve the block: 1. Capture the full bounce text, the sending account, the recipient address, and the timestamp. 2. If you are a hosting provider or administrator, log in to the [Host Console](https://console.mailchannels.net) and go to **Outbound > Log Search**. Locate the message using the sender, recipient, or timestamp captured above. The log entry shows the full block reason and lets you delist the sender or mark the message as not spam. See [Unblock senders](/outbound/unblock-senders) for the delisting steps. 3. If you send through the Email API, review [Sending policies and best practices](/email-api/sending-policies) to identify which control may have fired. 4. If you are an end user, forward the full bounce message to your hosting provider so they can investigate in the Host Console. 5. If the rejection is unexpected, [report it as a false positive](/outbound/report-false-positives) so MailChannels can review the message. ## Reporting misclassifications If a legitimate message was blocked, or if spam was delivered, see [Report false positives and false negatives](/outbound/report-false-positives) for instructions on how to submit a report to MailChannels. # SMTP ports Source: https://docs.mailchannels.com/outbound/smtp-ports MailChannels Outbound Filtering supports connections on ports 25, 587, 2525, and 465. Use an alternative port if port 25 is blocked by your provider. MailChannels Outbound Filtering listens on the following ports. All ports support STARTTLS encryption. | Port | Description | | - | - | | `25` | Standard SMTP. Preferred. Sometimes blocked by hosting providers. | | `587` | Standard authenticated SMTP. Requires authentication. Use this if port 25 is blocked. | | `2525` | Alternative to port 25. Commonly used on cloud platforms such as Azure that block port 25 on VMs. | | `465` | Legacy SMTP over SSL. Supported for older clients that cannot use STARTTLS. Not recommended for new configurations — see [RFC 8314](https://datatracker.ietf.org/doc/html/rfc8314#section-7.3). | Port 465 was briefly registered as `smtps` but the registration was revoked and the port reassigned. MailChannels continues to support it for legacy clients only. If port 25 is blocked in your environment, use port **587** or **2525**. Both are configured identically to port 25. # Why doesn't MailChannels catch all spam? Source: https://docs.mailchannels.com/outbound/spam-filtering-accuracy Why no spam filter catches 100% of spam, and how MailChannels balances detection rate against false positives. MailChannels Outbound Filtering uses a combination of content analysis, domain and URL reputation checks, and behavioral signals to detect spam. Despite this, no filtering system catches 100% of spam without ever blocking legitimate mail. Here's why. ## The fundamental trade-off Spam filtering is a classification problem: every message is either spam or not spam, and the filter must decide. The challenge is that the cost of each type of error is very different: * **False negative** (spam delivered) — the recipient receives an unwanted message. Annoying, but recoverable. * **False positive** (legitimate mail blocked) — a message someone needed never arrives. This can cause real harm — a missed invoice, a lost job opportunity, a failed account verification. Because false positives are so damaging, MailChannels tunes its system to minimize them, even at the cost of letting some spam through. A filter aggressive enough to block every piece of spam would inevitably block too much legitimate mail. ## Why spammers have the advantage When a spam campaign is blocked, the spammer can simply adjust the message and try again. The cost of sending another attempt is nearly zero. For the filter, however, every tuning change carries the risk of introducing new false positives. This asymmetry means spam filters must be conservative. ## What MailChannels does to maximize accuracy * **Content similarity matching** — compares messages against known spam campaigns * **URL and domain reputation** — checks links against blocklists of known malicious domains * **Behavioral analysis** — monitors dozens of sending patterns across all accounts to detect abuse signals ## What to do if spam gets through If you see spam being delivered from MailChannels IP addresses, report it. Forwarding the message helps improve detection for the entire platform. See [Report false positives and false negatives](/outbound/report-false-positives) for instructions. # Set up SPF records for MailChannels Outbound Filtering Source: https://docs.mailchannels.com/outbound/spf-records Add MailChannels to your domain's SPF record to authorize outbound delivery, prevent spoofing, and ensure recipient servers accept your email. Sender Policy Framework (SPF) tells receiving mail servers which IP addresses are authorized to send email for your domain. When you relay through MailChannels, messages leave from MailChannels' shared IP pool — not your own servers. Without an SPF record authorizing those addresses, recipients may reject your mail or mark it as spam, and your domain becomes easier to spoof. To authorize MailChannels, add a single `include` reference to your existing SPF record. MailChannels maintains its own SPF record, so it always reflects the current sending addresses — you don't need to update anything when IPs change. Migrating customer domains in bulk? [Audit customer SPF records](/outbound/migration-center/prepare-customer-dns) to group domains by nameserver and identify required, optional, and manual-review changes. ## Required DNS records You need two DNS records for full SPF coverage with MailChannels. ### SPF record (for your sending domain) Add or update the TXT record for your domain: | Location | Type | Value | | - | - | - | | `example.com` | TXT | `v=spf1 a mx include:relay.mailchannels.net ~all` | Replace `example.com` with your actual domain name. If you already have an SPF record, add `include:relay.mailchannels.net` to it — do not create a second SPF record. Only one SPF TXT record is permitted per domain. Place the `include` before the `all` mechanism, since `all` always matches and must appear at the end. **Example — adding MailChannels to an existing SPF record:** ```txt Before theme={null} v=spf1 a mx include:sendgrid.net ~all ``` ```txt After theme={null} v=spf1 a mx include:sendgrid.net include:relay.mailchannels.net ~all ``` ### Domain Lockdown record (strongly recommended) Including the MailChannels SPF record without also adding the Domain Lockdown TXT record makes it easier for other MailChannels customers to send email that appears to come from your domain. Always add the `_mailchannels` subdomain record. | Location | Type | Value | | - | - | - | | `_mailchannels.example.com` | TXT | `v=mc1 auth=` | Replace `` with the Account ID shown in your [Host Console](https://console.mailchannels.net). This record tells MailChannels that only your account is authorized to send from your domain, locking out any other MailChannels account from spoofing it. If you cannot configure Domain Lockdown right away, you can use a neutral qualifier as a temporary measure: ```txt theme={null} v=spf1 a mx ?include:relay.mailchannels.net ~all ``` A `?` qualifier produces a neutral SPF result. This allows messages from MailChannels to be accepted by hosts that would otherwise reject on SPF failure, but it does not help with DMARC alignment (which will depend on DKIM only until Domain Lockdown is configured). ## Verify your SPF record After updating your DNS, confirm the record is correct using `dig` or `nslookup`: ```bash dig theme={null} dig TXT example.com +short ``` ```bash nslookup theme={null} nslookup -type=TXT example.com ``` You should see your SPF record in the output. Look for the `include:relay.mailchannels.net` entry. To verify the Domain Lockdown record: ```bash theme={null} dig TXT _mailchannels.example.com +short ``` You can also use online tools to inspect the full expanded list of MailChannels IP addresses: * [MXToolbox SPF lookup for MailChannels](https://mxtoolbox.com/SuperTool.aspx?action=spf%3arelay.mailchannels.net\&run=toolpage) * [Dmarcian SPF survey for MailChannels](https://dmarcian.com/spf-survey/relay.mailchannels.net) DNS changes can take up to 48 hours to propagate, though most resolvers pick up changes within minutes to a few hours. ## Common SPF mistakes **Creating two SPF records for the same domain** — Only one `v=spf1` TXT record is allowed per domain. If you add a second one, both records become invalid. Edit your existing record instead of creating a new one. **Placing `include:relay.mailchannels.net` after `all`** — The `all` mechanism matches everything, so any mechanisms after it are ignored. Always put `include` statements before the `all` at the end. **Exceeding the 10 DNS lookup limit** — SPF records are limited to 10 DNS lookups during evaluation. If you have many `include` statements, you may hit this limit. Use an SPF flattening tool to consolidate lookups if needed. **Using `-all` (hard fail) without testing first** — A `-all` suffix causes receiving servers to reject messages that don't match. A `~all` (soft fail) is safer during initial setup because it marks non-matching messages rather than rejecting them outright. Switch to `-all` once you are confident your record is complete. ## cPanel bulk SPF setup If you manage many domains in cPanel, you can add SPF records in bulk using the `spf_installer` script from the command line: ```bash theme={null} cd /var/cpanel/users for i in $(ls /var/cpanel/users); do /usr/local/cpanel/bin/spf_installer $i; done ``` The spf\_installer is a built-in cPanel/WHM script that lets system administrators add or update SPF. [cPanel Documentation](https://docs.cpanel.net/whm/scripts/the-spf_installer-script/) Test this script on a single account before running it across all users in a production environment. ## Next step Once your SPF records are in place, configure your mail server to relay through MailChannels: Set up your MTA to use MailChannels as a smart host for outbound delivery. # Top Senders Source: https://docs.mailchannels.com/outbound/top-senders Use the Top Senders view in MailChannels Outbound Filtering to rank senders by volume, spam, or rejections and identify abuse quickly. The **Top Senders** view, available under **Outbound > Activities**, ranks your senders by total volume, detected spam, or rejections. You can pivot the data by IP address, sender ID, or sender domain, and adjust the time range from one hour to a full month. This makes it straightforward to spot when a spam campaign began and estimate the volume of malicious messages involved. Top Senders ## Data types The data type displayed can be toggled between **Total Volume**, **Detected Spam**, and **Rejections** to show a roll-up of your top spammers or potentially compromised accounts, broken down by **IP Address**, **Sender ID**, or **Sender Domain**. ## Time range The timeline can be adjusted from a single hour to an entire month. Expanding the time range helps you visualize the scope of a problem — you can determine when an outbound spam campaign began and see the relative number of malicious messages that were generated and filtered. ## Sender Profiles Sender Profile data is available in both Top Senders and Log Search. Opening a Sender Profile lets you view the behavior patterns of a single sender or sending entity. For more details, see [Sender Profiles](/outbound/sender-profiles). # Unblock senders in MailChannels Outbound Filtering Source: https://docs.mailchannels.com/outbound/unblock-senders How end-users and service providers can remediate false positives, investigate blocked senders in the Host Console, and safely restore sending access. MailChannels Outbound Filtering automatically blocks senders when their message traffic is flagged for confirmed abuse or suspicious activity. In cases of extreme abuse, a manual block is applied that requires explicit action to remove. This guide covers how end-users and service providers can remediate false positives and restore sending access. ## How senders get blocked MailChannels uses two blocking mechanisms: * **Automated blocks** are temporary. They activate when the system observes an unusual rate of spam, complaints, or invalid recipients from a sender, and lift automatically once the problematic traffic subsides. * **Manual blocks** are permanent and require explicit action to remove. They are applied when a sender accumulates repeated temporary blocks or when severe abuse is confirmed. The `[ESA]` bounce code indicates a permanent block of this type. ## For end-users ### Step 1 — Use MailChannels Insights MailChannels Insights includes a customized URL when certain emails are rejected. Open the URL in a browser to load a page with a self-service **Not Spam** button for reporting false positives. Click the Insights link in the NDR bounce message to open the block summary page in your browser. Insights Not Spam button Click **Not Spam**, then click **Confirm** to submit the false positive report. This clears the spam classification and allows the message to be resent. Not Spam confirmation ### Step 2 — Contact your service provider If the Insights page does not include a **Not Spam** button, contact your service provider. They can remediate false positives directly from the Host Console and can enable Insights NDR links on your account if they are not already active. ## For service providers ### Step 1 — Use the Host Console The **Log Search** area of the [Host Console](https://console.mailchannels.net) is the primary tool for locating and remediating blocked senders. Log in to the [Host Console](https://console.mailchannels.net) and navigate to **Outbound > Log Search**. Use the search bar or advanced filters to find messages from the affected sender. Log Search false positive reporting From the log entry, choose the appropriate action based on the block type: * **See Insights bounce page** — click this link under **SMTP Response** to open the Insights page and click **Not Spam** on behalf of the sender. * **Send Insights link** — click the icon to send a fresh Insights link directly to the sender for self-remediation. * **Report an Issue** — click the flag icon to open the **Report an Issue** dialog. Enter a title and description, then click **Submit** to create a support ticket. The MailChannels Support team will contact you to resolve the issue. * **Not Spam** (`[CS]` content spam blocks only) — a **Not Spam** button appears directly on the message details line, allowing immediate remediation without opening a support ticket. Not Spam button for CS blocks * **Delist Sender** (`[ESA]` blocks) — click the flag icon to open **Report an Issue**. When an active `[ESA]` block is present, a **Delist Sender** button appears in the dialog. Click it only after the account has been secured and all spam activity has stopped — delisting before securing the account will result in an immediate re-block. Delist Sender button for ESA blocks After delisting, you can complete the form to open a support ticket if you need additional context on why the block was applied. Do not unblock a sender before securing the account. If the underlying issue is not resolved, the account will be blocked again — and automatic self-service unblocking is limited to once every 30 days per account. ### Step 2 — Contact MailChannels Support Some infractions require additional analysis beyond what the Host Console can resolve. Contact [support@mailchannels.com](mailto:support@mailchannels.com) if: * The Insights bounce page does not include a **Not Spam** button. * The sender has been delisted but is being blocked again and you cannot identify the cause. * You need a detailed breakdown of the abuse events that triggered the block. When contacting support, include the sender email address, the full SMTP error message, and any relevant log details. ## Best practices before unblocking Unblocking a sender without addressing the root cause leads to repeat blocks. Before restoring access, verify the following: * **Password changed** — the sender has set a new, strong password. * **OS updates applied** — the sender's operating system has the latest security patches installed. * **Malware scan completed** — a full antivirus and malware scan has been run and any threats removed. * **Mailing list cleaned** — if the block was triggered by a high invalid-recipient rate (`[IRR]`), invalid addresses have been removed and remaining contacts verified. * **Sending practices reviewed** — if the block was triggered by feedback loop complaints (`[FBL]`), the sender has reviewed their opt-in practices, unsubscribe links, and message content. # Automate compromised account response with webhooks Source: https://docs.mailchannels.com/outbound/webhooks Set up webhook monitors so MailChannels POSTs a JSON alert to your endpoint the moment a compromised or spamming sender is detected on your network. When MailChannels detects a compromised account or a sender actively submitting spam, it can POST a JSON notification to an HTTP or HTTPS endpoint you control. This lets you trigger automated remediation — such as scrambling a password, suspending an account, or disabling a plugin — the moment abuse is detected, without requiring manual intervention. Customers who enable webhooks with automated account actions routinely reduce their total outbound volume by two-thirds, because blocked senders stop submitting messages entirely. Webhooks are a type of Alert Monitor and are available on Standard plan subscriptions and above. ## What MailChannels sends When a monitor condition is met, MailChannels sends an HTTP POST to your endpoint with a JSON payload in the following structure: ```json webhook payload theme={null} { "condition_name": "sender_id_sending_spam", "condition_description": "Sender ID is sending SPAM", "account_id": "myhostingcompany", "timestamp": "2024-08-15T14:23:00Z", "originator": "joe@example.com", "originator_type": "x-authuser", "sender_id": "myhostingcompany|x-authuser|joe@example.com", "ip": "203.0.113.42", "transaction_id": "abc123xyz", "envelope_sender": "joe@example.com", "message_id": "" } ``` | Field | Description | | - | - | | `condition_name` | Machine-readable name of the alert condition that triggered the webhook | | `condition_description` | Human-readable description of the condition | | `account_id` | Your MailChannels account identifier | | `timestamp` | ISO 8601 timestamp of when the alert was generated | | `originator` | The sender hint — typically the authenticated username or email address | | `originator_type` | The type of sender hint (for example, `x-authuser`) | | `sender_id` | The full sender ID string identifying the sender entity | | `ip` | The IP address that submitted the message triggering the alert | | `transaction_id` | The ID of the specific transaction that crossed the threshold | | `envelope_sender` | The envelope `MAIL FROM` address of the triggering message | | `message_id` | The `Message-Id` header value of the triggering message | ## Verify webhook signatures Every webhook we send is cryptographically signed, so your endpoint can confirm the alert genuinely came from us and was not altered or forged in transit. Verify the signature before you act on an alert, so a forged or tampered request cannot trigger an account suspension or password reset on your platform. Verification is optional. We recommend verifying so you can trust an alert before acting on it. Signatures follow the HTTP Message Signatures standard (RFC 9421) using an Ed25519 key, and the request body is covered by a Content-Digest (RFC 9530). Every signed request carries three headers: | Header | Purpose | | - | - | | `Content-Digest` | SHA-256 digest of the raw request body, formatted as `sha-256=::` | | `Signature-Input` | Signature metadata: the covered component (`content-digest`), the algorithm (`ed25519`), the creation time, and the `keyid` | | `Signature` | The Ed25519 signature, under the label `sig1` | The `keyid` in `Signature-Input` tells you which public key to verify against. The current key id is `mcnotifykey`. ### Fetch the public key Retrieve the public key by its id. The endpoint is public and needs no authentication: ```bash fetch the public key theme={null} curl 'https://api.mailchannels.net/notifications/v1/webhook/public-key?id=mcnotifykey' ``` It returns the id and the Ed25519 public key in PEM format: ```json public key response theme={null} { "id": "mcnotifykey", "key": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----\n" } ``` ### Verify a request Read the `keyid` from the `Signature-Input` header and fetch the matching public key. Cache it rather than fetching on every request, and refresh it periodically so you pick up a future key rotation. Recompute the SHA-256 digest of the raw request body and confirm it matches the `Content-Digest` header. This proves the body was not modified. Rebuild the RFC 9421 signature base from the covered `content-digest` component and the signature parameters, then verify the `Signature` against it with the Ed25519 public key. Reject the request if verification fails. We sign Email API webhooks with the same RFC 9421 scheme, so you can adapt the code in our [Email API webhook verification examples](/email-api/webhooks). Point it at the public key endpoint above and use the `mcnotifykey` key id. Treat verification as a gate: only run your automated remediation after the signature checks out. Log and drop any request that is unsigned or fails verification. ## Actions you can take Your endpoint receives the `sender_id` and other identifying fields and can immediately take action. Common automated responses include: * **Scramble the password** for the account associated with `originator`, preventing the sender from authenticating further. * **Block or suspend the account** at the hosting control panel level. * **Disable the sending plugin** (for example, a WordPress mail plugin) if the `originator_type` indicates a script. * **Rate-limit the account** at the mail server level to contain the damage while a human investigates. * **Create a support ticket** or page an on-call engineer using the `sender_id` and `condition_name` fields. The more aggressive your automated response, the greater the reduction in spam volume. Even a simple password scramble eliminates all further submissions from that sender without any manual work. ## Configure a webhook monitor Go to [console.mailchannels.net](https://console.mailchannels.net) and sign in. Go to **Outbound > Activity > Monitors** and click **Add Monitor**. Choose the condition you want to monitor from the dropdown menu, such as **Sender ID is sending SPAM** or **Sender ID blocked due to FBL reports**. Choose how frequently you want alerts to fire. The interval ranges from **1 to 24 hours**. For compromised account detection, a 1-hour interval gives you the fastest response. Change the alert type from the default **Email** to **Webhook**. Paste the full URL of your endpoint into the text box. Use `https://` to ensure the payload is encrypted in transit. Your endpoint must be publicly reachable and able to return an HTTP 2xx response. If you prefix your URL with `http://` instead of `https://`, the payload is sent without SSL encryption. Always use `https://` for production endpoints. Check the **Enabled** box to activate the monitor, then click the green check mark to save it. ## Test your endpoint Before relying on the webhook in production, verify that your endpoint handles the payload correctly. You can test it from the command line with `curl`: ```bash test webhook endpoint theme={null} curl -X POST https://your-endpoint.example.com/webhook \ -H "Content-Type: application/json" \ -d '{ "condition_name": "sender_id_sending_spam", "condition_description": "Sender ID is sending SPAM", "account_id": "myhostingcompany", "timestamp": "2024-08-15T14:23:00Z", "originator": "joe@example.com", "originator_type": "x-authuser", "sender_id": "myhostingcompany|x-authuser|joe@example.com", "ip": "203.0.113.42", "transaction_id": "abc123xyz", "envelope_sender": "joe@example.com", "message_id": "" }' ``` Confirm your server logs show the request and that any automated action (account suspension, password change, etc.) fires as expected. ## Routing webhook alerts to Slack MailChannels webhook payloads cannot be sent directly to a Slack incoming webhook URL because Slack requires a different JSON format. To route alerts to Slack, use an intermediary service such as **Zapier**: 1. Create a Zapier zap with a **Webhooks by Zapier** trigger (catch hook). 2. Use the Zapier webhook URL as your MailChannels webhook destination. 3. Add a Zapier action step that sends a formatted message to your Slack channel. Webhooks require a Standard plan subscription or higher. If the Monitors section is not visible in your console, upgrade your subscription to unlock webhook notifications. # Capacity and reconciliation Source: https://docs.mailchannels.com/plugins/whmcs/capacity-and-reconciliation Monitor shared parent capacity, understand over-allocation warnings, and use reconciliation to detect drift between WHMCS and MailChannels. The addon page gives you a live view of how much capacity you have sold against your parent account, and a reconciliation job that checks every service against MailChannels. ## Capacity dashboard Three cards appear at the top of **Addons > MailChannels Email API**: Addon status cards showing Parent connection, Active allocations of 310,000 sends, and Shared parent ceiling usage of 65,600 out of 2,000,000 | Card | What it shows | | - | - | | Parent connection | Whether a parent API key is stored and when it was last validated. | | Active allocations | The sum of plan send limits across all active and suspended services, and how many services that covers. | | Shared parent ceiling | Your parent account's current-period usage and limit, fetched live from MailChannels. Shows **Unavailable** with the reason if the figures cannot be retrieved. | ### Over-allocation Sub-account limits do not reserve parent capacity. Your parent account's limit is the hard ceiling for all sub-accounts combined. Once the parent limit is reached, MailChannels blocks sending from every sub-account until the next billing period. The **Shared parent ceiling** card turns into a warning when the sum of plan allocations exceeds your parent limit. This is not an error. It means that if every customer used their full allocation, your parent account would run out first. Decide how much to over-sell based on your customers' actual usage, and watch the parent usage figure as the period progresses. If parent usage approaches the limit, raise your MailChannels plan before customers are blocked. Blocked sends affect every customer at once, not just the heaviest ones. For the full rules, see [Sub-account sending limits](/email-api/sub-accounts-limits). ## Reconciliation Reconciliation compares each active or suspended service in WHMCS with its sub-account at MailChannels and records any drift. ### What is checked For each service, the plugin: 1. Confirms the sub-account still exists. 2. Compares the sub-account's enabled state with the WHMCS lifecycle state. 3. Refreshes usage and compares the remote send limit with the plan limit. 4. Confirms every active or pending module-managed credential still exists at MailChannels. ### Drift codes | Code | Meaning | | - | - | | `remote_sub_account_missing` | The sub-account no longer exists at MailChannels. | | `remote_lifecycle_drift` | The sub-account is enabled but WHMCS has it suspended, or the reverse. | | `remote_limit_drift` | The send limit at MailChannels differs from the plan limit. | | `remote_api_credential_missing` | A module-managed API key was deleted outside WHMCS. | | `remote_smtp_credential_missing` | A module-managed SMTP password was deleted outside WHMCS. | Drift is recorded in the operation history as a failed `reconcile` operation with error code `remote_drift` and the list of codes in its context. The service's last error code is updated so you can find it later. ### Automatic reconciliation The addon registers a WHMCS `AfterCronJob` hook. Once every 24 hours, after the WHMCS daily cron finishes, the hook reconciles up to 50 services. It is rate-limited so it never runs more than once per day, and it never writes secret material to cron output. ### Reconcile now To run reconciliation on demand, click **Reconcile now** under **Recovery and reconciliation**. This bypasses the daily rate limit and checks up to 100 services. The addon reports how many were checked and how many have drift or an API failure. The card also shows when reconciliation last ran. Recovery and reconciliation card with the Reconcile now button, the last run time, and the Retry provisioning form ### Fix drift Reconciliation detects drift but does not correct it automatically, because the right fix depends on why it happened. | Drift | Typical cause | Fix | | - | - | - | | Lifecycle drift | Someone suspended or activated the sub-account in the Console. | Run **Suspend** or **Unsuspend** from the service's Module Commands to re-apply the WHMCS state. | | Limit drift | Someone changed the limit in the Console. | Run **Change Package** on the service, or [apply plan changes](/plugins/whmcs/create-plans#change-an-existing-plan) for the product. | | Credential missing | The key or password was deleted in the Console. | Ask the customer to rotate the credential in the client area, or run **Change Package** to recreate missing required credentials. | | Sub-account missing | The sub-account was deleted in the Console. | Terminate the WHMCS service, or run **Create** again to provision a fresh sub-account with the same handle. | ## Operation history The **Operation history** table at the bottom of the addon page lists the 20 most recent operations across all services. Each row shows: * Time (UTC). * WHMCS service ID, linked to the service in the admin area. * Operation: Create, Suspend, Unsuspend, Change package, Terminate, Reconcile, Rotate create, or Rotate confirm. * Outcome: **Success** or **Failed**. * Error code, for failures. * MailChannels request ID, when the API returned one. Operation history table listing recent operations, including a failed Reconcile row with the error code remote_drift Every value passes through the plugin's redactor before it is stored. Include the request ID when you contact MailChannels support about a failed operation. The table stores operations under their internal names, such as `change_package` and `rotate_create`. Use those names when you query the table directly. For older entries, query the `mod_mailchannels_email_api_operations` table directly. # Client area Source: https://docs.mailchannels.com/plugins/whmcs/client-area What your customers see and can do on their MailChannels Email API service page in the WHMCS client area: usage, credential reveal, rotation, and domain checks. Each MailChannels Email API service has a page in the WHMCS client area under **Services > My Services**. Customers use it to monitor usage, retrieve credentials, rotate them, and check a sending domain. No credentials are ever sent by email. ## Status and usage The top of the page shows two cards: * **Emails sent this period**. Messages sent against the plan limit, with a progress bar, the percentage used, and the number remaining. The bar turns amber at 80% of the limit and red at 100%. The card also shows the billing period dates, when MailChannels reports them, and when usage was last updated. * **Status** and **Sub-account**. The service state, and the sub-account handle. SMTP customers use the handle as their username. Client area management panel showing usage, status, credentials, getting started guidance, and the domain check form Usage is cached for 15 minutes. The page refreshes it automatically when the cache is stale. Customers can click **Refresh usage** to bypass the cache at any time. If the service has not been provisioned yet, the page shows a notice instead of the cards. WHMCS shows this panel only while the service is active. When a service is suspended, the customer sees the standard WHMCS suspended notice instead. ## Credentials The **Credentials** section lists each module-managed credential with its type (**API key** or **SMTP password**) and status. Secrets are hidden until the customer asks for them. ### Reveal a credential Clicking **Reveal** decrypts the secret and shows it in a read-only field under the credential, with a **Copy** button and a reminder to store it safely. For SMTP credentials, the username is shown the same way. It is the sub-account handle. The secret is hidden again when the customer leaves the page. Credentials section with an SMTP password revealed, showing the SMTP username and a redacted password, each with a Copy button Reveals are ownership-checked and CSRF-protected POST requests. The response is sent with `Cache-Control: no-store` so browsers and proxies do not keep a copy. The plugin records the time of the reveal but never the secret itself. Customers can reveal the same credential again later. Because the secret is stored encrypted in WHMCS, there is no need to rotate just to see it again. ### Rotate a credential Rotation is two-phase so a customer never loses the ability to send: The customer clicks **Rotate** next to the credential. The plugin creates a new credential at MailChannels, stores it encrypted, and reveals it immediately. The replacement is listed under the original with the status **Replacement, not yet confirmed**. The old credential keeps working. The customer updates their application or SMTP settings with the new secret and confirms mail is sending. The customer clicks **Confirm replacement works** next to the pending credential and accepts the confirmation prompt. The plugin revokes the old credential at MailChannels, erases its stored secret, and marks the replacement **Active**. Credentials section during rotation, with the original API key still active and a highlighted replacement awaiting confirmation Until the customer confirms, both credentials remain valid. A pending replacement can be revealed again from the credential list. Only one replacement per credential type can be pending at a time, so **Rotate** is hidden until the customer confirms. Rotation is available only for credential types the plan includes. A customer on an **API only** plan has no SMTP password to rotate. ## Getting started guidance The page includes setup instructions that depend on the plan's credential mode. ### API plans API plans show a **Send with the API** card. The customer installs the official SDK with `composer require mailchannels/mailchannels-php`, creates `MailChannels\Client` with the revealed API key, and sends through `$client->emails->send(...)`. Point customers to the [PHP quickstart](/email-api/php/quickstart) and [SDK examples](/email-api/examples) for complete code. API plans also include a **Check a sending domain** form. The customer enters a domain and clicks **Check domain**. The plugin calls the MailChannels check-domain endpoint using the customer's own active API key. The result is a table with one row per check: SPF, each DKIM entry, [Domain Lockdown](/email-api/domain-lockdown), the sender domain, and its A and MX records. Each row shows **Passed** or **Failed** and the explanation MailChannels returned. **Show full response** expands the complete JSON result. Domain check results table for example.com with failed SPF, DKIM, and Domain Lockdown checks and passed sender domain, A record, and MX record checks ### SMTP plans SMTP plans show a **Send with SMTP** card with settings for WordPress, WooCommerce, or any custom SMTP mailer: | Setting | Value | | - | - | | Host | `smtp.mailchannels.net` | | Port | `587` | | Encryption | STARTTLS | | Username | The sub-account handle, filled in on the card | | Password | The revealed SMTP password | Messages submitted over SMTP do not generate MailChannels delivery [webhooks](/email-api/webhooks) and are not DKIM-signed by MailChannels. Customers who need delivery events or hosted DKIM should use the API. See [Email API vs SMTP](/email-api/email-api-vs-smtp). ### DNS guidance Every plan reminds the customer to configure [Domain Lockdown](/email-api/domain-lockdown) and to verify [SPF, DKIM, and DMARC](/email-api/spf-dkim-dmarc) before sending. The sending domain must also have a valid A or MX record, or MailChannels rejects mail from it. For Domain Lockdown, the customer authorizes your parent account ID or their sub-account in the `_mailchannels` TXT record on their domain. The domain check on API plans tells them whether the record is correct. ## Access control * Only the WHMCS client that owns the service can open the page. Requests for other services fail with `Access denied.` * Every action is a POST protected by the WHMCS client-area CSRF token. * Credentials never appear in URLs, ordinary page HTML, or the WHMCS welcome email. * Errors are passed through the plugin's redactor before display, so API keys or passwords cannot leak through exception text. See [Security and data flow](/plugins/whmcs/security) for details. # Connect your parent account Source: https://docs.mailchannels.com/plugins/whmcs/connect-parent-account Create a MailChannels parent API key and connect it to the WHMCS addon so the plugin can provision sub-accounts. The plugin manages every customer sub-account through one MailChannels parent account. You connect it once from the addon page. ## Prerequisites * The plugin is [installed and activated](/plugins/whmcs/installation). * You can sign in to the [MailChannels Console](https://dash.mailchannels.com) for the parent account. ## Create a parent API key Sign in to the MailChannels Console and open [Account > API Keys](https://dash.mailchannels.com/account/api-keys). Click **Create API Key**. Give it a label such as `WHMCS` and set the scope to `Sending Email` (`api`). Copy the key. You cannot view it again. Treat this key like a root password for your customers' email. It can create, suspend, and delete sub-accounts and their credentials. Store it only in WHMCS, where the plugin encrypts it. Do not reuse it in other integrations. Use a dedicated key for WHMCS. If you ever need to rotate it, you can disconnect and reconnect the addon without affecting other systems that use your parent account. ## Connect the key in WHMCS In the WHMCS admin area, go to **Addons > MailChannels Email API**. Under **1. Connect the parent account**, paste the key into **Parent API key** and click **Validate and connect**. Connect the parent account card with the Parent API key field and the Validate and connect button The plugin validates the key by retrieving your parent account's current-period usage and listing sub-accounts. If both calls succeed, it encrypts the key with the WHMCS encryption API, saves it, and shows a success message with your current usage and limit. The **Parent connection** card changes to **Connected** and records the validation time. The **Shared parent ceiling** card now shows your parent account's used and total sends for the current billing period. Addon status cards showing Parent connection as Connected, Active allocations, and Shared parent ceiling usage If the key is stored but MailChannels cannot return parent usage, the **Shared parent ceiling** card shows **Unavailable** with the reason. ### If validation fails | Message | Cause | Fix | | - | - | - | | MailChannels rejected the parent API key. | The key is wrong, revoked, or belongs to a sub-account. | Create a new key on the parent account and try again. | | The parent API key cannot manage this MailChannels resource. | The key is valid but cannot list sub-accounts. | Confirm the key belongs to the parent account, not a sub-account. | | MailChannels rate-limited the operation. | Too many requests in a short period. | Wait a minute and retry. | | Non-HTTPS MailChannels API override is disabled outside the development harness. | The server sets `MAILCHANNELS_WHMCS_API_BASE_URL` to a non-HTTPS URL. | Remove the override in production. See [Security and data flow](/plugins/whmcs/security#api-endpoint). | ## Disconnect Once a key is connected, the connection form is collapsed. To remove the stored key, expand **Disconnect** under **1. Connect the parent account**, type `DISCONNECT` in the confirmation field, and click **Disconnect**. Disconnecting deletes the encrypted key and the validation timestamp. It does not change any sub-account at MailChannels, and existing WHMCS service records are retained. Until you connect a key again, every provisioning action fails with the message `Connect a MailChannels parent account in the addon module before provisioning.` ## Rotate the parent key 1. Create a new API key in the MailChannels Console. 2. In the addon, expand **Replace the API key**, paste the new key, and click **Validate and connect**. The new key replaces the old one. 3. Revoke the old key in the Console. Existing sub-accounts and customer credentials are unaffected. The parent key is used only for management calls, never for customer sending. # Create and change plans Source: https://docs.mailchannels.com/plugins/whmcs/create-plans Build sellable MailChannels Email API products in WHMCS with the guided plan builder, or attach the module to an existing product. Preview and apply plan changes to existing services. A plan is a WHMCS product that uses the **MailChannels Email API** provisioning module. Every plan defines two module settings: | Setting | Values | Meaning | | - | - | - | | Sends per billing period | Positive integer | The sub-account send limit. MailChannels resets usage at the start of each billing period. | | Credential mode | `api`, `smtp`, `both` | Which credentials the plugin creates for each customer. | You can create plans two ways: with the guided plan builder in the addon, or by attaching the module to a product you configure yourself. ## Prerequisites * A [connected parent account](/plugins/whmcs/connect-parent-account). * At least one WHMCS product group. If none exists, the plan builder shows a link to create one instead of the form. ## Use the guided plan builder The plan builder creates a complete, ready-to-sell product in one step. Go to **Addons > MailChannels Email API** and find **2. Guided plan builder**. | Field | Notes | | - | - | | Plan name | Must be unique within the group. Limited to 120 characters. | | Product group | The WHMCS group the product appears in. | | Description | Shown to customers in the order form. | | Monthly price | A non-negative amount with at most two decimals, for example `239.00`. | | Currency | The WHMCS currency the price is in. | | Sends per billing period | A positive integer without separators, for example `100000`. | | Credentials | **API and SMTP** (default), **API only**, or **SMTP only**. This sets the plan's credential mode. | Guided plan builder form filled in with a plan name, description, monthly price, currency, send limit, and credentials Click **Create plan**. The addon reports the new WHMCS product ID. If a value is rejected, the form keeps what you entered so you can correct it. The builder creates a product with these properties: * Product type **Other**, with domain options hidden. * Recurring **monthly** billing in the selected currency. Other billing cycles are disabled. * Module set to **MailChannels Email API** with your send limit and credential mode. * Automatic setup **as soon as the first payment is received**. * The WHMCS welcome email disabled, because credentials are never sent by email. After creation, edit the product in **System Settings > Products/Services** to add other billing cycles, currencies, or a custom welcome email. Do not put credentials in email templates. Prices are your WHMCS store prices, not official MailChannels pricing. The plugin never reads or sets MailChannels billing. ## Attach the module to an existing product You can also use the module with a product you configure by hand. Go to **System Settings > Products/Services** and edit the product, or create a new product of type **Other**. On the **Module Settings** tab, choose **MailChannels Email API** as the module name. No server is required. Enter **Sends per billing period** as a positive integer and choose a **Credential mode**. Select **Automatically setup the product as soon as the first payment is received**. WHMCS product Module Settings tab with MailChannels Email API selected, a send limit, and a credential mode Save the product. New orders for it now provision sub-accounts. A product with a missing, zero, or non-numeric send limit fails to provision with the error `Sends per billing period must be a positive integer.` The failure is recorded in the WHMCS Module Queue and the addon's operation history. ## How limits relate to your parent account Sub-account limits do not reserve capacity on your parent account. The parent account's own limit is the hard ceiling. When your parent account reaches its limit, MailChannels blocks sending from every sub-account until the next billing period, regardless of how much of their own limit each customer has left. You may sell more total capacity than your parent limit if your customers' actual usage stays below it. The addon's **Shared parent ceiling** card warns you when allocations exceed the parent limit. See [Capacity and reconciliation](/plugins/whmcs/capacity-and-reconciliation) and [Sub-account sending limits](/email-api/sub-accounts-limits). ## Change an existing plan Editing a product's module settings in WHMCS does not change existing services. Only new orders pick up the new values. To roll a change out to existing customers, use the addon. Change **Sends per billing period** or **Credential mode** on the product's **Module Settings** tab and save. Go to **Addons > MailChannels Email API** and find **3. Preview and apply product changes**. The table lists every MailChannels product with its configured limit, credentials, and the number of active or suspended services. The **Status** column shows whether existing services match the product. **Up to date** means nothing needs applying. A count such as **2 to update** means that many services still have the old limit or credential mode. Preview and apply product changes table where the Lite plan shows 2 services to update Select the product under **Apply current settings to**, type `APPLY` in the confirmation field, and click **Apply plan changes**. The first product with services to update is selected for you. For each active or suspended service on the product, the plugin runs the same package change that a WHMCS upgrade runs: 1. Retrieves the sub-account's current-period usage. 2. Skips the service if the new limit is below current usage. The service keeps its old limit and is counted as requiring review. 3. Applies the new send limit. 4. Creates any credential type the new mode requires but the service lacks. 5. Revokes module-managed credentials of a type the new mode no longer includes. The addon reports how many services were updated and how many require review. Services that were skipped for a downgrade below usage can be retried after the customer's billing period resets, or after you move them to a suitable plan. Switching a plan from **both** to **API only** or **SMTP only** revokes the removed credential type for every service on the plan. Customers using that credential lose the ability to send with it immediately. Tell them before you apply the change. ### Change one customer's plan To move a single customer to a different plan, use the standard WHMCS upgrade or downgrade order, or edit the service and change its product under **Products/Services**, then run the **Change Package** module command. The same rules apply: a downgrade below current usage is rejected with the message `The new send limit is below current-period usage; the package change was not applied.` # FAQ Source: https://docs.mailchannels.com/plugins/whmcs/faq Frequently asked questions about selling MailChannels Email API sub-accounts through WHMCS. From the public Git repository at [bitbucket.org/mailchannels/mailchannels-email-api-whmcs-plugin](https://bitbucket.org/mailchannels/mailchannels-email-api-whmcs-plugin). Clone it and copy the two module directories into WHMCS, or build a release ZIP first. See [Install the WHMCS plugin](/plugins/whmcs/installation). Yes. The plugin is free and MIT-licensed, but it manages sub-accounts under your own MailChannels Email API parent account. You are billed by MailChannels for your parent account according to your MailChannels plan. Your customers are billed by you, through WHMCS, at prices you set. No. The plan builder shows illustrative examples only. Prices in WHMCS are your store prices. The plugin never reads or changes MailChannels billing. MailChannels blocks sending from that sub-account until the next MailChannels billing period. Other customers are unaffected. The customer sees their usage against the limit in the client area. MailChannels blocks sending from every sub-account until the next billing period, even those with remaining individual capacity. The **Shared parent ceiling** card on the addon page shows parent usage. See [Capacity and reconciliation](/plugins/whmcs/capacity-and-reconciliation). Yes. Sub-account limits do not reserve parent capacity, so you can over-allocate based on expected usage. The addon warns you when allocations exceed the parent limit so you can make that decision deliberately. No. The plugin makes no remote change on renewal. MailChannels controls billing-period resets for the parent account and all sub-accounts. A customer's WHMCS billing date and their MailChannels usage period are independent. Sub-accounts have access to the MailChannels Console and API with their own credentials, so a customer can create additional keys there. The plugin manages only the credentials it created. It never revokes or reconciles customer-created credentials. Credentials are never sent by email. The plan builder disables the welcome email so a template cannot accidentally include them. You can enable a welcome email on the product afterwards as long as it does not reference credential fields. Customers retrieve credentials from the client area. Handles are derived from your installation and the WHMCS service ID and cannot be changed. The company name is set once at creation from the client's WHMCS company name. You can change it in the MailChannels Console; the plugin does not overwrite it. No. Messages submitted over SMTP do not produce MailChannels delivery webhooks and are not DKIM-signed by MailChannels. Customers who need either should use an API plan. See [Email API vs SMTP](/email-api/email-api-vs-smtp). Only the sub-account handle, the client's company name (or a generic label), the send limit, and, for customer-initiated domain checks, the domain name entered. See [Security and data flow](/plugins/whmcs/security). No. The provisioning module declares that it does not require a server. Leave the product's server group empty. The plugin is tested against WHMCS 9.0 and 8.13 LTS with PHP 8.2 and 8.3. Other versions are not supported. No. WHMCS Marketplace listings are reviewed for completeness only. WHMCS does not audit, certify, or test third-party module code. Open a request with [MailChannels support](https://support.mailchannels.com/hc/en-us/requests/new) and include the plugin version and the details listed in [Troubleshooting](/plugins/whmcs/troubleshooting#contact-support). # Install the WHMCS plugin Source: https://docs.mailchannels.com/plugins/whmcs/installation Get MailChannels Email API for WHMCS from its Git repository, optionally build a release ZIP, and install, upgrade, deactivate, or remove it. ## Prerequisites * A WHMCS 9.0 or 8.13 LTS installation running PHP 8.2 or 8.3. * File system access to the WHMCS installation directory. * A WHMCS administrator account that can activate addon modules. * Git, to download the plugin. The plugin does not need Composer on your WHMCS server. The repository already contains an isolated, namespace-scoped copy of the official `mailchannels/mailchannels-php` SDK and its runtime dependencies. ## Get the plugin The plugin is open source under the MIT license. The source code is in a public Git repository: ```text theme={null} https://bitbucket.org/mailchannels/mailchannels-email-api-whmcs-plugin ``` Clone it on a workstation or on the WHMCS server: ```bash theme={null} git clone https://bitbucket.org/mailchannels/mailchannels-email-api-whmcs-plugin.git cd mailchannels-email-api-whmcs-plugin ``` You can install the plugin in two ways. Both install the same code. | Method | Use it when | | - | - | | [Install from the clone](#install-from-the-clone) | You want the quickest path. You copy two directories. No build tools are needed. | | [Build a release ZIP](#build-a-release-zip) | You want a single versioned archive to store, transfer, or deploy to several WHMCS servers. | ## Install from the clone The two module directories in the repository are ready to use as they are. From the root of the clone, copy both module directories into your WHMCS installation. Replace `/path/to/whmcs` with your WHMCS directory. ```bash theme={null} cp -R modules/addons/mailchannels_email_api /path/to/whmcs/modules/addons/ cp -R modules/servers/mailchannels_email_api /path/to/whmcs/modules/servers/ ``` The files must end up at these paths: ```text theme={null} /path/to/whmcs/modules/addons/mailchannels_email_api/ /path/to/whmcs/modules/servers/mailchannels_email_api/ ``` The web server user must be able to read every file under both directories. Continue with [Activate and connect](#activate-and-connect). ## Build a release ZIP The build script regenerates the bundled SDK runtime and packages the two module directories, the license, and the README into one archive. ### Build requirements Building runs on Linux or macOS and needs these tools on the build machine. The WHMCS server does not need them. * Docker. The build runs PHP, Composer, and the namespace scoper in containers, so you do not need PHP installed locally. * Bash, `rsync`, `zip`, and `unzip`. * [ripgrep](https://github.com/BurntSushi/ripgrep) (`rg`). The build uses it to check that no secret markers end up in the archive. ### Build the archive From the root of the clone, install the Composer dependencies for the build tools and for the runtime that gets bundled: ```bash theme={null} docker run --rm -v "$PWD:/work" -w /work composer:2.9 composer install docker run --rm -v "$PWD/runtime:/work" -w /work composer:2.9 composer install --no-dev ``` Pass the version number you want in the archive name: ```bash theme={null} dev/scripts/build-release 1.0.0 ``` The script finishes with a line such as: ```text theme={null} Built and content-verified /path/to/clone/dist/mailchannels-email-api-for-whmcs-1.0.0.zip ``` The archive is written to the `dist/` directory. It contains one top-level folder: ```text theme={null} mailchannels-email-api-for-whmcs-1.0.0/ modules/addons/mailchannels_email_api/ modules/servers/mailchannels_email_api/ docs/operations.md LICENSE README.md ``` The build rewrites generated files under `modules/servers/mailchannels_email_api/vendor-scoped/` in your clone. This is expected. Run `git checkout -- modules/servers/mailchannels_email_api/vendor-scoped` afterwards if you want a clean working tree. ### Install the archive Transfer `mailchannels-email-api-for-whmcs-1.0.0.zip` to the server and extract it: ```bash theme={null} unzip mailchannels-email-api-for-whmcs-1.0.0.zip ``` Copy both module directories from the extracted folder into your WHMCS installation: ```bash theme={null} cd mailchannels-email-api-for-whmcs-1.0.0 cp -R modules/addons/mailchannels_email_api /path/to/whmcs/modules/addons/ cp -R modules/servers/mailchannels_email_api /path/to/whmcs/modules/servers/ ``` The web server user must be able to read every file under both directories. ## Activate and connect In the WHMCS admin area, go to **System Settings > Addon Modules**. Find **MailChannels Email API** and click **Activate**. Activation creates the plugin's database tables and records an installation identity. The identity is a 12-character hash of your WHMCS system URL and encryption hash. It becomes part of every sub-account handle. Still in **Addon Modules**, click **Configure** next to **MailChannels Email API** and select the administrator roles that may open the addon page. Save the changes. Open **Addons > MailChannels Email API** and follow [Connect your parent account](/plugins/whmcs/connect-parent-account). The addon page shows three cards at the top: **Parent connection**, **Active allocations**, and **Shared parent ceiling**. If you see them, the modules loaded correctly. ## Database tables Activation creates five tables. All of them use the `mod_mailchannels_email_api_` prefix. | Table | Purpose | | - | - | | `settings` | Plugin settings, including the encrypted parent API key and the installation identity. | | `services` | One row per WHMCS service: handle, lifecycle state, plan limit, credential mode, and cached usage. | | `credentials` | Module-managed credentials: provider ID, encrypted secret, status, and rotation links. | | `operations` | A sanitized history of every provisioning operation, its outcome, error code, and request ID. | | `migrations` | The schema version applied to this installation. | Secrets in `settings` and `credentials` are encrypted with the WHMCS encryption API. See [Security and data flow](/plugins/whmcs/security). ## Upgrade 1. Update your clone with `git pull`. If you install from a release ZIP, [build a new archive](#build-a-release-zip). 2. Replace both module directories in WHMCS with the new versions. Remove the old directories first so that files deleted in the new version do not linger. 3. Open **System Settings > Addon Modules**. WHMCS detects the new version and runs the plugin's schema migration. Upgrades never delete provisioning state or encrypted credentials. ## Deactivate Deactivating the addon in **Addon Modules** disables the admin page and the daily reconciliation hook. Provisioning state and encrypted credentials are retained so you can reactivate safely. While the addon is deactivated, WHMCS still runs the provisioning module for lifecycle events such as suspend and terminate, because those are attached to products. Those operations continue to work as long as the parent API key remains stored. ## Remove To remove the plugin completely: 1. Terminate or move any WHMCS services that use the **MailChannels Email API** module. Terminating a service deletes its sub-account at MailChannels. 2. Deactivate the addon. 3. Delete both module directories. 4. Drop the five `mod_mailchannels_email_api_*` tables if you do not want to keep the operation history. Deleting the module directories does not delete sub-accounts at MailChannels. Use the WHMCS terminate action, or delete them from the MailChannels Console, before you remove the plugin. # MailChannels Email API for WHMCS Source: https://docs.mailchannels.com/plugins/whmcs/overview Sell MailChannels Email API sub-accounts through your own WHMCS store. WHMCS bills your customers, and the plugin provisions sub-accounts, send limits, and credentials automatically. MailChannels Email API for WHMCS is a free, MIT-licensed WHMCS addon and provisioning module for hosting providers. It lets you sell MailChannels Email API capacity as WHMCS products. WHMCS stays the authority for prices and billing. The plugin manages the MailChannels sub-account, its send limit, and the API key or SMTP password each customer uses to send. Each customer gets an isolated [sub-account](/email-api/multi-tenancy) under your MailChannels parent account. One customer's sending behavior stays scoped to their own sub-account, so it does not become everyone's delivery problem. Plans you build with the plugin appear in your WHMCS store like any other product. You choose the names, descriptions, and prices. WHMCS store page listing three MailChannels Email API plans with monthly prices and Order Now buttons ## How it works The plugin ships as two WHMCS modules that work together: * **Addon module** (`modules/addons/mailchannels_email_api`). Adds a **MailChannels Email API** page to the WHMCS admin area. You use it to connect your parent account, build plans, preview and apply plan changes, watch shared capacity, retry failed provisioning, and reconcile services against MailChannels. * **Provisioning module** (`modules/servers/mailchannels_email_api`). Attached to each WHMCS product. It creates, suspends, reactivates, upgrades, and terminates the customer's sub-account as WHMCS moves the service through its lifecycle. It also renders the customer's client area page. When a customer pays for a plan, WHMCS calls the provisioning module. The module creates a sub-account with a deterministic handle, applies the plan's send limit, creates the credentials the plan requires, encrypts them with the WHMCS encryption API, and marks the service active. The customer then reveals their credentials from the client area and starts sending. ```text theme={null} Customer pays in WHMCS -> WHMCS runs the provisioning module -> create sub-account (handle derived from the WHMCS service ID) -> set the plan's send limit -> create API key and/or SMTP password -> encrypt secrets at rest and mark the service active -> Customer reveals credentials in the client area and sends ``` ## Key concepts | Term | Meaning | | - | - | | Parent account | Your MailChannels Email API account. The plugin uses one parent API key to manage every sub-account it creates. | | Sub-account | The isolated MailChannels account created for each WHMCS service. It has its own credentials, send limit, usage, suppressions, and webhooks. | | Handle | The sub-account identifier. The plugin derives it from your installation and the WHMCS service ID, so it never contains customer identity or secret data. | | Send limit | The number of messages a sub-account may send per MailChannels billing period. Every plan must define a positive send limit. | | Credential mode | Whether a plan provisions an `api` key, an `smtp` password, or `both`. | | Shared parent ceiling | Your parent account's own limit. It is the hard cap on all sub-accounts combined. See [Capacity and reconciliation](/plugins/whmcs/capacity-and-reconciliation). | ## Requirements | Requirement | Supported | | - | - | | WHMCS | 9.0, 8.13 LTS | | PHP | 8.2, 8.3 | | MailChannels | An Email API parent account and a parent API key that can manage sub-accounts | | Composer | Not required on the WHMCS server. The plugin bundles a namespace-scoped copy of the official PHP SDK. | ## Source code The plugin is open source. Get it from the [Bitbucket repository](https://bitbucket.org/mailchannels/mailchannels-email-api-whmcs-plugin), then follow [Install the WHMCS plugin](/plugins/whmcs/installation). ## What you get * Automatic sub-account provisioning after the first payment. * Per-plan send limits and API, SMTP, or combined credential modes. * A guided plan builder that creates ready-to-sell WHMCS products. * Suspend, reactivate, package change, and terminate mapped to MailChannels operations. * A capacity dashboard that compares plan allocations with your parent account's limit. * Daily reconciliation that detects drift between WHMCS and MailChannels. * A customer client area with usage, credential reveal, two-phase credential rotation, and a domain check. * Credentials encrypted at rest and never written to logs, URLs, page HTML, or email. ## Get started Clone the repository, copy the two module directories into WHMCS, and activate the addon. Validate and store your parent API key. Build sellable products with a send limit and credential mode. Learn what happens on create, suspend, upgrade, and terminate. Prices you set in WHMCS are your store prices. They are not official MailChannels pricing. You are billed by MailChannels for your parent account according to your MailChannels plan. # Security and data flow Source: https://docs.mailchannels.com/plugins/whmcs/security How MailChannels Email API for WHMCS protects credentials, what data it sends to MailChannels, and which server settings affect it. ## Credential storage * The parent API key and every customer credential are encrypted at rest with the WHMCS encryption API (`EncryptPassword` and `DecryptPassword`). They are protected by your WHMCS encryption hash. * Secrets are decrypted only at the moment they are needed: to call the MailChannels API, or to reveal a credential to its owner. * When a credential is revoked, replaced, or its service is terminated, the ciphertext is deleted and an erasure timestamp is recorded. Your WHMCS encryption hash in `configuration.php` protects every stored secret. If you change it, WHMCS can no longer decrypt the parent key or customer credentials. Reconnect the parent account and ask customers to rotate their credentials. ## Where credentials never appear * URLs or query strings. * Ordinary page HTML. Secrets are shown only in the response to an ownership-checked, CSRF-protected POST request that carries `Cache-Control: no-store`. * The WHMCS welcome email. The plan builder disables it, and the module never populates credential fields. * WHMCS module logs, the plugin's operation history, cron output, or exception messages. * Sub-account handles. Handles are derived from a hash of your installation and the WHMCS service ID. ## Redaction All error text and operation context passes through a redactor before it is stored or displayed. The redactor: * Replaces the values of keys such as `api_key`, `password`, `secret`, `authorization`, and `x-api-key` with `[redacted]`. * Replaces inline patterns such as `api_key=...` or `password: ...` in free text. * Replaces tokens that match MailChannels key prefixes. * Truncates messages to 500 characters. MailChannels API errors are also mapped to fixed, safe messages by HTTP status before they reach an administrator or customer. See [Troubleshooting](/plugins/whmcs/troubleshooting#error-messages). ## Access control | Surface | Protection | | - | - | | Addon page | WHMCS administrator role permission for the addon, plus the WHMCS admin CSRF token on every action. | | Client area page | The logged-in client must own the service. Every action requires the WHMCS client CSRF token. | | Provisioning | Runs only through WHMCS module commands, the WHMCS API, or the addon's retry action. | | Concurrency | A per-service MySQL advisory lock prevents overlapping operations on the same service. | ## Data sent to MailChannels The plugin sends the minimum needed to manage sub-accounts. All calls go to the MailChannels Email API over HTTPS, authenticated with your parent API key, with the user agent `mailchannels-email-api-for-whmcs/`. | Data | When | Purpose | | - | - | - | | Sub-account handle | Every sub-account call | Identifies the sub-account. Derived from your installation hash and the WHMCS service ID. | | Company name | Sub-account creation | The client's company name from WHMCS, truncated to 128 characters, or `WHMCS service ` if none. Shown on the MailChannels unsubscribe page for that sub-account. | | Send limit | Create and change package | Sets the sub-account limit. | | Domain name | Customer runs the domain check | The domain the customer typed, checked with the customer's own sub-account API key. | The plugin does not send customer names, email addresses, WHMCS IDs, invoices, or message content. WHMCS service IDs are encoded into handles only as base 36 alongside the installation hash. ## Data received from MailChannels * Sub-account list entries (handle and enabled state) when locating a sub-account. * Usage snapshots: total usage, limit, and billing period dates. Cached in WHMCS for 15 minutes. * Credential IDs and one-time secret values at creation. Secrets are encrypted immediately. * Domain check results, which are shown to the customer and not stored. ## API endpoint The plugin talks to `https://api.mailchannels.net/tx/v1` by default. Two environment variables exist for the development harness only: | Variable | Effect | | - | - | | `MAILCHANNELS_WHMCS_API_BASE_URL` | Overrides the API base URL. | | `MAILCHANNELS_WHMCS_ALLOW_INSECURE_API=1` | Required for the override to accept a non-HTTPS URL. Without it, a non-HTTPS override fails closed. | Do not set either variable on a production server. ## Bundled SDK The release contains the official `mailchannels/mailchannels-php` SDK and its HTTP dependencies under `modules/servers/mailchannels_email_api/vendor-scoped/`, rewritten into the `MailChannelsWhmcsScoped` namespace. This prevents conflicts with other WHMCS modules that ship their own copies of Guzzle or PSR libraries. Do not run Composer inside the module directory. ## Reporting a vulnerability If you find a security issue in the plugin, contact MailChannels through the [support portal](https://support.mailchannels.com/hc/en-us/requests/new) and mark the request as a security report. Do not post details publicly until MailChannels has responded. # Service lifecycle Source: https://docs.mailchannels.com/plugins/whmcs/service-lifecycle What the plugin does at MailChannels when WHMCS creates, suspends, reactivates, upgrades, renews, or terminates a service, and how failures are recovered. The provisioning module maps WHMCS module commands to MailChannels sub-account operations. Every operation is idempotent and safe to retry. ## Lifecycle mapping | WHMCS action | MailChannels result | | - | - | | Create | Create the sub-account, set the send limit, create the required credentials, mark the service active. | | Suspend | Suspend the sub-account. Sending is blocked. Credentials are kept. | | Unsuspend | Activate the sub-account. Sending resumes with the same credentials. | | Change Package | Apply the new send limit, create newly required credentials, revoke credential types the new plan drops. | | Renew | No remote change. MailChannels resets usage on its own billing period. | | Terminate | Permanently delete the sub-account, erase encrypted secrets, keep a non-secret tombstone. | You can run any of these by hand from **Module Commands** on the service's page in the WHMCS admin area. WHMCS Module Commands row with Create, Renew, Suspend, Unsuspend, Terminate, and Change Package buttons ## Sub-account handles Each sub-account handle is deterministic. It is built from three parts: ```text theme={null} whmcs + <12-character installation hash> + ``` For example, service `1234` on an installation with hash `3f9a1c7e2b4d` gets the handle `whmcs3f9a1c7e2b4dya`. The installation hash is derived from your WHMCS system URL and encryption hash when the addon is activated. Because the handle is derived rather than random, a retried provisioning run always looks for the same sub-account. The handle contains no customer identity and no secret material. The sub-account's company name is the client's company name from their WHMCS profile, truncated to 128 characters. If the client has no company name, or it is shorter than three characters, the plugin uses `WHMCS service ` instead. ## What happens on create When WHMCS runs **Create**, the module: 1. Takes a per-service MySQL advisory lock so two operations cannot run on the same service at once. The lock waits up to 20 seconds, then fails with `Another MailChannels operation is already running for this service.` 2. Records the service as `provisioning` with its handle, limit, and credential mode. 3. Looks for an existing sub-account with that handle by paging through your parent account's sub-account list. 4. Creates the sub-account if none exists. If MailChannels answers with a conflict and the handle is then found, the existing sub-account is reused. 5. Sets the send limit from the plan. 6. Creates each credential type the plan requires that the service does not already have. Secrets are encrypted immediately with the WHMCS encryption API. 7. Marks the service `active` and writes a success entry to the operation history. If any step fails, the service stays in `provisioning`, a sanitized failure entry is written to the operation history, and WHMCS shows the error in the **Module Queue**. Retrying **Create** picks up where it left off. ### Credential creation safety Creating a credential is the one step whose outcome can be ambiguous, for example when a request times out after MailChannels has already created the key. To avoid leaving unknown credentials behind, the module: 1. Lists the sub-account's credential IDs before the request. 2. Creates the credential. 3. On an ambiguous failure, lists the IDs again and compares. 4. If exactly one new ID appeared, revokes it and retries the creation once. 5. If more than one new ID appeared, stops and records `RecoveryRequiredException`. An administrator must inspect the sub-account in the MailChannels Console and remove stray credentials before retrying. ## What happens on change package **Change Package** runs when a customer upgrades or downgrades, or when you [apply plan changes](/plugins/whmcs/create-plans#change-an-existing-plan) from the addon: 1. Retrieves the sub-account's current-period usage. 2. Rejects the change if the new limit is below current usage. Nothing is changed at MailChannels. 3. Applies the new limit. 4. Creates any newly required credential type. 5. Revokes active module-managed credentials of a type the new plan no longer includes, and erases their stored secrets. 6. Records the new limit and mode. Credentials the customer created themselves in the MailChannels Console are never touched. The plugin only manages credentials it created. ## What happens on terminate **Terminate** deletes the sub-account at MailChannels. If the sub-account is already gone, the deletion is treated as successful. The plugin then: * Sets every stored credential to `erased` and removes its ciphertext. * Clears cached usage. * Sets the service to `terminated` and records a tombstone timestamp. The service row and operation history are kept for auditing. They contain the handle, timestamps, states, and sanitized error codes, but no secrets. Termination is permanent at MailChannels. The sub-account's suppressions, webhooks, and usage history are deleted with it. A re-created service for the same WHMCS service ID gets the same handle but starts with a fresh sub-account. ## Recover a failed service Failed operations appear in two places: the WHMCS **Module Queue** and the **Operation history** at the bottom of the addon page. Each history row shows the time, service ID, operation, outcome, error code, and the MailChannels request ID when one is available. To retry provisioning from the addon: 1. Go to **Addons > MailChannels Email API** and find **Recovery and reconciliation**. 2. Enter the WHMCS service ID and click **Retry provisioning**. This calls the WHMCS `ModuleCreate` API for that service, which runs the same idempotent create flow. You can also retry from the service's **Module Commands** in the WHMCS admin area. Common causes and fixes are listed in [Troubleshooting](/plugins/whmcs/troubleshooting). ## Service states | State | Meaning | | - | - | | `provisioning` | Create started but has not completed. Retry is safe. | | `active` | The sub-account is enabled and credentials are available. | | `suspended` | The sub-account is suspended at MailChannels. | | `terminated` | The sub-account was deleted and secrets were erased. | The client area shows the state to the customer while the service is active. See [Client area](/plugins/whmcs/client-area). # Troubleshooting Source: https://docs.mailchannels.com/plugins/whmcs/troubleshooting Diagnose and fix common MailChannels Email API for WHMCS problems: provisioning failures, error messages, drift, and customer sending issues. ## Where to look 1. **Addons > MailChannels Email API > Operation history**. The last 20 operations with outcome, error code, and MailChannels request ID. 2. **Utilities > Module Queue** in WHMCS. Failed module commands with the sanitized error message returned to WHMCS. 3. The `mod_mailchannels_email_api_operations` table, for history older than the last 20 entries. 4. **WHMCS Activity Log**, for module command invocations. The plugin never writes secrets to any of these locations, so you can share their contents with MailChannels support along with the request ID. ## Error messages The plugin maps MailChannels API responses to fixed messages. | Message | HTTP status | What to do | | - | - | - | | MailChannels rejected the parent API key. | 401 | The stored parent key is invalid or revoked. [Reconnect](/plugins/whmcs/connect-parent-account) with a new key. | | The parent API key cannot manage this MailChannels resource. | 403 | The key lacks permission, or belongs to a sub-account. Use a parent-account key. | | The MailChannels resource was not found. | 404 | The sub-account or credential was deleted outside WHMCS. Run **Reconcile now** to identify drift. | | The MailChannels resource already exists. | 409 | A sub-account with this handle exists but could not be found in the list. Retry. If it persists, contact support with the request ID. | | MailChannels rate-limited the operation. | 429 | Wait and retry. Avoid applying plan changes to many services at once during peak hours. | | MailChannels could not complete the operation. | 5xx | A MailChannels-side error. Retry later. Check [System Status](https://status.mailchannels.net/). | | The MailChannels API operation failed. | Other | A transport or unexpected error. Check outbound HTTPS connectivity from the WHMCS server to `api.mailchannels.net`. | ### Plugin messages | Message | Cause | Fix | | - | - | - | | Connect a MailChannels parent account in the addon module before provisioning. | No parent key is stored. | [Connect the parent account](/plugins/whmcs/connect-parent-account). | | Sends per billing period must be a positive integer. | The product's first module setting is empty, zero, negative, or not a whole number. | Edit the product's **Module Settings** and enter a positive integer. | | Credential mode must be api, smtp, or both. | The product's second module setting has an unexpected value. | Choose a value from the dropdown and save. | | The new send limit is below current-period usage; the package change was not applied. | A downgrade would put the customer over their new limit. | Wait for the billing period to reset, or choose a higher limit. | | Another MailChannels operation is already running for this service. | Two operations overlapped and the second waited more than 20 seconds. | Retry after the first finishes. | | MailChannels service state was not found. | A lifecycle command ran on a service that was never provisioned. | Run **Create** first. | | The bundled MailChannels SDK runtime is missing. Reinstall the complete module release. | The `vendor-scoped` directory is missing or incomplete. | Re-copy `modules/servers/mailchannels_email_api` from the repository or your release ZIP. | | Non-HTTPS MailChannels API override is disabled outside the development harness. | `MAILCHANNELS_WHMCS_API_BASE_URL` points at a non-HTTPS URL. | Remove the override. See [API endpoint](/plugins/whmcs/security#api-endpoint). | | Credential creation had an ambiguous outcome and multiple new provider IDs were found. Administrator recovery is required. | A timeout during credential creation left more than one unknown credential. | Open the sub-account in the MailChannels Console, remove credentials WHMCS does not list, then retry. | | WHMCS could not process encrypted module data. | The WHMCS encryption API failed, usually because the encryption hash changed. | Restore the hash, or reconnect the parent key and have customers rotate credentials. | ## Provisioning stays in "provisioning" The service row exists but never reached `active`. 1. Check the operation history for the `create` failure and its error code. 2. Fix the cause using the tables above. 3. Retry from **Recovery and reconciliation > Retry provisioning**, or run **Create** from the service's Module Commands. Retrying is safe. The plugin reuses the sub-account if it already exists and only creates credentials the service still lacks. ## Customer cannot send Have the customer reveal the credential again from the client area and compare it with what their application uses. If they rotated recently, make sure they confirmed the rotation and updated every application that uses the old credential. For SMTP, the username must be the sub-account handle shown on the page, not the customer's email address. Ask the customer to use **Check domain** on an API plan, or check the domain yourself with the [check-domain tool](https://dash.mailchannels.com/domain-health). The `_mailchannels` TXT record must authorize the account that sends. See [Domain Lockdown](/email-api/domain-lockdown). The sending domain has no valid A or MX record. The customer must add one. See [Troubleshooting the Email API](/email-api/troubleshooting). Compare the client area usage with the plan limit. If the customer is under their own limit but sending is blocked, check the **Shared parent ceiling** card. When the parent account reaches its limit, every sub-account is blocked until the next billing period. WHMCS shows the service as suspended in the client area and hides the management panel. Unsuspend the service in WHMCS. If WHMCS shows it active but MailChannels has it disabled, reconciliation reports `remote_lifecycle_drift`. Run **Unsuspend** from Module Commands to re-apply the WHMCS state. Messages sent over SMTP do not produce MailChannels delivery webhooks and are not DKIM-signed by MailChannels. This is expected. Customers who need either should send through the API. See [Email API vs SMTP](/email-api/email-api-vs-smtp). ## Reconciliation reports drift See [Fix drift](/plugins/whmcs/capacity-and-reconciliation#fix-drift) for the meaning of each drift code and the module command that corrects it. ## Client area shows "Usage: Unavailable" or an error The page refreshes usage from MailChannels when the 15-minute cache expires. If MailChannels cannot be reached, the page shows the last cached values and a sanitized error. Check connectivity from the WHMCS server and the parent key status on the addon page. ## Contact support When you contact [MailChannels support](https://support.mailchannels.com/hc/en-us/requests/new), include: * The plugin version, WHMCS version, and PHP version. * The operation, outcome, error code, and request ID from the operation history. * The sub-account handle. Never include credentials.