Browning is a tiny PHP library to send emails with Mailgun, that uses CURL instead of Mailgun's (slightly porky) library. Requires PHP 8.1+ and the cURL extension.
composer require eustasy/browningThen include the Composer autoloader:
require 'vendor/autoload.php';This makes the Eustasy\Browning\Mailer and Eustasy\Browning\Recaptcha classes available.
The cURL extension is required. On Debian/Ubuntu:
sudo apt-get install php-curlSign up for Mailgun — the free tier covers 100 emails per day. See pricing for the paid tiers.
Copy the bundled config template out of vendor/, for example to config/browning.php:
cp vendor/eustasy/browning/config/browning.default.php config/browning.phpOpen your copy and fill in your details — it's a PHP file that returns a config array (loaded with require in step 2):
<?php
return [
// Mailgun API URL
// Replace `<`example.com`>` with your verified Mailgun domain.
'URL' => 'https://api.mailgun.net/v3/example.com',
// Mailgun private API key.
// Get it from "Domain Settings" > "Sending Keys" in your Mailgun dashboard.
'Key' => '<your-mailgun-api-key>',
'Default' => [
'Regards' => 'Example Support', // Sender display name
'ReplyTo' => 'support@example.com', // From / reply-to address
],
// Optional reCAPTCHA v2 keys (see section 3).
'Recaptcha' => [
'SiteKey' => '<your-recaptcha-site-key>',
'SecretKey' => '<your-recaptcha-secret-key>',
],
];Add config/browning.php to your .gitignore to avoid committing credentials.
Build a Mailer from your config and call send():
require 'vendor/autoload.php';
use Eustasy\Browning\Mailer;
$config = require 'config/browning.php';
$mailer = Mailer::fromArray($config);
$result = $mailer->send(
'recipient@example.com', // Required: recipient address
'Message Subject', // Required: subject line
'Text or HTML body', // Required: message body
'Sender Name', // Optional: overrides Default.Regards
'reply-to@example.com', // Optional: overrides Default.ReplyTo
);
if ($result->success) {
echo 'Email sent successfully.';
} else {
echo 'Failed to send email: ' . $result->error;
}send() returns a Eustasy\Browning\Result:
| Property | Type | Description |
|---|---|---|
success |
bool |
true if the email was accepted by Mailgun. |
error |
string|null |
Error message, or null on success. |
By default error is a friendly, user-safe message. Pass debug: true to get technical detail instead:
$mailer = Mailer::fromArray($config, debug: true);Prefer explicit wiring? Construct it directly. The constructor also accepts a custom Transport as its fifth argument —
that's how the tests avoid the network:
$mailer = new Mailer(
apiUrl: 'https://api.mailgun.net/v3/example.com',
apiKey: $secret,
fromName: 'Example Support',
fromAddress: 'support@example.com',
);To protect a form with Google reCAPTCHA v2, add your keys under Recaptcha in the config —
SiteKey for the form, SecretKey for verification.
Keys come from the Google reCAPTCHA admin console.
Add the widget to your HTML form, using your site key:
<script src="https://www.google.com/recaptcha/api.js"></script>
<form method="post" action="">
<input type="email" name="dear" placeholder="Recipient email" required>
<input type="text" name="subject" placeholder="Subject" required>
<textarea name="message" placeholder="Message" required></textarea>
<div class="g-recaptcha" data-sitekey="your-site-key"></div>
<button type="submit">Send</button>
</form>Verify the response server-side before sending:
use Eustasy\Browning\Mailer;
use Eustasy\Browning\Recaptcha;
$config = require 'config/browning.php';
$check = Recaptcha::fromArray($config)->verify(
$_POST['g-recaptcha-response'] ?? null,
$_SERVER['REMOTE_ADDR'] ?? null, // Optional: user's IP address
);
if (! $check->success) {
echo 'reCAPTCHA failed. Please go back and try again.';
} else {
// reCAPTCHA passed — send the email.
$result = Mailer::fromArray($config)->send(
$_POST['dear'] ?? '',
$_POST['subject'] ?? '',
$_POST['message'] ?? '',
);
}verify() returns a Eustasy\Browning\RecaptchaResult with success (bool), errorCodes (string[]), and error (string|null).