Prerequisites
- A Groundmail account with a verified sending domain.
- An API key (
gm_…): in the dashboard, open API keys and create one. A send-only key is enough. - Craft CMS 5 with PHP 8.2 or later, and shell access to run Composer.
Craft's email goes out as transactional email by default, so use it for account, order and notification email. Never send cold email.
Install the plugin
In your Craft project, add the plugin's repository, install it with Composer, then install it in Craft:
composer config repositories.groundmail-craft vcs https://github.com/Groundmail/groundmail-craft
composer require groundmail/craft-plugin
php craft plugin/install groundmailConnect Craft
Add your API key to the project's
.envfile:GROUNDMAIL_API_KEY=gm_your_keyIn the control panel, go to Settings, then Email.
Set System Email Address to an address on your verified domain, for example
noreply@yourdomain.com, and set Sender Name.Change Transport Type to Groundmail.
Set API Key to
$GROUNDMAIL_API_KEY. Craft reads the key from the environment, so it stays out of your project config.Leave Email category on Transactional unless the site only sends email about your product (see Email category).
Click Save.
If allowAdminChanges is off in production, make the change in development and deploy the project config as usual. The key itself lives in each environment's .env.
Test it
Groundmail's simulator addresses never reach a real inbox. Send Craft's test email to one:
php craft mailer/test --to=delivered@simulator.groundmail.devOr click Test on the Email settings page, which sends to your own address. The email shows up under Emails in the Groundmail dashboard. Sent to the simulator it has the status delivered and "test": true; use bounce@simulator.groundmail.dev to see a hard bounce. See Testing.
Email category
Groundmail sends transactional, product and newsletter email from separate IPs. Email category sets the stream for the email Craft sends:
- Transactional (default): account email, password resets, order and form notifications.
- Product: email about your product to its users, such as onboarding tips. Recipients can opt out of product email and keep getting transactional email.
A single message can set its own category with an X-Groundmail-Category header, and add tags with X-Groundmail-Tag: name=value:
Craft::$app->getMailer()->compose()
->setTo($user->email)
->setSubject('Your order has shipped')
->setHtmlBody($html)
->addHeader('X-Groundmail-Tag', 'order=' . $order->number)
->send();X-Groundmail-Category: newsletter sends an opt-in newsletter, once your account is approved for newsletters (email support@groundmail.dev). Until then those sends are refused with a 403 and nothing is sent.
Craft Commerce
Commerce sends order emails through Craft's mailer, so once Groundmail is the transport they go out through it too. Each Commerce email has its own Status Email Address (under Commerce, then System Settings, then Emails). Leave it empty to send from the system email address, or set it to an address on your verified domain.
To know whether an order email was delivered or bounced, create a webhook for email.delivered, email.bounced and email.complained, or check Emails in the dashboard.
Troubleshooting
"Groundmail API keys start with gm_". The API Key setting, or the environment variable it names, holds something else, such as an SMTP credential (gmsmtp_…). Create an API key under API keys.
The email is refused because of the From address. The system email address, and the Status Email Address of any Commerce email, must be on a verified domain.
A recipient never receives the email. The address may be on your suppression list after a hard bounce or complaint. Sends to it return 422. See Suppressions.
Sends are deferred. New accounts have a daily ramp-up limit. See Sending limits.