Skip to content

[3.4.0] Add Batch sending functionality (transactional, bulk and sandbox) #42

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
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
- 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('[email protected]')
->bcc('[email protected]')
->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('[email protected]', '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('[email protected]', 'Recipient 1')),
(new MailtrapEmail())->to(new Address('[email protected]', '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('[email protected]', '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('[email protected]', 'Recipient 1'))
// Optional: Override template variables for this recipient
->templateVariables([
'user_name' => 'Custom User 1',
]),
(new MailtrapEmail())
->to(new Address('[email protected]', '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('[email protected]', '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('[email protected]', 'Recipient 1')),
(new MailtrapEmail())->to(new Address('[email protected]', '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('[email protected]', '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('[email protected]', 'Recipient 1'))
// Optional: Override template variables for this recipient
->templateVariables([
'user_name' => 'Custom User 1',
]),
(new MailtrapEmail())
->to(new Address('[email protected]', '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