├── .github
└── workflows
│ └── tests.yml
├── .gitignore
├── Dockerfile
├── README.md
├── composer.json
├── ecs.php
├── phpstan.neon
├── phpunit.xml
├── src
├── ApiException.php
├── Client.php
├── HttpClientFactory.php
├── Messages
│ ├── Delivery.php
│ └── Message.php
├── MessagesService.php
├── RetryMiddlewareFactory.php
├── Send
│ ├── Message.php
│ ├── RawMessage.php
│ └── Result.php
└── SendService.php
└── tests
├── ClientTest.php
├── MessagesServiceTest.php
├── Send
└── MessageTest.php
└── SendServiceTest.php
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 | strategy:
9 | fail-fast: false
10 | matrix:
11 | php: [7.4, "8.0", 8.1, 8.2, 8.3]
12 |
13 | steps:
14 | - uses: actions/checkout@v3
15 |
16 | - name: test on PHP ${{ matrix.php }}
17 | run: docker build . --build-arg PHP_VERSION=${{ matrix.php }}
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor/
2 | .DS_Store
3 | test.php
4 | composer.lock
5 | *.cache
6 | coverage
7 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | ARG PHP_VERSION=8.3
2 | FROM php:$PHP_VERSION-cli-alpine
3 |
4 | RUN apk add git zip unzip autoconf make g++ icu-dev
5 |
6 | RUN docker-php-ext-configure intl \
7 | && docker-php-ext-install -j $(nproc) intl
8 |
9 | RUN curl -sS https://getcomposer.org/installer | php \
10 | && mv composer.phar /usr/local/bin/composer
11 |
12 | RUN adduser -S php
13 |
14 | WORKDIR /package
15 |
16 | RUN chown php /package
17 |
18 | USER php
19 |
20 | COPY composer.json ./
21 |
22 | RUN composer install
23 |
24 | COPY src src
25 | COPY tests tests
26 | COPY ecs.php phpunit.xml phpstan.neon ./
27 |
28 | RUN composer test
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Postal for PHP
2 |
3 | This library helps you send e-mails through [Postal](https://github.com/postalserver/postal) in PHP 7.4 and above.
4 |
5 | ## Installation
6 |
7 | Install the library using [Composer](https://getcomposer.org/):
8 |
9 | ```
10 | $ composer require postal/postal
11 | ```
12 |
13 | ## Usage
14 |
15 | Sending an email is very simple. Just follow the example below. Before you can begin, you'll
16 | need to login to our web interface and generate a new API credential.
17 |
18 | ```php
19 | // Create a new Postal client using the server key you generate in the web interface
20 | $client = new Postal\Client('https://postal.yourdomain.com', 'your-api-key');
21 |
22 | // Create a new message
23 | $message = new Postal\Send\Message();
24 |
25 | // Add some recipients
26 | $message->to('john@example.com');
27 | $message->to('mary@example.com');
28 | $message->cc('mike@example.com');
29 | $message->bcc('secret@awesomeapp.com');
30 |
31 | // Specify who the message should be from. This must be from a verified domain
32 | // on your mail server.
33 | $message->from('test@test.postal.io');
34 |
35 | // Set the subject
36 | $message->subject('Hi there!');
37 |
38 | // Set the content for the e-mail
39 | $message->plainBody('Hello world!');
40 | $message->htmlBody('
Hello world!
');
41 |
42 | // Add any custom headers
43 | $message->header('X-PHP-Test', 'value');
44 |
45 | // Attach any files
46 | $message->attach('textmessage.txt', 'text/plain', 'Hello world!');
47 |
48 | // Send the message and get the result
49 | $result = $client->send->message($message);
50 |
51 | // Loop through each of the recipients to get the message ID
52 | foreach ($result->recipients() as $email => $message) {
53 | $email; // The e-mail address of the recipient
54 | $message->id; // The message ID
55 | $message->token; // The message's token
56 | }
57 | ```
58 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "postal/postal",
3 | "description": "Postal for PHP library.",
4 | "keywords": ["postal", "mail"],
5 | "license": "MIT",
6 | "homepage":"https://github.com/atech/postal",
7 | "authors": [
8 | {
9 | "name": "Adam Cooke",
10 | "email": "me@adamcooke.io"
11 | },
12 | {
13 | "name": "Josh Grant",
14 | "email": "josh@grantj.io"
15 | },
16 | {
17 | "name": "William Hall",
18 | "email": "william.hall@synergitech.co.uk"
19 | }
20 | ],
21 | "type": "library",
22 | "require": {
23 | "php": "^7.4 || ^8.0",
24 | "guzzlehttp/guzzle": "^6 || ^7"
25 | },
26 | "autoload": {
27 | "psr-4": {
28 | "Postal\\": "src/"
29 | }
30 | },
31 | "require-dev": {
32 | "phpstan/phpstan": "^1.10",
33 | "phpunit/phpunit": "^9.6",
34 | "symplify/easy-coding-standard": "^11.3"
35 | },
36 | "scripts": {
37 | "test": [
38 | "ecs",
39 | "phpunit",
40 | "phpstan"
41 | ]
42 | },
43 | "config": {
44 | "platform": {
45 | "php": "7.4"
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/ecs.php:
--------------------------------------------------------------------------------
1 | paths([__DIR__ . '/src', __DIR__ . '/tests']);
8 |
9 | $ecsConfig->sets([
10 | SetList::ARRAY,
11 | SetList::CLEAN_CODE,
12 | SetList::CONTROL_STRUCTURES,
13 | SetList::DOCBLOCK,
14 | SetList::NAMESPACES,
15 | SetList::PSR_12,
16 | SetList::SPACES,
17 | ]);
18 | };
19 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | level: 9
3 | paths:
4 | - src
5 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 | ./tests
10 |
11 |
12 |
13 |
14 | ./src
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/ApiException.php:
--------------------------------------------------------------------------------
1 | httpClient = $httpClient ?: HttpClientFactory::create($host, $apiKey);
24 | $this->messages = new MessagesService($this);
25 | $this->send = new SendService($this);
26 | }
27 |
28 | public function getHttpClient(): HttpClient
29 | {
30 | return $this->httpClient;
31 | }
32 |
33 | /**
34 | * @template T
35 | * @param class-string $class
36 | * @return T
37 | */
38 | public function prepareResponse(ResponseInterface $response, $class)
39 | {
40 | return new $class($this->validateResponse($response));
41 | }
42 |
43 | /**
44 | * @template T
45 | * @param class-string $class
46 | * @return array
47 | */
48 | public function prepareListResponse(ResponseInterface $response, $class)
49 | {
50 | $list = $this->validateResponse($response);
51 |
52 | // if (! array_is_list($list)) {
53 | if (! $this->arrayIsList($list)) {
54 | throw new ApiException('Unexpected response received, expected a list');
55 | }
56 |
57 | return array_map(fn ($item) => new $class($item), $list);
58 | }
59 |
60 | /**
61 | * @return array
62 | */
63 | protected function validateResponse(ResponseInterface $response): array
64 | {
65 | $json = json_decode((string) $response->getBody(), true);
66 |
67 | if (json_last_error() !== JSON_ERROR_NONE || ! is_array($json)) {
68 | throw new ApiException('Malformed response body received');
69 | }
70 |
71 | if (! isset($json['status']) || $json['status'] !== 'success') {
72 | $message = $json['data']['message'] ?? 'An unexpected error was received';
73 | $code = 0;
74 | if (isset($json['data']['code'])) {
75 | $message = $json['data']['code'] . ': ' . $message;
76 | }
77 |
78 | throw new ApiException($message);
79 | }
80 |
81 | if (! isset($json['data'])) {
82 | throw new ApiException('Unexpected response received');
83 | }
84 |
85 | return $json['data'];
86 | }
87 |
88 | /**
89 | * @param array $array
90 | */
91 | private function arrayIsList(array $array): bool
92 | {
93 | $i = 0;
94 | foreach ($array as $k => $v) {
95 | if ($k !== $i++) {
96 | return false;
97 | }
98 | }
99 |
100 | return true;
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/HttpClientFactory.php:
--------------------------------------------------------------------------------
1 | push(RetryMiddlewareFactory::build());
17 | }
18 |
19 | return new Client([
20 | 'base_uri' => "{$host}/api/v1/",
21 | 'headers' => [
22 | 'X-Server-API-Key' => $apiKey,
23 | ],
24 | 'handler' => $handler,
25 | ]);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Messages/Delivery.php:
--------------------------------------------------------------------------------
1 | id = $attributes['id'];
33 | $this->status = $attributes['status'];
34 | $this->details = $attributes['details'];
35 | $this->output = $attributes['output'];
36 | $this->sent_with_ssl = $attributes['sent_with_ssl'];
37 | $this->log_id = $attributes['log_id'];
38 | $this->time = $attributes['time'];
39 |
40 | $this->timestamp = new DateTime('@' . $attributes['timestamp']);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Messages/Message.php:
--------------------------------------------------------------------------------
1 | $attributes
57 | */
58 | public function __construct(array $attributes)
59 | {
60 | if (! is_int($attributes['id'])) {
61 | throw new ApiException('Unexpected API response, expected an integer ID');
62 | }
63 | if (! is_string($attributes['token'])) {
64 | throw new ApiException('Unexpected API response, expected a string token');
65 | }
66 |
67 | $this->id = $attributes['id'];
68 | $this->token = $attributes['token'];
69 | $this->status = $attributes['status'] ?? null;
70 | $this->details = $attributes['details'] ?? null;
71 | $this->inspection = $attributes['inspection'] ?? null;
72 | $this->plain_body = $attributes['plain_body'] ?? null;
73 | $this->html_body = $attributes['html_body'] ?? null;
74 | $this->attachments = $attributes['attachments'] ?? null;
75 | $this->headers = $attributes['headers'] ?? null;
76 | $this->raw_message = $attributes['raw_message'] ?? null;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/MessagesService.php:
--------------------------------------------------------------------------------
1 | client = $client;
17 | }
18 |
19 | /**
20 | * @param array|true $expansions
21 | */
22 | public function details(int $id, $expansions = []): Message
23 | {
24 | return $this->client->prepareResponse(
25 | $this->client->getHttpClient()->post('messages/message', [
26 | 'json' => [
27 | 'id' => $id,
28 | '_expansions' => $expansions,
29 | ],
30 | ]),
31 | Message::class,
32 | );
33 | }
34 |
35 | /**
36 | * @return array
37 | */
38 | public function deliveries(int $id): array
39 | {
40 | return $this->client->prepareListResponse(
41 | $this->client->getHttpClient()->post('messages/deliveries', [
42 | 'json' => [
43 | 'id' => $id,
44 | ],
45 | ]),
46 | Delivery::class,
47 | );
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/RetryMiddlewareFactory.php:
--------------------------------------------------------------------------------
1 | self::$maxRetries) {
32 | return false;
33 | }
34 |
35 | if (self::isConnectionError($exception)) {
36 | return true;
37 | }
38 |
39 | if (self::isInternalServerError($exception) && $request->getMethod() === 'GET') {
40 | return true;
41 | }
42 |
43 | return false;
44 | };
45 | }
46 |
47 | public static function buildDelay(): callable
48 | {
49 | return function (
50 | $retries,
51 | Response $response = null
52 | ) {
53 | return self::$defaultRetryDelay;
54 | };
55 | }
56 |
57 | private static function isConnectionError(\Throwable $exception = null): bool
58 | {
59 | return $exception instanceof \GuzzleHttp\Exception\ConnectException;
60 | }
61 |
62 | private static function isInternalServerError(\Throwable $exception = null): bool
63 | {
64 | return $exception instanceof \GuzzleHttp\Exception\ServerException;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Send/Message.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | public array $to = [];
13 |
14 | /**
15 | * @var array
16 | */
17 | public array $cc = [];
18 |
19 | /**
20 | * @var array
21 | */
22 | public array $bcc = [];
23 |
24 | public ?string $from = null;
25 |
26 | public ?string $sender = null;
27 |
28 | public ?string $subject = null;
29 |
30 | public ?string $tag = null;
31 |
32 | public ?string $reply_to = null;
33 |
34 | public ?string $plain_body = null;
35 |
36 | public ?string $html_body = null;
37 |
38 | /**
39 | * @var array
40 | */
41 | public ?array $headers = null;
42 |
43 | /**
44 | * @var array>
45 | */
46 | public array $attachments = [];
47 |
48 | public function to(string $address): self
49 | {
50 | $this->to[] = $address;
51 |
52 | return $this;
53 | }
54 |
55 | public function cc(string $address): self
56 | {
57 | $this->cc[] = $address;
58 |
59 | return $this;
60 | }
61 |
62 | public function bcc(string $address): self
63 | {
64 | $this->bcc[] = $address;
65 |
66 | return $this;
67 | }
68 |
69 | public function from(string $address): self
70 | {
71 | $this->from = $address;
72 |
73 | return $this;
74 | }
75 |
76 | public function sender(string $address): self
77 | {
78 | $this->sender = $address;
79 |
80 | return $this;
81 | }
82 |
83 | public function subject(string $subject): self
84 | {
85 | $this->subject = $subject;
86 |
87 | return $this;
88 | }
89 |
90 | public function tag(string $tag): self
91 | {
92 | $this->tag = $tag;
93 |
94 | return $this;
95 | }
96 |
97 | public function replyTo(string $replyTo): self
98 | {
99 | $this->reply_to = $replyTo;
100 |
101 | return $this;
102 | }
103 |
104 | public function plainBody(string $content): self
105 | {
106 | $this->plain_body = $content;
107 |
108 | return $this;
109 | }
110 |
111 | public function htmlBody(string $content): self
112 | {
113 | $this->html_body = $content;
114 |
115 | return $this;
116 | }
117 |
118 | public function header(string $key, string $value): self
119 | {
120 | if ($this->headers === null) {
121 | $this->headers = [];
122 | }
123 |
124 | $this->headers[$key] = $value;
125 |
126 | return $this;
127 | }
128 |
129 | public function attach(string $filename, string $content_type, string $data): self
130 | {
131 | $attachment = [
132 | 'name' => $filename,
133 | 'content_type' => $content_type,
134 | 'data' => base64_encode($data),
135 | ];
136 |
137 | $this->attachments[] = $attachment;
138 |
139 | return $this;
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/Send/RawMessage.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | public array $rcpt_to = [];
13 |
14 | public ?string $mail_from = null;
15 |
16 | public ?string $data = null;
17 |
18 | public function mailFrom(string $address): self
19 | {
20 | $this->mail_from = $address;
21 |
22 | return $this;
23 | }
24 |
25 | public function rcptTo(string $address): self
26 | {
27 | $this->rcpt_to[] = $address;
28 |
29 | return $this;
30 | }
31 |
32 | public function data(string $data): self
33 | {
34 | $this->data = base64_encode($data);
35 |
36 | return $this;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Send/Result.php:
--------------------------------------------------------------------------------
1 |
15 | */
16 | public array $messages;
17 |
18 | /**
19 | * @param array{message_id: string, messages: array>} $attributes
20 | */
21 | public function __construct(array $attributes)
22 | {
23 | $this->message_id = $attributes['message_id'];
24 | $this->messages = array_change_key_case(
25 | array_map(fn ($message) => new Message($message), $attributes['messages'])
26 | );
27 | }
28 |
29 | /**
30 | * @return array
31 | */
32 | public function recipients(): array
33 | {
34 | return $this->messages;
35 | }
36 |
37 | public function size(): int
38 | {
39 | return count($this->messages);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/SendService.php:
--------------------------------------------------------------------------------
1 | client = $client;
18 | }
19 |
20 | public function message(Message $message): Result
21 | {
22 | return $this->client->prepareResponse(
23 | $this->client->getHttpClient()->post('send/message', [
24 | 'json' => $message,
25 | ]),
26 | Result::class,
27 | );
28 | }
29 |
30 | public function raw(RawMessage $message): Result
31 | {
32 | return $this->client->prepareResponse(
33 | $this->client->getHttpClient()->post('send/raw', [
34 | 'json' => $message,
35 | ]),
36 | Result::class,
37 | );
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tests/ClientTest.php:
--------------------------------------------------------------------------------
1 | expectException(ApiException::class);
20 | $this->expectExceptionMessage('Malformed response body received');
21 |
22 | $client->prepareResponse($response, new class() {
23 | });
24 | }
25 |
26 | public function testPrepareResponseThrowsJsonNotArray(): void
27 | {
28 | $client = new Client('', '');
29 | $response = new Response(200, [], '"test"');
30 |
31 | $this->expectException(ApiException::class);
32 | $this->expectExceptionMessage('Malformed response body received');
33 |
34 | $client->prepareResponse($response, new class() {
35 | });
36 | }
37 |
38 | public function testPrepareResponseThrowsApiErrors(): void
39 | {
40 | $client = new Client('', '');
41 | $response = new Response(200, [], json_encode([
42 | 'status' => 'error',
43 | 'data' => [
44 | 'code' => 'TestExceptionCode',
45 | 'message' => 'my-test-error',
46 | ],
47 | ]));
48 |
49 | $this->expectException(ApiException::class);
50 | $this->expectExceptionMessage('TestExceptionCode: my-test-error');
51 |
52 | $client->prepareResponse($response, new class() {
53 | });
54 | }
55 |
56 | public function testPrepareResponseThrowsWithoutResponse(): void
57 | {
58 | $client = new Client('', '');
59 | $response = new Response(200, [], json_encode([
60 | 'status' => 'success',
61 | ]));
62 |
63 | $this->expectException(ApiException::class);
64 | $this->expectExceptionMessage('Unexpected response received');
65 |
66 | $client->prepareResponse($response, new class() {
67 | });
68 | }
69 |
70 | public function testPrepareResponseCoercesToClass(): void
71 | {
72 | $client = new Client('', '');
73 | $response = new Response(200, [], json_encode([
74 | 'status' => 'success',
75 | 'data' => [
76 | 'id' => 123,
77 | 'string' => 'abc',
78 | ],
79 | ]));
80 |
81 | $result = $client->prepareResponse($response, new class([]) {
82 | public ?int $id;
83 |
84 | public ?string $string;
85 |
86 | public function __construct($attributes)
87 | {
88 | $this->id = $attributes['id'] ?? null;
89 | $this->string = $attributes['string'] ?? null;
90 | }
91 | });
92 |
93 | $this->assertSame(123, $result->id);
94 | $this->assertSame('abc', $result->string);
95 | }
96 |
97 | public function testPrepareListResponseCoercesToClass(): void
98 | {
99 | $client = new Client('', '');
100 | $response = new Response(200, [], json_encode([
101 | 'status' => 'success',
102 | 'data' => [
103 | [
104 | 'id' => 123,
105 | 'string' => 'abc',
106 | ],
107 | [
108 | 'id' => 456,
109 | 'string' => 'def',
110 | ],
111 | ],
112 | ]));
113 |
114 | $result = $client->prepareListResponse($response, new class([]) {
115 | public ?int $id;
116 |
117 | public ?string $string;
118 |
119 | public function __construct($attributes)
120 | {
121 | $this->id = $attributes['id'] ?? null;
122 | $this->string = $attributes['string'] ?? null;
123 | }
124 | });
125 |
126 | $this->assertCount(2, $result);
127 | $this->assertSame(123, $result[0]->id);
128 | $this->assertSame('abc', $result[0]->string);
129 | $this->assertSame(456, $result[1]->id);
130 | $this->assertSame('def', $result[1]->string);
131 | }
132 |
133 | public function testPrepareListResponseThrowsWithoutList(): void
134 | {
135 | $client = new Client('', '');
136 | $response = new Response(200, [], json_encode([
137 | 'status' => 'success',
138 | 'data' => [
139 | 'string' => 'this is not a list',
140 | ],
141 | ]));
142 |
143 | $this->expectException(ApiException::class);
144 | $this->expectExceptionMessage('Unexpected response received, expected a list');
145 |
146 | $client->prepareListResponse($response, new class() {
147 | });
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/tests/MessagesServiceTest.php:
--------------------------------------------------------------------------------
1 | 'success',
22 | 'data' => [
23 | 'id' => 123,
24 | 'token' => 'abc',
25 | 'status' => [
26 | 'status' => 'Held',
27 | 'last_delivery_attempt' => 1666083425.913461,
28 | 'held' => true,
29 | 'hold_expiry' => 1666688225.924133,
30 | ],
31 | 'details' => [
32 | 'rcpt_to' => 'rcpt_to@example.com',
33 | 'mail_from' => 'mail_from@example.com',
34 | 'subject' => 'my subject',
35 | 'message_id' => '969c4ad7-4cb1-464c-bdfd-14e9995342d3@example.com',
36 | ],
37 | 'inspection' => [
38 | 'inspected' => true,
39 | 'spam' => false,
40 | 'spam_score' => 0,
41 | 'threat' => false,
42 | 'threat_details' => null,
43 | ],
44 | 'plain_body' => 'Plain Body',
45 | 'html_body' => 'HTML Body
',
46 | 'attachments' => [
47 | [
48 | 'filename' => 'file.txt',
49 | 'content_type' => 'text/plain',
50 | 'data' => 'dGHzdA==',
51 | 'size' => 4,
52 | 'hash' => 'a94a8fe5ccb19ba61c4c0873d391e987982fbbd3',
53 | ],
54 | ],
55 | 'headers' => [
56 | 'from' => [
57 | 'from@example.com',
58 | ],
59 | ],
60 | 'raw_message' => 'raw message',
61 | ],
62 | ])),
63 | ]);
64 | $handlerStack = HandlerStack::create($mock);
65 | $guzzle = new GuzzleHttpClient([
66 | 'handler' => $handlerStack,
67 | ]);
68 |
69 | $client = new Client('', '', $guzzle);
70 |
71 | $details = $client->messages->details(123, true);
72 |
73 | $this->assertSame(123, $details->id);
74 | $this->assertSame('abc', $details->token);
75 | $this->assertSame('Held', $details->status['status']);
76 | $this->assertSame('rcpt_to@example.com', $details->details['rcpt_to']);
77 | $this->assertTrue($details->inspection['inspected']);
78 | $this->assertSame('Plain Body', $details->plain_body);
79 | $this->assertSame('HTML Body
', $details->html_body);
80 | $this->assertSame('file.txt', $details->attachments[0]['filename']);
81 | $this->assertSame('from@example.com', $details->headers['from'][0]);
82 | $this->assertSame('raw message', $details->raw_message);
83 | }
84 |
85 | public function testDeliveries(): void
86 | {
87 | $mock = new MockHandler([
88 | new Response(200, [], json_encode([
89 | 'status' => 'success',
90 | 'data' => [
91 | [
92 | 'id' => 1,
93 | 'status' => 'Held',
94 | 'details' => 'Credential is configured to hold all messages authenticated by it.',
95 | 'output' => null,
96 | 'sent_with_ssl' => false,
97 | 'log_id' => null,
98 | 'time' => null,
99 | 'timestamp' => 1666100297,
100 | ],
101 | [
102 | 'id' => 2,
103 | 'status' => 'Sent',
104 | 'details' => 'Message for test@example.com accepted by mail.protection.outlook.com (0.0.0.0)',
105 | 'output' => '250',
106 | 'sent_with_ssl' => false,
107 | 'log_id' => 'ABCDEF',
108 | 'time' => 1.15,
109 | 'timestamp' => 1666100297,
110 | ],
111 | ],
112 | ])),
113 | ]);
114 | $handlerStack = HandlerStack::create($mock);
115 | $guzzle = new GuzzleHttpClient([
116 | 'handler' => $handlerStack,
117 | ]);
118 |
119 | $client = new Client('', '', $guzzle);
120 |
121 | $deliveries = $client->messages->deliveries(123);
122 |
123 | $this->assertCount(2, $deliveries);
124 |
125 | $this->assertSame(1, $deliveries[0]->id);
126 | $this->assertSame('Held', $deliveries[0]->status);
127 | $this->assertSame('Credential is configured to hold all messages authenticated by it.', $deliveries[0]->details);
128 | $this->assertNull($deliveries[0]->output);
129 | $this->assertFalse($deliveries[0]->sent_with_ssl);
130 | $this->assertNull($deliveries[0]->log_id);
131 | $this->assertNull($deliveries[0]->time);
132 | $this->assertInstanceOf(DateTime::class, $deliveries[0]->timestamp);
133 | $this->assertSame('2022-10-18 13:38:17', $deliveries[0]->timestamp->format('Y-m-d H:i:s'));
134 |
135 | $this->assertSame(2, $deliveries[1]->id);
136 | $this->assertSame('Sent', $deliveries[1]->status);
137 | $this->assertSame('Message for test@example.com accepted by mail.protection.outlook.com (0.0.0.0)', $deliveries[1]->details);
138 | $this->assertSame('250', $deliveries[1]->output);
139 | $this->assertFalse($deliveries[1]->sent_with_ssl);
140 | $this->assertSame('ABCDEF', $deliveries[1]->log_id);
141 | $this->assertSame(1.15, $deliveries[1]->time);
142 | $this->assertInstanceOf(DateTime::class, $deliveries[1]->timestamp);
143 | $this->assertSame('2022-10-18 13:38:17', $deliveries[1]->timestamp->format('Y-m-d H:i:s'));
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/tests/Send/MessageTest.php:
--------------------------------------------------------------------------------
1 | assertNull($message->headers);
16 |
17 | $message->to('to@example.com');
18 | $this->assertSame(['to@example.com'], $message->to);
19 |
20 | $message->cc('cc@example.com');
21 | $this->assertSame(['cc@example.com'], $message->cc);
22 |
23 | $message->bcc('bcc@example.com');
24 | $this->assertSame(['bcc@example.com'], $message->bcc);
25 |
26 | $message->from('from@example.com');
27 | $this->assertSame('from@example.com', $message->from);
28 |
29 | $message->sender('sender@example.com');
30 | $this->assertSame('sender@example.com', $message->sender);
31 |
32 | $message->subject('my-subject');
33 | $this->assertSame('my-subject', $message->subject);
34 |
35 | $message->tag('my-tag');
36 | $this->assertSame('my-tag', $message->tag);
37 |
38 | $message->replyTo('reply-to@example.com');
39 | $this->assertSame('reply-to@example.com', $message->reply_to);
40 |
41 | $message->plainBody('my plain body');
42 | $this->assertSame('my plain body', $message->plain_body);
43 |
44 | $message->htmlBody('my html body');
45 | $this->assertSame('my html body', $message->html_body);
46 |
47 | $message->header('my-header', 'value');
48 | $this->assertSame([
49 | 'my-header' => 'value',
50 | ], $message->headers);
51 |
52 | $message->attach('test.txt', 'text/plain', 'test');
53 | $this->assertSame([
54 | [
55 | 'name' => 'test.txt',
56 | 'content_type' => 'text/plain',
57 | 'data' => 'dGVzdA==',
58 | ],
59 | ], $message->attachments);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/tests/SendServiceTest.php:
--------------------------------------------------------------------------------
1 | 'success',
24 | 'data' => [
25 | 'message_id' => 'my-message-id',
26 | 'messages' => [
27 | [
28 | 'id' => 1,
29 | 'token' => 'A',
30 | ],
31 | [
32 | 'id' => 2,
33 | 'token' => 'B',
34 | ],
35 | ],
36 | ],
37 | ])),
38 | ]);
39 | $handlerStack = HandlerStack::create($mock);
40 |
41 | $requests = [];
42 | $handlerStack->push(Middleware::history($requests));
43 |
44 | $guzzle = new GuzzleHttpClient([
45 | 'handler' => $handlerStack,
46 | ]);
47 |
48 | $client = new Client('', '', $guzzle);
49 |
50 | $message = new Message();
51 | $result = $client->send->message($message);
52 |
53 | $this->assertSame('my-message-id', $result->message_id);
54 | $this->assertCount(2, $result->messages);
55 | $this->assertSame(1, $result->messages[0]->id);
56 | $this->assertSame('A', $result->messages[0]->token);
57 | $this->assertSame(2, $result->messages[1]->id);
58 | $this->assertSame('B', $result->messages[1]->token);
59 |
60 | $this->assertCount(1, $requests);
61 | $uri = (string) $requests[0]['request']->getUri();
62 |
63 | $this->assertSame('send/message', $uri);
64 | }
65 |
66 | public function testRaw(): void
67 | {
68 | $mock = new MockHandler([
69 | new Response(200, [], json_encode([
70 | 'status' => 'success',
71 | 'data' => [
72 | 'message_id' => 'my-message-id',
73 | 'messages' => [
74 | [
75 | 'id' => 1,
76 | 'token' => 'A',
77 | ],
78 | [
79 | 'id' => 2,
80 | 'token' => 'B',
81 | ],
82 | ],
83 | ],
84 | ])),
85 | ]);
86 | $handlerStack = HandlerStack::create($mock);
87 |
88 | $requests = [];
89 | $handlerStack->push(Middleware::history($requests));
90 |
91 | $guzzle = new GuzzleHttpClient([
92 | 'handler' => $handlerStack,
93 | ]);
94 |
95 | $client = new Client('', '', $guzzle);
96 |
97 | $message = new RawMessage();
98 | $result = $client->send->raw($message);
99 |
100 | $this->assertSame('my-message-id', $result->message_id);
101 | $this->assertCount(2, $result->messages);
102 | $this->assertSame(1, $result->messages[0]->id);
103 | $this->assertSame('A', $result->messages[0]->token);
104 | $this->assertSame(2, $result->messages[1]->id);
105 | $this->assertSame('B', $result->messages[1]->token);
106 |
107 | $this->assertCount(1, $requests);
108 | $uri = (string) $requests[0]['request']->getUri();
109 |
110 | $this->assertSame('send/raw', $uri);
111 | }
112 | }
113 |
--------------------------------------------------------------------------------