diff --git a/README.md b/README.md index 2919dfe..b56935f 100644 --- a/README.md +++ b/README.md @@ -240,6 +240,7 @@ Email API: - Batch send with Template (Transactional) – [`batch/transactional_template.php`](examples/batch/transactional_template.php) - Batch send with Template (Bulk) – [`batch/bulk_template.php`](examples/batch/bulk_template.php) - Sending domain management CRUD – [`sending-domains/all.php`](examples/sending-domains/all.php) +- Sending domain company info – [`company-info/all.php`](examples/company-info/all.php) - Suppressions (find & delete) – [`sending/suppressions.php`](examples/sending/suppressions.php) - Email Logs (list & get by message ID) – [`sending/email-logs.php`](examples/sending/email-logs.php) diff --git a/examples/README.md b/examples/README.md index 964353c..bc0adaa 100644 --- a/examples/README.md +++ b/examples/README.md @@ -65,6 +65,7 @@ Central index of runnable example scripts demonstrating Mailtrap PHP SDK feature |---------|------| | Templates CRUD | [`templates/all.php`](templates/all.php) | | Sending domains CRUD | [`sending-domains/all.php`](sending-domains/all.php) | +| Sending domain company info | [`company-info/all.php`](company-info/all.php) | ### General API diff --git a/examples/company-info/all.php b/examples/company-info/all.php new file mode 100644 index 0000000..045310b --- /dev/null +++ b/examples/company-info/all.php @@ -0,0 +1,76 @@ +companyInfo($domainId); #required parameter is domainId + +/** + * Create company info for a sending domain + * + * POST https://mailtrap.io/api/domains/{domain_id}/company_info + */ +try { + $response = $companyInfo->createCompanyInfo( + new CreateCompanyInfo( + name: 'Mailtrap', + address: '123 Main St', + city: 'San Francisco', + country: 'US', + zipCode: '94105', + websiteUrl: 'https://mailtrap.io', + phone: '+1-555-0100', + privacyPolicyUrl: 'https://mailtrap.io/privacy', + termsOfServiceUrl: 'https://mailtrap.io/terms', + infoLevel: CompanyInfo::INFO_LEVEL_BUSINESS + ) + ); + + // print the response body (array) + var_dump(ResponseHelper::toArray($response)); +} catch (Exception $e) { + echo 'Caught exception: ', $e->getMessage(), "\n"; +} + + +/** + * Get company info for a sending domain + * + * GET https://mailtrap.io/api/domains/{domain_id}/company_info + */ +try { + $response = $companyInfo->getCompanyInfo(); + + // print the response body (array) + var_dump(ResponseHelper::toArray($response)); +} catch (Exception $e) { + echo 'Caught exception: ', $e->getMessage(), "\n"; +} + + +/** + * Update company info for a sending domain + * + * PATCH https://mailtrap.io/api/domains/{domain_id}/company_info + * + * Only the fields provided are updated. + */ +try { + $response = $companyInfo->updateCompanyInfo(new UpdateCompanyInfo(city: 'New York', zipCode: '10001')); + + // print the response body (array) + var_dump(ResponseHelper::toArray($response)); +} catch (Exception $e) { + echo 'Caught exception: ', $e->getMessage(), "\n"; +} diff --git a/examples/sending-domains/all.php b/examples/sending-domains/all.php index ce2584f..415a1e7 100644 --- a/examples/sending-domains/all.php +++ b/examples/sending-domains/all.php @@ -3,6 +3,7 @@ declare(strict_types=1); use Mailtrap\Config; +use Mailtrap\DTO\Request\Domain\UpdateDomain; use Mailtrap\Helper\ResponseHelper; use Mailtrap\MailtrapSendingClient; @@ -80,6 +81,32 @@ } +/** + * Update sending domain configuration settings + * + * PATCH https://mailtrap.io/api/accounts/{account_id}/domains/{domain_id} + */ +try { + $domainId = (int) $_ENV['MAILTRAP_DOMAIN_ID']; // Set this environment variable with a valid domain ID + + $response = $sendingDomains->updateSendingDomain( + $domainId, + new UpdateDomain( + openTrackingEnabled: true, + clickTrackingEnabled: true, + trackingOptOutEnabled: true, + autoUnsubscribeLinkEnabled: false, + inboundEnabled: false + ) + ); + + // print the response body (array) + var_dump(ResponseHelper::toArray($response)); +} catch (Exception $e) { + echo 'Caught exception: ', $e->getMessage(), "\n"; +} + + /** * Delete a sending domain * diff --git a/src/Api/Sending/CompanyInfo.php b/src/Api/Sending/CompanyInfo.php new file mode 100644 index 0000000..2160fae --- /dev/null +++ b/src/Api/Sending/CompanyInfo.php @@ -0,0 +1,76 @@ +handleResponse( + $this->httpGet($this->getBasePath()) + ); + } + + /** + * Create the company info for the sending domain. Company info is required + * for domain compliance verification. + * + * @param CreateCompanyInfo $companyInfo + * @return ResponseInterface + */ + public function createCompanyInfo(CreateCompanyInfo $companyInfo): ResponseInterface + { + return $this->handleResponse( + $this->httpPost( + path: $this->getBasePath(), + body: [ + 'company_info' => $companyInfo->toArray() + ] + ) + ); + } + + /** + * Update the company info for the sending domain. + * + * @param UpdateCompanyInfo $companyInfo + * @return ResponseInterface + */ + public function updateCompanyInfo(UpdateCompanyInfo $companyInfo): ResponseInterface + { + return $this->handleResponse( + $this->httpPatch( + path: $this->getBasePath(), + body: [ + 'company_info' => $companyInfo->toArray() + ] + ) + ); + } + + private function getBasePath(): string + { + return sprintf('%s/api/domains/%s/company_info', $this->getHost(), $this->domainId); + } +} diff --git a/src/Api/Sending/Domain.php b/src/Api/Sending/Domain.php index 4f7d8f3..5496749 100644 --- a/src/Api/Sending/Domain.php +++ b/src/Api/Sending/Domain.php @@ -6,6 +6,7 @@ use Mailtrap\Api\AbstractApi; use Mailtrap\ConfigInterface; +use Mailtrap\DTO\Request\Domain\UpdateDomain; use Psr\Http\Message\ResponseInterface; /** @@ -65,6 +66,25 @@ public function getDomainById(int $domainId): ResponseInterface ); } + /** + * Update configuration settings for a sending domain. + * + * @param int $domainId + * @param UpdateDomain $domain + * @return ResponseInterface + */ + public function updateSendingDomain(int $domainId, UpdateDomain $domain): ResponseInterface + { + return $this->handleResponse( + $this->httpPatch( + path: sprintf('%s/%s', $this->getBasePath(), $domainId), + body: [ + 'sending_domain' => $domain->toArray() + ] + ) + ); + } + /** * Delete a sending domain by ID. * diff --git a/src/DTO/Request/Domain/CompanyInfo.php b/src/DTO/Request/Domain/CompanyInfo.php new file mode 100644 index 0000000..a82b278 --- /dev/null +++ b/src/DTO/Request/Domain/CompanyInfo.php @@ -0,0 +1,18 @@ + $this->name, + 'address' => $this->address, + 'city' => $this->city, + 'country' => $this->country, + 'zip_code' => $this->zipCode, + 'website_url' => $this->websiteUrl, + ]; + + if ($this->phone !== null) { + $payload['phone'] = $this->phone; + } + + if ($this->privacyPolicyUrl !== null) { + $payload['privacy_policy_url'] = $this->privacyPolicyUrl; + } + + if ($this->termsOfServiceUrl !== null) { + $payload['terms_of_service_url'] = $this->termsOfServiceUrl; + } + + if ($this->infoLevel !== null) { + $payload['info_level'] = $this->infoLevel; + } + + return $payload; + } +} diff --git a/src/DTO/Request/Domain/DomainInterface.php b/src/DTO/Request/Domain/DomainInterface.php new file mode 100644 index 0000000..c16e835 --- /dev/null +++ b/src/DTO/Request/Domain/DomainInterface.php @@ -0,0 +1,11 @@ +name !== null) { + $payload['name'] = $this->name; + } + + if ($this->address !== null) { + $payload['address'] = $this->address; + } + + if ($this->city !== null) { + $payload['city'] = $this->city; + } + + if ($this->country !== null) { + $payload['country'] = $this->country; + } + + if ($this->zipCode !== null) { + $payload['zip_code'] = $this->zipCode; + } + + if ($this->websiteUrl !== null) { + $payload['website_url'] = $this->websiteUrl; + } + + if ($this->phone !== null) { + $payload['phone'] = $this->phone; + } + + if ($this->privacyPolicyUrl !== null) { + $payload['privacy_policy_url'] = $this->privacyPolicyUrl; + } + + if ($this->termsOfServiceUrl !== null) { + $payload['terms_of_service_url'] = $this->termsOfServiceUrl; + } + + if ($this->infoLevel !== null) { + $payload['info_level'] = $this->infoLevel; + } + + if ($payload === []) { + throw new InvalidArgumentException( + 'At least one updatable field must be provided to update company info' + ); + } + + return $payload; + } +} diff --git a/src/DTO/Request/Domain/UpdateDomain.php b/src/DTO/Request/Domain/UpdateDomain.php new file mode 100644 index 0000000..f6c8c2b --- /dev/null +++ b/src/DTO/Request/Domain/UpdateDomain.php @@ -0,0 +1,67 @@ +openTrackingEnabled !== null) { + $payload['open_tracking_enabled'] = $this->openTrackingEnabled; + } + + if ($this->clickTrackingEnabled !== null) { + $payload['click_tracking_enabled'] = $this->clickTrackingEnabled; + } + + if ($this->trackingOptOutEnabled !== null) { + $payload['tracking_opt_out_enabled'] = $this->trackingOptOutEnabled; + } + + if ($this->autoUnsubscribeLinkEnabled !== null) { + $payload['auto_unsubscribe_link_enabled'] = $this->autoUnsubscribeLinkEnabled; + } + + if ($this->inboundEnabled !== null) { + $payload['inbound_enabled'] = $this->inboundEnabled; + } + + if ($payload === []) { + throw new InvalidArgumentException( + 'At least one updatable field must be provided to update a sending domain' + ); + } + + return $payload; + } +} diff --git a/src/MailtrapSendingClient.php b/src/MailtrapSendingClient.php index a239d52..48e5f1b 100644 --- a/src/MailtrapSendingClient.php +++ b/src/MailtrapSendingClient.php @@ -8,6 +8,7 @@ * @method Api\Sending\Emails emails() * @method Api\Sending\Suppression suppressions(int $accountId) * @method Api\Sending\Domain domains(int $accountId) + * @method Api\Sending\CompanyInfo companyInfo(int $domainId) * @method Api\Sending\Stats stats(int $accountId) * @method Api\Sending\EmailLogs emailLogs(int $accountId) * @method Api\Sending\Webhook webhooks(int $accountId) @@ -20,6 +21,7 @@ final class MailtrapSendingClient extends AbstractMailtrapClient implements Emai 'emails' => Api\Sending\Emails::class, 'suppressions' => Api\Sending\Suppression::class, 'domains' => Api\Sending\Domain::class, + 'companyInfo' => Api\Sending\CompanyInfo::class, 'stats' => Api\Sending\Stats::class, 'emailLogs' => Api\Sending\EmailLogs::class, 'webhooks' => Api\Sending\Webhook::class, diff --git a/tests/Api/Sending/CompanyInfoTest.php b/tests/Api/Sending/CompanyInfoTest.php new file mode 100644 index 0000000..7ad27ac --- /dev/null +++ b/tests/Api/Sending/CompanyInfoTest.php @@ -0,0 +1,215 @@ +companyInfo = $this->getMockBuilder(CompanyInfoApi::class) + ->onlyMethods(['httpGet', 'httpPost', 'httpPatch']) + ->setConstructorArgs([$this->getConfigMock(), self::DOMAIN_ID]) + ->getMock(); + } + + protected function tearDown(): void + { + $this->companyInfo = null; + parent::tearDown(); + } + + public function testGetCompanyInfo(): void + { + $this->companyInfo->expects($this->once()) + ->method('httpGet') + ->with($this->getExpectedPath()) + ->willReturn( + new Response(200, ['Content-Type' => 'application/json'], $this->getExpectedCompanyInfoResponse()) + ); + + $response = $this->companyInfo->getCompanyInfo(); + $responseData = ResponseHelper::toArray($response); + + $this->assertSame(200, $response->getStatusCode()); + $this->assertArrayHasKey('data', $responseData); + $this->assertSame('Mailtrap', $responseData['data']['name']); + $this->assertSame(CompanyInfo::INFO_LEVEL_BUSINESS, $responseData['data']['info_level']); + } + + public function testGetCompanyInfoNotFound(): void + { + $errorResponse = ['error' => 'Not Found']; + + $this->companyInfo->expects($this->once()) + ->method('httpGet') + ->with($this->getExpectedPath()) + ->willReturn(new Response(404, ['Content-Type' => 'application/json'], json_encode($errorResponse))); + + $this->expectException(HttpClientException::class); + + $this->companyInfo->getCompanyInfo(); + } + + public function testCreateCompanyInfo(): void + { + $this->companyInfo->expects($this->once()) + ->method('httpPost') + ->with( + $this->getExpectedPath(), + [], + [ + 'company_info' => [ + 'name' => 'Mailtrap', + 'address' => '123 Main St', + 'city' => 'San Francisco', + 'country' => 'US', + 'zip_code' => '94105', + 'website_url' => 'https://mailtrap.io', + 'info_level' => CompanyInfo::INFO_LEVEL_BUSINESS, + ] + ] + ) + ->willReturn( + new Response(200, ['Content-Type' => 'application/json'], $this->getExpectedCompanyInfoResponse()) + ); + + $response = $this->companyInfo->createCompanyInfo( + new CreateCompanyInfo( + name: 'Mailtrap', + address: '123 Main St', + city: 'San Francisco', + country: 'US', + zipCode: '94105', + websiteUrl: 'https://mailtrap.io', + infoLevel: CompanyInfo::INFO_LEVEL_BUSINESS + ) + ); + + $this->assertSame(200, $response->getStatusCode()); + } + + public function testCreateCompanyInfoWithAllOptionalFields(): void + { + $this->companyInfo->expects($this->once()) + ->method('httpPost') + ->with( + $this->getExpectedPath(), + [], + [ + 'company_info' => [ + 'name' => 'Mailtrap', + 'address' => '123 Main St', + 'city' => 'San Francisco', + 'country' => 'US', + 'zip_code' => '94105', + 'website_url' => 'https://mailtrap.io', + 'phone' => '+1-555-0100', + 'privacy_policy_url' => 'https://mailtrap.io/privacy', + 'terms_of_service_url' => 'https://mailtrap.io/terms', + 'info_level' => CompanyInfo::INFO_LEVEL_INDIVIDUAL, + ] + ] + ) + ->willReturn( + new Response(200, ['Content-Type' => 'application/json'], $this->getExpectedCompanyInfoResponse()) + ); + + $response = $this->companyInfo->createCompanyInfo( + new CreateCompanyInfo( + name: 'Mailtrap', + address: '123 Main St', + city: 'San Francisco', + country: 'US', + zipCode: '94105', + websiteUrl: 'https://mailtrap.io', + phone: '+1-555-0100', + privacyPolicyUrl: 'https://mailtrap.io/privacy', + termsOfServiceUrl: 'https://mailtrap.io/terms', + infoLevel: CompanyInfo::INFO_LEVEL_INDIVIDUAL + ) + ); + + $this->assertSame(200, $response->getStatusCode()); + } + + public function testUpdateCompanyInfoSendsOnlyProvidedFields(): void + { + $this->companyInfo->expects($this->once()) + ->method('httpPatch') + ->with( + $this->getExpectedPath(), + [], + [ + 'company_info' => [ + 'city' => 'New York', + 'zip_code' => '10001', + ] + ] + ) + ->willReturn( + new Response(200, ['Content-Type' => 'application/json'], $this->getExpectedCompanyInfoResponse()) + ); + + $response = $this->companyInfo->updateCompanyInfo( + new UpdateCompanyInfo(city: 'New York', zipCode: '10001') + ); + + $this->assertSame(200, $response->getStatusCode()); + } + + public function testUpdateCompanyInfoWithEmptyPayload(): void + { + $this->companyInfo->expects($this->never())->method('httpPatch'); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('At least one updatable field must be provided to update company info'); + + $this->companyInfo->updateCompanyInfo(new UpdateCompanyInfo()); + } + + private function getExpectedPath(): string + { + return AbstractApi::DEFAULT_HOST . '/api/domains/' . self::DOMAIN_ID . '/company_info'; + } + + private function getExpectedCompanyInfoResponse(): string + { + return json_encode([ + 'data' => [ + 'name' => 'Mailtrap', + 'address' => '123 Main St', + 'city' => 'San Francisco', + 'country' => 'US', + 'phone' => '+1-555-0100', + 'zip_code' => '94105', + 'privacy_policy_url' => 'https://mailtrap.io/privacy', + 'terms_of_service_url' => 'https://mailtrap.io/terms', + 'website_url' => 'https://mailtrap.io', + 'info_level' => CompanyInfo::INFO_LEVEL_BUSINESS, + ] + ]); + } +} diff --git a/tests/Api/Sending/DomainTest.php b/tests/Api/Sending/DomainTest.php index d29bfb1..8f7d2ec 100644 --- a/tests/Api/Sending/DomainTest.php +++ b/tests/Api/Sending/DomainTest.php @@ -6,7 +6,9 @@ use Mailtrap\Api\AbstractApi; use Mailtrap\Api\Sending\Domain; +use Mailtrap\DTO\Request\Domain\UpdateDomain; use Mailtrap\Exception\HttpClientException; +use Mailtrap\Exception\InvalidArgumentException; use Mailtrap\Helper\ResponseHelper; use Mailtrap\Tests\MailtrapTestCase; use Nyholm\Psr7\Response; @@ -24,7 +26,7 @@ protected function setUp(): void { parent::setUp(); $this->domain = $this->getMockBuilder(Domain::class) - ->onlyMethods(['httpGet', 'httpPost', 'httpDelete']) + ->onlyMethods(['httpGet', 'httpPost', 'httpPatch', 'httpDelete']) ->setConstructorArgs([$this->getConfigMock(), self::FAKE_ACCOUNT_ID]) ->getMock(); } @@ -275,6 +277,82 @@ private function getExpectedDomainsResponse(): string ]); } + public function testUpdateSendingDomain(): void + { + $domainId = 12345; + $expectedResponseBody = $this->getExpectedDomainResponse('example.com', $domainId); + + $this->domain->expects($this->once()) + ->method('httpPatch') + ->with( + AbstractApi::DEFAULT_HOST . '/api/accounts/' . self::FAKE_ACCOUNT_ID . '/sending_domains/' . $domainId, + [], + [ + 'sending_domain' => [ + 'open_tracking_enabled' => true, + 'click_tracking_enabled' => true, + 'tracking_opt_out_enabled' => true, + 'auto_unsubscribe_link_enabled' => false, + 'inbound_enabled' => false, + ] + ] + ) + ->willReturn(new Response(200, ['Content-Type' => 'application/json'], $expectedResponseBody)); + + $response = $this->domain->updateSendingDomain( + $domainId, + new UpdateDomain( + openTrackingEnabled: true, + clickTrackingEnabled: true, + trackingOptOutEnabled: true, + autoUnsubscribeLinkEnabled: false, + inboundEnabled: false + ) + ); + $responseData = ResponseHelper::toArray($response); + + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame($domainId, $responseData['id']); + } + + public function testUpdateSendingDomainSendsOnlyProvidedFields(): void + { + $domainId = 12345; + $expectedResponseBody = $this->getExpectedDomainResponse('example.com', $domainId); + + $this->domain->expects($this->once()) + ->method('httpPatch') + ->with( + AbstractApi::DEFAULT_HOST . '/api/accounts/' . self::FAKE_ACCOUNT_ID . '/sending_domains/' . $domainId, + [], + [ + 'sending_domain' => [ + 'tracking_opt_out_enabled' => false, + ] + ] + ) + ->willReturn(new Response(200, ['Content-Type' => 'application/json'], $expectedResponseBody)); + + $response = $this->domain->updateSendingDomain( + $domainId, + new UpdateDomain(trackingOptOutEnabled: false) + ); + + $this->assertSame(200, $response->getStatusCode()); + } + + public function testUpdateSendingDomainWithEmptyPayload(): void + { + $this->domain->expects($this->never())->method('httpPatch'); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage( + 'At least one updatable field must be provided to update a sending domain' + ); + + $this->domain->updateSendingDomain(12345, new UpdateDomain()); + } + private function getExpectedDomainResponse(string $domainName, ?int $domainId = null): string { return json_encode([ diff --git a/tests/MailtrapSendingClientTest.php b/tests/MailtrapSendingClientTest.php index 2322ade..db2d03a 100644 --- a/tests/MailtrapSendingClientTest.php +++ b/tests/MailtrapSendingClientTest.php @@ -16,6 +16,8 @@ */ class MailtrapSendingClientTest extends MailtrapClientTestCase { + private const DOMAIN_ID = 12345; + public function getMailtrapClientClassName(): string { return MailtrapSendingClient::class; @@ -31,6 +33,7 @@ public function mapInstancesProvider(): iterable foreach (MailtrapSendingClient::API_MAPPING as $key => $item) { yield match ($key) { 'suppressions', 'domains', 'stats', 'emailLogs', 'webhooks' => [new $item($this->getConfigMock(), self::FAKE_ACCOUNT_ID)], + 'companyInfo' => [new $item($this->getConfigMock(), self::DOMAIN_ID)], default => [new $item($this->getConfigMock())], }; }