diff --git a/CHANGELOG.md b/CHANGELOG.md index 51dcbe1..e97800c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## [3.5.0] - 2025-07-12 +- Add Contact Fields API functionality + ## [3.4.0] - 2025-07-04 - Add Batch sending functionality (transactional, bulk and sandbox) diff --git a/README.md b/README.md index 1fdecc6..cc65570 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ Currently with this SDK you can: - Inbox management - Project management - Contact management + - Fields CRUD - Contacts CRUD - Lists CRUD - General diff --git a/examples/general/contacts.php b/examples/general/contacts.php index 3a70ab2..5b52f98 100644 --- a/examples/general/contacts.php +++ b/examples/general/contacts.php @@ -186,3 +186,88 @@ } catch (Exception $e) { echo 'Caught exception: ', $e->getMessage(), PHP_EOL; } + + +/** + * Get all Contact Fields existing in your account + * + * GET https://mailtrap.io/api/accounts/{account_id}/contacts/fields + */ +try { + $response = $contacts->getAllContactFields(); + + // print the response body (array) + var_dump(ResponseHelper::toArray($response)); +} catch (Exception $e) { + echo 'Caught exception: ', $e->getMessage(), PHP_EOL; +} + + +/** + * Get a specific Contact Field by ID. + * + * GET https://mailtrap.io/api/accounts/{account_id}/contacts/fields/{field_id} + */ +try { + $fieldId = 1; // Replace 1 with the actual field ID + $response = $contacts->getContactField($fieldId); + + // print the response body (array) + var_dump(ResponseHelper::toArray($response)); +} catch (Exception $e) { + echo 'Caught exception: ', $e->getMessage(), PHP_EOL; +} + + +/** + * Create new Contact Fields. Please note, you can have up to 40 fields. + * + * POST https://mailtrap.io/api/accounts/{account_id}/contacts/fields + */ +try { + $response = $contacts->createContactField( + 'New Field Name', // <= 80 characters + 'text', // Allowed values: text, integer, float, boolean, date + 'new_field_merge_tag' // Personalize your campaigns by adding a merge tag. This field will be replaced with unique contact details for each recipient. + ); + + // print the response body (array) + var_dump(ResponseHelper::toArray($response)); +} catch (Exception $e) { + echo 'Caught exception: ', $e->getMessage(), PHP_EOL; +} + + +/** + * Update existing Contact Field. Please note, you cannot change data_type of the field. + * + * PATCH https://mailtrap.io/api/accounts/{account_id}/contacts/fields/{field_id} + */ +try { + $fieldId = 1; // Replace 1 with the actual field ID + $response = $contacts->updateContactField( + $fieldId, + 'Updated Field Name', + 'updated_field_merge_tag' + ); + + // print the response body (array) + var_dump(ResponseHelper::toArray($response)); +} catch (Exception $e) { + echo 'Caught exception: ', $e->getMessage(), PHP_EOL; +} + +/** + * Delete Contact Field by ID. + * + * DELETE https://mailtrap.io/api/accounts/{account_id}/contacts/fields/{field_id} + */ +try { + $fieldId = 1; // Replace 1 with the actual field ID + $response = $contacts->deleteContactField($fieldId); + + // Print the response status code + var_dump($response->getStatusCode()); +} catch (Exception $e) { + echo 'Caught exception: ', $e->getMessage(), PHP_EOL; +} diff --git a/src/Api/General/Contact.php b/src/Api/General/Contact.php index b16b13c..56ff150 100644 --- a/src/Api/General/Contact.php +++ b/src/Api/General/Contact.php @@ -172,6 +172,80 @@ public function deleteContactByEmail(string $email): ResponseInterface return $this->deleteContact($email); } + /** + * Get all Contact Fields. + * + * @return ResponseInterface + */ + public function getAllContactFields(): ResponseInterface + { + return $this->handleResponse( + $this->httpGet($this->getBasePath() . '/fields') + ); + } + + /** + * Get a specific Contact Field by ID. + * + * @param int $fieldId + * @return ResponseInterface + */ + public function getContactField(int $fieldId): ResponseInterface + { + return $this->handleResponse( + $this->httpGet($this->getBasePath() . '/fields/' . $fieldId) + ); + } + + /** + * Create a new Contact Field. + * + * @param string $name + * @param string $dataType + * @param string $mergeTag + * @return ResponseInterface + */ + public function createContactField(string $name, string $dataType, string $mergeTag): ResponseInterface + { + return $this->handleResponse( + $this->httpPost( + path: $this->getBasePath() . '/fields', + body: ['name' => $name, 'data_type' => $dataType, 'merge_tag' => $mergeTag] + ) + ); + } + + /** + * Update an existing Contact Field by ID. + * + * @param int $fieldId + * @param string $name + * @param string $mergeTag + * @return ResponseInterface + */ + public function updateContactField(int $fieldId, string $name, string $mergeTag): ResponseInterface + { + return $this->handleResponse( + $this->httpPatch( + path: $this->getBasePath() . '/fields/' . $fieldId, + body: ['name' => $name, 'merge_tag' => $mergeTag] + ) + ); + } + + /** + * Delete a Contact Field by ID. + * + * @param int $fieldId + * @return ResponseInterface + */ + public function deleteContactField(int $fieldId): ResponseInterface + { + return $this->handleResponse( + $this->httpDelete($this->getBasePath() . '/fields/' . $fieldId) + ); + } + public function getAccountId(): int { return $this->accountId; diff --git a/tests/Api/General/ContactTest.php b/tests/Api/General/ContactTest.php index 6739b9b..1093e3d 100644 --- a/tests/Api/General/ContactTest.php +++ b/tests/Api/General/ContactTest.php @@ -359,6 +359,131 @@ public function testInvalidCreateContact(): void $this->contact->createContact($invalidContactDTO); } + public function testGetAllContactFields(): void + { + $this->contact->expects($this->once()) + ->method('httpGet') + ->with(AbstractApi::DEFAULT_HOST . '/api/accounts/' . self::FAKE_ACCOUNT_ID . '/contacts/fields') + ->willReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode($this->getExpectedContactFields()))); + + $response = $this->contact->getAllContactFields(); + $responseData = ResponseHelper::toArray($response); + + $this->assertInstanceOf(Response::class, $response); + $this->assertCount(2, $responseData); + $this->assertArrayHasKey('id', $responseData[0]); + $this->assertArrayHasKey('name', $responseData[0]); + $this->assertArrayHasKey('data_type', $responseData[0]); + $this->assertArrayHasKey('merge_tag', $responseData[0]); + } + + public function testGetContactField(): void + { + $fieldId = 1; + + $this->contact->expects($this->once()) + ->method('httpGet') + ->with(AbstractApi::DEFAULT_HOST . '/api/accounts/' . self::FAKE_ACCOUNT_ID . '/contacts/fields/' . $fieldId) + ->willReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode($this->getExpectedContactFields()[0]))); + + $response = $this->contact->getContactField($fieldId); + $responseData = ResponseHelper::toArray($response); + + $this->assertArrayHasKey('id', $responseData); + $this->assertEquals($fieldId, $responseData['id']); + } + + public function testCreateContactField(): void + { + $fieldData = ['name' => 'Custom Field', 'data_type' => 'text', 'merge_tag' => 'my_contact_field']; + + $this->contact->expects($this->once()) + ->method('httpPost') + ->with( + AbstractApi::DEFAULT_HOST . '/api/accounts/' . self::FAKE_ACCOUNT_ID . '/contacts/fields', + [], + $fieldData + ) + ->willReturn(new Response(201, ['Content-Type' => 'application/json'], json_encode($this->getExpectedContactFields()[0]))); + + $response = $this->contact->createContactField($fieldData['name'], $fieldData['data_type'], $fieldData['merge_tag']); + $responseData = ResponseHelper::toArray($response); + + $this->assertArrayHasKey('id', $responseData); + $this->assertEquals($fieldData['name'], $responseData['name']); + } + + public function testCreateContactFieldMultipleValidationErrors(): void + { + $fieldData = ['name' => 'Duplicate Field', 'data_type' => 'text', 'merge_tag' => 'duplicate_merge_tag']; + $errors = [ + 'errors' => [ + 'name' => ['has already been taken'], + 'merge_tag' => ['has already been taken'], + ], + ]; + + $this->contact->expects($this->once()) + ->method('httpPost') + ->with( + AbstractApi::DEFAULT_HOST . '/api/accounts/' . self::FAKE_ACCOUNT_ID . '/contacts/fields', + [], + $fieldData + ) + ->willReturn( + new Response(422, ['Content-Type' => 'application/json'], json_encode($errors)) + ); + + $this->expectException(HttpClientException::class); + $this->expectExceptionMessage('Errors: name -> has already been taken. merge_tag -> has already been taken.'); + + $this->contact->createContactField($fieldData['name'], $fieldData['data_type'], $fieldData['merge_tag']); + } + + public function testUpdateContactField(): void + { + $fieldId = 1; + $fieldData = ['name' => 'Updated Field', 'merge_tag' => 'my_contact_field_new']; + + $this->contact->expects($this->once()) + ->method('httpPatch') + ->with( + AbstractApi::DEFAULT_HOST . '/api/accounts/' . self::FAKE_ACCOUNT_ID . '/contacts/fields/' . $fieldId, + [], + $fieldData + ) + ->willReturn(new Response(200, ['Content-Type' => 'application/json'], json_encode($this->getExpectedContactFields()[1]))); + + $response = $this->contact->updateContactField($fieldId, $fieldData['name'], $fieldData['merge_tag']); + $responseData = ResponseHelper::toArray($response); + + $this->assertArrayHasKey('id', $responseData); + $this->assertEquals($fieldData['name'], $responseData['name']); + $this->assertEquals($fieldData['merge_tag'], $responseData['merge_tag']); + } + + public function testDeleteContactField(): void + { + $fieldId = 1; + + $this->contact->expects($this->once()) + ->method('httpDelete') + ->with(AbstractApi::DEFAULT_HOST . '/api/accounts/' . self::FAKE_ACCOUNT_ID . '/contacts/fields/' . $fieldId) + ->willReturn(new Response(204)); + + $response = $this->contact->deleteContactField($fieldId); + + $this->assertEquals(204, $response->getStatusCode()); + } + + private function getExpectedContactFields(): array + { + return [ + ['id' => 1, 'name' => 'Custom Field', 'data_type' => 'text', 'merge_tag' => 'my_contact_field'], + ['id' => 2, 'name' => 'Updated Field', 'data_type' => 'text', 'merge_tag' => 'my_contact_field_new'], + ]; + } + private function getExpectedContactLists(): array { return [