Skip to content
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## [3.4.0] - 2025-07-04
- Add Batch sending functionality (transactional, bulk and sandbox)

## [3.3.0] - 2025-06-17
- Add Email Sending Suppressions API

Expand Down
37 changes: 33 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,41 @@
Official Mailtrap PHP client
===============
# Mailtrap PHP client - Official

![GitHub Actions](https://github.com/railsware/mailtrap-php/actions/workflows/ci-phpunit.yml/badge.svg)
![GitHub Actions](https://github.com/railsware/mailtrap-php/actions/workflows/ci-psalm.yaml/badge.svg)

[![PHP version support](https://img.shields.io/packagist/dependency-v/railsware/mailtrap-php/php?style=flat)](https://packagist.org/packages/railsware/mailtrap-php)
[![Latest Version on Packagist](https://img.shields.io/packagist/v/railsware/mailtrap-php.svg?style=flat)](https://packagist.org/packages/railsware/mailtrap-php)
[![Total Downloads](https://img.shields.io/packagist/dt/railsware/mailtrap-php.svg?style=flat)](https://packagist.org/packages/railsware/mailtrap-php)

## Prerequisites

To get the most of this official Mailtrap.io PHP SDK:
- [Create a Mailtrap account](https://mailtrap.io/signup)
- [Verify your domain](https://mailtrap.io/sending/domains)

## Supported functionality

It supports Symphony and Laravel integrations.

Currently with this SDK you can:
- Email API/SMTP
- Send an email (Transactional and Bulk streams)
- Send an email with a Template
- Send a batch of emails (Transactional and Bulk streams)
- Email Sandbox
- Send an email
- Send an email with a template
- Send a batch of emails
- Message management
- Inbox management
- Project management
Comment thread
gaalferov marked this conversation as resolved.
- Contact management
- Contacts CRUD
- Lists CRUD
- General
- Templates CRUD
- Suppressions management (find and delete)


## Installation
You can install the package via [composer](http://getcomposer.org/)
Expand Down Expand Up @@ -56,13 +85,13 @@ $email = (new MailtrapEmail())
->addCc('staging@example.com')
->bcc('mailtrapdev@example.com')
->subject('Best practices of building HTML emails')
->text('Hey! Learn the best practices of building HTML emails and play with ready-to-go templates. Mailtraps Guide on How to Build HTML Email is live on our blog')
->text('Hey! Learn the best practices of building HTML emails and play with ready-to-go templates. Mailtrap's Guide on How to Build HTML Email is live on our blog')
->html(
'<html>
<body>
<p><br>Hey</br>
Learn the best practices of building HTML emails and play with ready-to-go templates.</p>
<p><a href="https://mailtrap.io/blog/build-html-email/">Mailtraps Guide on How to Build HTML Email</a> is live on our blog</p>
<p><a href="https://mailtrap.io/blog/build-html-email/">Mailtrap's Guide on How to Build HTML Email</a> is live on our blog</p>
<img src="cid:logo">
</body>
</html>'
Expand Down
108 changes: 108 additions & 0 deletions examples/sending/emails.php
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,111 @@
} catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), "\n";
}


/**********************************************************************************************************************
******************************************* EMAIL BATCH SENDING *******************************************************
**********************************************************************************************************************
*/

/**
* Email Batch Sending API (Transactional OR Bulk)
*
* Batch send email (text, html, text&html, templates).
* Please note that the endpoint will return a 200-level http status, even when sending for individual messages may fail.
* Users of this endpoint should check the success and errors for each message in the response (the results are ordered the same as the original messages - requests).
* Please note that the endpoint accepts up to 500 messages per API call, and up to 50 MB payload size, including attachments.
*/
try {
// Choose either Transactional API or Bulk API
// For Transactional API
$mailtrap = MailtrapClient::initSendingEmails(
apiKey: getenv('MAILTRAP_API_KEY'), // Your API token from https://mailtrap.io/api-tokens
);

// OR for Bulk API (uncomment the line below and comment out the transactional initialization)
// $mailtrap = MailtrapClient::initSendingEmails(
// apiKey: getenv('MAILTRAP_API_KEY'), // Your API token from https://mailtrap.io/api-tokens
// isBulk: true // Enable bulk sending
//);

$baseEmail = (new MailtrapEmail())
->from(new Address('example@YOUR-DOMAIN-HERE.com', 'Mailtrap Test')) // Use your domain installed in Mailtrap
->subject('Batch Email Subject')
->text('Batch email text')
->html('<p>Batch email text</p>');

$recipientEmails = [
(new MailtrapEmail())->to(new Address('recipient1@example.com', 'Recipient 1')),
(new MailtrapEmail())->to(new Address('recipient2@example.com', 'Recipient 2')),
];

$response = $mailtrap->batchSend($recipientEmails, $baseEmail);

var_dump(ResponseHelper::toArray($response)); // Output response body as array
} catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), "\n";
}


/**
* Email Batch Sending WITH TEMPLATE (Transactional OR Bulk)
*
* WARNING! If a template is provided, then subject, text, html, category and other params are forbidden.
*
* UUID of email template. Subject, text and html will be generated from template using optional template_variables.
* Optional template variables that will be used to generate actual subject, text and html from email template
*/
try {
// Choose either Transactional API or Bulk API
// For Transactional API
$mailtrap = MailtrapClient::initSendingEmails(
apiKey: getenv('MAILTRAP_API_KEY'), // Your API token from https://mailtrap.io/api-tokens
);

// OR for Bulk API (uncomment the line below and comment out the transactional initialization)
// $mailtrap = MailtrapClient::initSendingEmails(
// apiKey: getenv('MAILTRAP_API_KEY'), // Your API token from https://mailtrap.io/api-tokens
// isBulk: true // Enable bulk sending
//);

$baseEmail = (new MailtrapEmail())
->from(new Address('example@YOUR-DOMAIN-HERE.com', 'Mailtrap Test')) // Use your domain installed in Mailtrap
->templateUuid('bfa432fd-0000-0000-0000-8493da283a69') // Template UUID
->templateVariables([
'user_name' => 'Jon Bush',
'next_step_link' => 'https://mailtrap.io/',
'get_started_link' => 'https://mailtrap.io/',
'company' => [
'name' => 'Best Company',
'address' => 'Its Address',
],
'products' => [
[
'name' => 'Product 1',
'price' => 100,
],
[
'name' => 'Product 2',
'price' => 200,
],
],
]);

$recipientEmails = [
(new MailtrapEmail())
->to(new Address('recipient1@example.com', 'Recipient 1'))
// Optional: Override template variables for this recipient
->templateVariables([
'user_name' => 'Custom User 1',
]),
(new MailtrapEmail())
->to(new Address('recipient2@example.com', 'Recipient 2')),
];

$response = $mailtrap->batchSend($recipientEmails, $baseEmail);

var_dump(ResponseHelper::toArray($response)); // Output response body as array
} catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), "\n";
}
95 changes: 95 additions & 0 deletions examples/testing/emails.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,98 @@
} catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), "\n";
}

/**********************************************************************************************************************
******************************************* EMAIL BATCH SENDING *******************************************************
**********************************************************************************************************************
*/

/**
* Test Email Batch
*
* Batch send email (text, html, text&html, templates).
* Please note that the endpoint will return a 200-level http status, even when sending for individual messages may fail.
* Users of this endpoint should check the success and errors for each message in the response (the results are ordered the same as the original messages - requests).
* Please note that the endpoint accepts up to 500 messages per API call, and up to 50 MB payload size, including attachments.
*/
try {
$mailtrap = MailtrapClient::initSendingEmails(
apiKey: getenv('MAILTRAP_API_KEY'), #your API token from here https://mailtrap.io/api-tokens
isSandbox: true, # Sandbox sending (@see https://help.mailtrap.io/article/109-getting-started-with-mailtrap-email-testing)
inboxId: getenv('MAILTRAP_INBOX_ID') # required param for sandbox sending
);

$baseEmail = (new MailtrapEmail())
->from(new Address('example@YOUR-DOMAIN-HERE.com', 'Mailtrap Test')) // Use your domain installed in Mailtrap
->subject('Batch Email Subject')
->text('Batch email text')
->html('<p>Batch email text</p>');

$recipientEmails = [
(new MailtrapEmail())->to(new Address('recipient1@example.com', 'Recipient 1')),
(new MailtrapEmail())->to(new Address('recipient2@example.com', 'Recipient 2')),
];

$response = $mailtrap->batchSend($recipientEmails, $baseEmail);

var_dump(ResponseHelper::toArray($response)); // Output response body as array
} catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), "\n";
}


/**
* Test Email Batch WITH TEMPLATE
*
* WARNING! If a template is provided, then subject, text, html, category and other params are forbidden.
*
* UUID of email template. Subject, text and html will be generated from template using optional template_variables.
* Optional template variables that will be used to generate actual subject, text and html from email template
*/
try {
$mailtrap = MailtrapClient::initSendingEmails(
apiKey: getenv('MAILTRAP_API_KEY'), #your API token from here https://mailtrap.io/api-tokens
isSandbox: true, # Sandbox sending (@see https://help.mailtrap.io/article/109-getting-started-with-mailtrap-email-testing)
inboxId: getenv('MAILTRAP_INBOX_ID') # required param for sandbox sending
);

$baseEmail = (new MailtrapEmail())
->from(new Address('example@YOUR-DOMAIN-HERE.com', 'Mailtrap Test')) // Use your domain installed in Mailtrap
->templateUuid('bfa432fd-0000-0000-0000-8493da283a69') // Template UUID
->templateVariables([
'user_name' => 'Jon Bush',
'next_step_link' => 'https://mailtrap.io/',
'get_started_link' => 'https://mailtrap.io/',
'company' => [
'name' => 'Best Company',
'address' => 'Its Address',
],
'products' => [
[
'name' => 'Product 1',
'price' => 100,
],
[
'name' => 'Product 2',
'price' => 200,
],
],
]);

$recipientEmails = [
(new MailtrapEmail())
->to(new Address('recipient1@example.com', 'Recipient 1'))
// Optional: Override template variables for this recipient
->templateVariables([
'user_name' => 'Custom User 1',
]),
(new MailtrapEmail())
->to(new Address('recipient2@example.com', 'Recipient 2')),
];

$response = $mailtrap->batchSend($recipientEmails, $baseEmail);

var_dump(ResponseHelper::toArray($response)); // Output response body as array
} catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), "\n";
}
70 changes: 64 additions & 6 deletions src/Api/AbstractEmails.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Email;
use Symfony\Component\Mime\Header\Headers;
use Symfony\Component\Mime\Header\MailboxListHeader;

/**
* Class AbstractEmails
Expand All @@ -21,10 +22,15 @@ abstract class AbstractEmails extends AbstractApi implements EmailsSendApiInterf
{
protected function getPayload(Email $email): array
{
$payload = [
'from' => $this->getStringifierAddress($this->getSender($email->getHeaders())),
'to' => array_map([$this, 'getStringifierAddress'], $this->getRecipients($email->getHeaders(), $email)),
];
$payload = [];

if (null !== $this->getSender($email->getHeaders())) {
$payload['from'] = $this->getStringifierAddress($this->getSender($email->getHeaders()));
}

if (!empty($this->getRecipients($email->getHeaders(), $email))) {
$payload['to'] = array_map([$this, 'getStringifierAddress'], $this->getRecipients($email->getHeaders(), $email));
}

if (null !== $email->getSubject()) {
$payload['subject'] = $email->getSubject();
Expand Down Expand Up @@ -89,6 +95,39 @@ protected function getPayload(Email $email): array
return $payload;
}

protected function getBatchBasePayload(Email $email): array
{
$payload = $this->getPayload($email);
if (!empty($payload['to']) || !empty($payload['cc']) || !empty($payload['bcc'])) {
throw new LogicException(
"Batch base email does not support 'to', 'cc', or 'bcc' fields. Please use individual batch email requests to specify recipients."
);
}

if (!empty($this->getFirstReplyTo($email->getHeaders()))) {
$payload['reply_to'] = $this->getStringifierAddress(
$this->getFirstReplyTo($email->getHeaders())
);
}

return $payload;
}

protected function getBatchBody(array $recipientEmails, ?Email $baseEmail = null): array
{
$body = [];
if ($baseEmail !== null) {
$body['base'] = $this->getBatchBasePayload($baseEmail);
}

$body['requests'] = array_map(
fn(Email $email) => $this->getPayload($email),
$recipientEmails
);

return $body;
}

private function getAttachments(Email $email): array
{
$attachments = [];
Expand Down Expand Up @@ -125,7 +164,7 @@ private function getStringifierAddress(Address $address): array
return $res;
}

private function getSender(Headers $headers): Address
private function getSender(Headers $headers): ?Address
{
if ($sender = $headers->get('Sender')) {
return $sender->getAddress();
Expand All @@ -137,7 +176,7 @@ private function getSender(Headers $headers): Address
return $from->getAddresses()[0];
}

throw new LogicException('Unable to determine the sender of the message.');
return null;
}

/**
Expand All @@ -162,4 +201,23 @@ private function getRecipients(Headers $headers, Email $email): array
static fn (Address $address) => false === in_array($address, array_merge($email->getCc(), $email->getBcc()), true)
);
}

/**
* Returns the first address from the 'Reply-To' header, if it exists.
*
* @param Headers $headers
*
* @return Address|null
*/
private function getFirstReplyTo(Headers $headers): ?Address
{
/** @var MailboxListHeader|null $replyToHeader */
$replyToHeader = $headers->get('Reply-To');

if (empty($replyToHeader) || empty($replyToHeader->getAddresses())) {
return null;
}

return $replyToHeader->getAddresses()[0];
}
}
Loading