Skip to content

Commit 87f2116

Browse files
authored
Merge pull request #42 from railsware/feature/batch-send
[3.4.0] Add Batch sending functionality (transactional, bulk and sandbox)
2 parents 86a7659 + 071b08c commit 87f2116

File tree

13 files changed

+934
-12
lines changed

13 files changed

+934
-12
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
## [3.4.0] - 2025-07-04
2+
- Add Batch sending functionality (transactional, bulk and sandbox)
3+
14
## [3.3.0] - 2025-06-17
25
- Add Email Sending Suppressions API
36

README.md

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,41 @@
1-
Official Mailtrap PHP client
2-
===============
1+
# Mailtrap PHP client - Official
2+
33
![GitHub Actions](https://github.com/railsware/mailtrap-php/actions/workflows/ci-phpunit.yml/badge.svg)
44
![GitHub Actions](https://github.com/railsware/mailtrap-php/actions/workflows/ci-psalm.yaml/badge.svg)
55

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

10+
## Prerequisites
11+
12+
To get the most of this official Mailtrap.io PHP SDK:
13+
- [Create a Mailtrap account](https://mailtrap.io/signup)
14+
- [Verify your domain](https://mailtrap.io/sending/domains)
15+
16+
## Supported functionality
17+
18+
It supports Symphony and Laravel integrations.
19+
20+
Currently with this SDK you can:
21+
- Email API/SMTP
22+
- Send an email (Transactional and Bulk streams)
23+
- Send an email with a Template
24+
- Send a batch of emails (Transactional and Bulk streams)
25+
- Email Sandbox
26+
- Send an email
27+
- Send an email with a template
28+
- Send a batch of emails
29+
- Message management
30+
- Inbox management
31+
- Project management
32+
- Contact management
33+
- Contacts CRUD
34+
- Lists CRUD
35+
- General
36+
- Templates CRUD
37+
- Suppressions management (find and delete)
38+
1039

1140
## Installation
1241
You can install the package via [composer](http://getcomposer.org/)
@@ -56,13 +85,13 @@ $email = (new MailtrapEmail())
5685
->addCc('[email protected]')
5786
5887
->subject('Best practices of building HTML emails')
59-
->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')
88+
->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')
6089
->html(
6190
'<html>
6291
<body>
6392
<p><br>Hey</br>
6493
Learn the best practices of building HTML emails and play with ready-to-go templates.</p>
65-
<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>
94+
<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>
6695
<img src="cid:logo">
6796
</body>
6897
</html>'

examples/sending/emails.php

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,3 +226,111 @@
226226
} catch (Exception $e) {
227227
echo 'Caught exception: ', $e->getMessage(), "\n";
228228
}
229+
230+
231+
/**********************************************************************************************************************
232+
******************************************* EMAIL BATCH SENDING *******************************************************
233+
**********************************************************************************************************************
234+
*/
235+
236+
/**
237+
* Email Batch Sending API (Transactional OR Bulk)
238+
*
239+
* Batch send email (text, html, text&html, templates).
240+
* Please note that the endpoint will return a 200-level http status, even when sending for individual messages may fail.
241+
* 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).
242+
* Please note that the endpoint accepts up to 500 messages per API call, and up to 50 MB payload size, including attachments.
243+
*/
244+
try {
245+
// Choose either Transactional API or Bulk API
246+
// For Transactional API
247+
$mailtrap = MailtrapClient::initSendingEmails(
248+
apiKey: getenv('MAILTRAP_API_KEY'), // Your API token from https://mailtrap.io/api-tokens
249+
);
250+
251+
// OR for Bulk API (uncomment the line below and comment out the transactional initialization)
252+
// $mailtrap = MailtrapClient::initSendingEmails(
253+
// apiKey: getenv('MAILTRAP_API_KEY'), // Your API token from https://mailtrap.io/api-tokens
254+
// isBulk: true // Enable bulk sending
255+
//);
256+
257+
$baseEmail = (new MailtrapEmail())
258+
->from(new Address('[email protected]', 'Mailtrap Test')) // Use your domain installed in Mailtrap
259+
->subject('Batch Email Subject')
260+
->text('Batch email text')
261+
->html('<p>Batch email text</p>');
262+
263+
$recipientEmails = [
264+
(new MailtrapEmail())->to(new Address('[email protected]', 'Recipient 1')),
265+
(new MailtrapEmail())->to(new Address('[email protected]', 'Recipient 2')),
266+
];
267+
268+
$response = $mailtrap->batchSend($recipientEmails, $baseEmail);
269+
270+
var_dump(ResponseHelper::toArray($response)); // Output response body as array
271+
} catch (Exception $e) {
272+
echo 'Caught exception: ', $e->getMessage(), "\n";
273+
}
274+
275+
276+
/**
277+
* Email Batch Sending WITH TEMPLATE (Transactional OR Bulk)
278+
*
279+
* WARNING! If a template is provided, then subject, text, html, category and other params are forbidden.
280+
*
281+
* UUID of email template. Subject, text and html will be generated from template using optional template_variables.
282+
* Optional template variables that will be used to generate actual subject, text and html from email template
283+
*/
284+
try {
285+
// Choose either Transactional API or Bulk API
286+
// For Transactional API
287+
$mailtrap = MailtrapClient::initSendingEmails(
288+
apiKey: getenv('MAILTRAP_API_KEY'), // Your API token from https://mailtrap.io/api-tokens
289+
);
290+
291+
// OR for Bulk API (uncomment the line below and comment out the transactional initialization)
292+
// $mailtrap = MailtrapClient::initSendingEmails(
293+
// apiKey: getenv('MAILTRAP_API_KEY'), // Your API token from https://mailtrap.io/api-tokens
294+
// isBulk: true // Enable bulk sending
295+
//);
296+
297+
$baseEmail = (new MailtrapEmail())
298+
->from(new Address('[email protected]', 'Mailtrap Test')) // Use your domain installed in Mailtrap
299+
->templateUuid('bfa432fd-0000-0000-0000-8493da283a69') // Template UUID
300+
->templateVariables([
301+
'user_name' => 'Jon Bush',
302+
'next_step_link' => 'https://mailtrap.io/',
303+
'get_started_link' => 'https://mailtrap.io/',
304+
'company' => [
305+
'name' => 'Best Company',
306+
'address' => 'Its Address',
307+
],
308+
'products' => [
309+
[
310+
'name' => 'Product 1',
311+
'price' => 100,
312+
],
313+
[
314+
'name' => 'Product 2',
315+
'price' => 200,
316+
],
317+
],
318+
]);
319+
320+
$recipientEmails = [
321+
(new MailtrapEmail())
322+
->to(new Address('[email protected]', 'Recipient 1'))
323+
// Optional: Override template variables for this recipient
324+
->templateVariables([
325+
'user_name' => 'Custom User 1',
326+
]),
327+
(new MailtrapEmail())
328+
->to(new Address('[email protected]', 'Recipient 2')),
329+
];
330+
331+
$response = $mailtrap->batchSend($recipientEmails, $baseEmail);
332+
333+
var_dump(ResponseHelper::toArray($response)); // Output response body as array
334+
} catch (Exception $e) {
335+
echo 'Caught exception: ', $e->getMessage(), "\n";
336+
}

examples/testing/emails.php

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,98 @@
124124
} catch (Exception $e) {
125125
echo 'Caught exception: ', $e->getMessage(), "\n";
126126
}
127+
128+
/**********************************************************************************************************************
129+
******************************************* EMAIL BATCH SENDING *******************************************************
130+
**********************************************************************************************************************
131+
*/
132+
133+
/**
134+
* Test Email Batch
135+
*
136+
* Batch send email (text, html, text&html, templates).
137+
* Please note that the endpoint will return a 200-level http status, even when sending for individual messages may fail.
138+
* 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).
139+
* Please note that the endpoint accepts up to 500 messages per API call, and up to 50 MB payload size, including attachments.
140+
*/
141+
try {
142+
$mailtrap = MailtrapClient::initSendingEmails(
143+
apiKey: getenv('MAILTRAP_API_KEY'), #your API token from here https://mailtrap.io/api-tokens
144+
isSandbox: true, # Sandbox sending (@see https://help.mailtrap.io/article/109-getting-started-with-mailtrap-email-testing)
145+
inboxId: getenv('MAILTRAP_INBOX_ID') # required param for sandbox sending
146+
);
147+
148+
$baseEmail = (new MailtrapEmail())
149+
->from(new Address('[email protected]', 'Mailtrap Test')) // Use your domain installed in Mailtrap
150+
->subject('Batch Email Subject')
151+
->text('Batch email text')
152+
->html('<p>Batch email text</p>');
153+
154+
$recipientEmails = [
155+
(new MailtrapEmail())->to(new Address('[email protected]', 'Recipient 1')),
156+
(new MailtrapEmail())->to(new Address('[email protected]', 'Recipient 2')),
157+
];
158+
159+
$response = $mailtrap->batchSend($recipientEmails, $baseEmail);
160+
161+
var_dump(ResponseHelper::toArray($response)); // Output response body as array
162+
} catch (Exception $e) {
163+
echo 'Caught exception: ', $e->getMessage(), "\n";
164+
}
165+
166+
167+
/**
168+
* Test Email Batch WITH TEMPLATE
169+
*
170+
* WARNING! If a template is provided, then subject, text, html, category and other params are forbidden.
171+
*
172+
* UUID of email template. Subject, text and html will be generated from template using optional template_variables.
173+
* Optional template variables that will be used to generate actual subject, text and html from email template
174+
*/
175+
try {
176+
$mailtrap = MailtrapClient::initSendingEmails(
177+
apiKey: getenv('MAILTRAP_API_KEY'), #your API token from here https://mailtrap.io/api-tokens
178+
isSandbox: true, # Sandbox sending (@see https://help.mailtrap.io/article/109-getting-started-with-mailtrap-email-testing)
179+
inboxId: getenv('MAILTRAP_INBOX_ID') # required param for sandbox sending
180+
);
181+
182+
$baseEmail = (new MailtrapEmail())
183+
->from(new Address('[email protected]', 'Mailtrap Test')) // Use your domain installed in Mailtrap
184+
->templateUuid('bfa432fd-0000-0000-0000-8493da283a69') // Template UUID
185+
->templateVariables([
186+
'user_name' => 'Jon Bush',
187+
'next_step_link' => 'https://mailtrap.io/',
188+
'get_started_link' => 'https://mailtrap.io/',
189+
'company' => [
190+
'name' => 'Best Company',
191+
'address' => 'Its Address',
192+
],
193+
'products' => [
194+
[
195+
'name' => 'Product 1',
196+
'price' => 100,
197+
],
198+
[
199+
'name' => 'Product 2',
200+
'price' => 200,
201+
],
202+
],
203+
]);
204+
205+
$recipientEmails = [
206+
(new MailtrapEmail())
207+
->to(new Address('[email protected]', 'Recipient 1'))
208+
// Optional: Override template variables for this recipient
209+
->templateVariables([
210+
'user_name' => 'Custom User 1',
211+
]),
212+
(new MailtrapEmail())
213+
->to(new Address('[email protected]', 'Recipient 2')),
214+
];
215+
216+
$response = $mailtrap->batchSend($recipientEmails, $baseEmail);
217+
218+
var_dump(ResponseHelper::toArray($response)); // Output response body as array
219+
} catch (Exception $e) {
220+
echo 'Caught exception: ', $e->getMessage(), "\n";
221+
}

src/Api/AbstractEmails.php

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Symfony\Component\Mime\Address;
1414
use Symfony\Component\Mime\Email;
1515
use Symfony\Component\Mime\Header\Headers;
16+
use Symfony\Component\Mime\Header\MailboxListHeader;
1617

1718
/**
1819
* Class AbstractEmails
@@ -21,10 +22,15 @@ abstract class AbstractEmails extends AbstractApi implements EmailsSendApiInterf
2122
{
2223
protected function getPayload(Email $email): array
2324
{
24-
$payload = [
25-
'from' => $this->getStringifierAddress($this->getSender($email->getHeaders())),
26-
'to' => array_map([$this, 'getStringifierAddress'], $this->getRecipients($email->getHeaders(), $email)),
27-
];
25+
$payload = [];
26+
27+
if (null !== $this->getSender($email->getHeaders())) {
28+
$payload['from'] = $this->getStringifierAddress($this->getSender($email->getHeaders()));
29+
}
30+
31+
if (!empty($this->getRecipients($email->getHeaders(), $email))) {
32+
$payload['to'] = array_map([$this, 'getStringifierAddress'], $this->getRecipients($email->getHeaders(), $email));
33+
}
2834

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

98+
protected function getBatchBasePayload(Email $email): array
99+
{
100+
$payload = $this->getPayload($email);
101+
if (!empty($payload['to']) || !empty($payload['cc']) || !empty($payload['bcc'])) {
102+
throw new LogicException(
103+
"Batch base email does not support 'to', 'cc', or 'bcc' fields. Please use individual batch email requests to specify recipients."
104+
);
105+
}
106+
107+
if (!empty($this->getFirstReplyTo($email->getHeaders()))) {
108+
$payload['reply_to'] = $this->getStringifierAddress(
109+
$this->getFirstReplyTo($email->getHeaders())
110+
);
111+
}
112+
113+
return $payload;
114+
}
115+
116+
protected function getBatchBody(array $recipientEmails, ?Email $baseEmail = null): array
117+
{
118+
$body = [];
119+
if ($baseEmail !== null) {
120+
$body['base'] = $this->getBatchBasePayload($baseEmail);
121+
}
122+
123+
$body['requests'] = array_map(
124+
fn(Email $email) => $this->getPayload($email),
125+
$recipientEmails
126+
);
127+
128+
return $body;
129+
}
130+
92131
private function getAttachments(Email $email): array
93132
{
94133
$attachments = [];
@@ -125,7 +164,7 @@ private function getStringifierAddress(Address $address): array
125164
return $res;
126165
}
127166

128-
private function getSender(Headers $headers): Address
167+
private function getSender(Headers $headers): ?Address
129168
{
130169
if ($sender = $headers->get('Sender')) {
131170
return $sender->getAddress();
@@ -137,7 +176,7 @@ private function getSender(Headers $headers): Address
137176
return $from->getAddresses()[0];
138177
}
139178

140-
throw new LogicException('Unable to determine the sender of the message.');
179+
return null;
141180
}
142181

143182
/**
@@ -162,4 +201,23 @@ private function getRecipients(Headers $headers, Email $email): array
162201
static fn (Address $address) => false === in_array($address, array_merge($email->getCc(), $email->getBcc()), true)
163202
);
164203
}
204+
205+
/**
206+
* Returns the first address from the 'Reply-To' header, if it exists.
207+
*
208+
* @param Headers $headers
209+
*
210+
* @return Address|null
211+
*/
212+
private function getFirstReplyTo(Headers $headers): ?Address
213+
{
214+
/** @var MailboxListHeader|null $replyToHeader */
215+
$replyToHeader = $headers->get('Reply-To');
216+
217+
if (empty($replyToHeader) || empty($replyToHeader->getAddresses())) {
218+
return null;
219+
}
220+
221+
return $replyToHeader->getAddresses()[0];
222+
}
165223
}

0 commit comments

Comments
 (0)