Prerequisites
- Drupal 10.6 or later, or Drupal 11, with PHP 8.1 or later, Composer and Drush.
- A verified sending domain.
- An API v2 key.
Version 2.x of the module uses API v2. It replaces version 1.x, which used the legacy API v1 and the Mail System module. Version 1.x is no longer supported and has no upgrade path.
Install the Module
From your Drupal project root:/admin/config/system/mailer/override and enable the overrides for the emails your site sends, for example User for account and password-reset emails.
Add the AhaSend Transport
- At Configuration → System → Basic site settings, set the site’s Email address to an address on your verified domain. Drupal sends from this address by default.
-
Open
/admin/config/system/mailer/transport, choose AhaSend (API v2) as the Transport type and click Add transport. -
Enter a label and fill in the AhaSend fields:
- Set Mode to Sandbox: deliver for your first tests. AhaSend records sandbox sends and sends their webhook events, but delivers nothing. See sandbox mode.
- Click Test connection. It calls the ping endpoint, which checks only the API key. It does not check the account ID, the key’s scope or your sender address; the first real send does.
- Save the transport, then click Set as default next to it in the transport list.
Send a Test Email
- Symfony Mailer 1.x: open the Test tab at
/admin/config/system/mailer/test. - Mailer Plus 2.x: open
/admin/config/system/mailer.
/admin/reports/ahasend). If it is missing, look for AhaSend errors in Reports → Recent log messages.
Then test the emails your site depends on, such as password reset, new account and form notifications.
When the tests pass, set Mode to Live. A key created in Sandbox mode keeps every send in sandbox, even when the transport is set to Live, so use a production key on the live site.
Receive Delivery Events with Webhooks
Webhooks are optional. With them, the message log shows what happened to each email after AhaSend accepted it.- Open Configuration → Web services → AhaSend (
/admin/config/services/ahasend) and copy the Webhook URL, for examplehttps://example.com/ahasend/webhook. - In the AhaSend dashboard, open Webhooks, click Add Webhook, paste the URL, choose the events to send and create the webhook. See webhooks.
- Copy the Webhook Secret from the webhook’s details page, paste it into Signing secret in Drupal and save.
- Send a sandbox test email. After a short delay, its entry in the message log shows the reception and delivery events.
Message Log
Reports → AhaSend message log (/admin/reports/ahasend) has two tabs:
- Messages: one entry per recipient, with the send time, subject, sender, recipient and status. The status follows the webhook events received for the message and uses the dashboard’s names: Received, Deferred, Delivered, Opened, Clicked, Bounced, Failed or Suppressed. Until the first event arrives, or if no webhook is set up, it shows the result AhaSend returned when the email was sent, such as Queued. Open an entry to see its delivery timeline from webhooks, such as Message Delivered, Message Deferred, Message Bounced or Message Opened, with the receiving server’s SMTP response when AhaSend reports one.
- Events: account-level events that belong to no single message, such as suppressions and DNS errors.
Keep Secrets Out of Configuration
By default, Drupal saves the API key and the webhook signing secret in its configuration, sodrush config:export writes them to your export files. To avoid this, use the Key module 1.18 or later:
-
Install and enable it:
-
At
/admin/config/system/keys, add a key of type Authentication that uses the File or Environment provider. A key that uses the Configuration provider is exported too, and the module warns you when you select one. - In the transport form, set API key source to Use a key from the Key module and select the key. In the AhaSend settings form, do the same with Signing secret source.
settings.php. Replace TRANSPORT_ID with the transport’s machine name:
settings.php
api_key override; override api_key_id to select a different key instead. Review key security and IP allow lists.
React to Webhook Events in Code
Every verified webhook delivery dispatches theAhaSendWebhookEvent::RECEIVED event (ahasend.webhook_received). The module stores each delivery at priority 100 and stops retried deliveries there, so subscribe with a lower priority to react once per event. This subscriber in a custom module logs each bounce:
src/EventSubscriber/AhaSendBounceSubscriber.php
mymodule.services.yml:
mymodule.services.yml
ahasend:ahasend to the dependencies in mymodule.info.yml, so the AhaSend module cannot be uninstalled while your subscriber uses its event class.
The event also has getPayload(), getMessageIdHeader() and getDeliveryId(). The signature proves a delivery came from AhaSend, but values such as subjects, addresses and SMTP responses come from third parties: treat them as untrusted input. See webhooks for the event types and their data.
Troubleshooting
- AhaSend (API v2) is missing from the transport types: clear caches with
drush cr. - Test connection passes, but emails fail: check the account ID, that the key’s scope covers your sender domain, and that the sender address uses a verified domain. AhaSend’s error message is in Reports → Recent log messages; see API errors.
- Emails are accepted but never arrive: check that Mode is Live and that the API key is not a sandbox key.
- Core emails skip AhaSend on Mailer Plus 2.x: enable Mailer Override and the override for those emails.
- The webhook URL answers 404: no signing secret is saved, or the selected key is missing or empty.
- Webhook deliveries get 401: the signing secret does not match the webhook, or the server clock is more than 5 minutes off.
- Webhooks stopped arriving: AhaSend disables a webhook after more than 100 consecutive failed deliveries. Fix the cause, then enable it again in the dashboard.
Related Guides
- Domain setup, API credentials, scoped credentials and sandbox mode.
- Webhooks, bounces and suppressions, open tracking and click tracking.
- Troubleshooting, API errors and key security.
- The module’s project page and issue queue.

