Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
lint:
timeout-minutes: 10
name: lint
runs-on: ${{ github.repository == 'stainless-sdks/courier-php' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork

steps:
Expand All @@ -37,7 +37,7 @@ jobs:
test:
timeout-minutes: 10
name: test
runs-on: ${{ github.repository == 'stainless-sdks/courier-php' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
Expand Down
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "5.11.2"
".": "5.12.0"
}
8 changes: 4 additions & 4 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 138
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/courier/courier-11e1931d767cef402bbed5a965746d1933ae72db0f41881860593a0b1424fd80.yml
openapi_spec_hash: 9def941805f233f8761c4bdae88f6137
config_hash: 6c3a754258f0f77e9032a90a478ab76d
configured_endpoints: 139
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/courier/courier-b03f5346718dcd7b22f15893445ea593ab87535c90c540fc5a796c6ecd54d779.yml
openapi_spec_hash: 15677d3be4ecd33eab154080b4bc437f
config_hash: dcd58abf1e56b114bdce22336ca3d570
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Changelog

## 5.12.0 (2026-07-20)

Full Changelog: [v5.11.2...v5.12.0](https://github.com/trycourier/courier-php/compare/v5.11.2...v5.12.0)

### Features

* Document POST /notifications/{id}/duplicate [C-18607] ([#165](https://github.com/trycourier/courier-php/issues/165)) ([be965ad](https://github.com/trycourier/courier-php/commit/be965adeb3c022bd988e03a976d2842634726a5c))
* Merge pull request [#149](https://github.com/trycourier/courier-php/issues/149) from trycourier/geraldosilva/c-19201-notification-template-subscription-topic-id ([622c65e](https://github.com/trycourier/courier-php/commit/622c65e6334f628d60e5c3aa433bc37b38eb3619))
* Merge pull request [#168](https://github.com/trycourier/courier-php/issues/168) from trycourier/geraldosilva/c-19419-topic-id-notification-template-summary ([9f7f082](https://github.com/trycourier/courier-php/commit/9f7f082b3f2f87033f9d1242aed96ebcdf141485))
* **stlc:** configurable CI runner and private-production-repo support in workflow templates ([69bfa6b](https://github.com/trycourier/courier-php/commit/69bfa6b0e807f5120d48dcc1cc7aa1f532ec0a9c))


### Bug Fixes

* **ci:** drop Packagist secret requirement from release doctor ([#90](https://github.com/trycourier/courier-php/issues/90)) ([e0b992d](https://github.com/trycourier/courier-php/commit/e0b992df2ac46603779dc3c566dd178c558f42c4))

## 5.11.2 (2026-07-10)

Full Changelog: [v5.11.1...v5.11.2](https://github.com/trycourier/courier-php/compare/v5.11.1...v5.11.2)
Expand Down
40 changes: 40 additions & 0 deletions src/Notifications/NotificationTemplateSummary.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
* name: string,
* state: State|value-of<State>,
* tags: list<string>,
* subscriptionTopicID?: string|null,
* topicID?: string|null,
* updated?: int|null,
* updater?: string|null,
* }
Expand Down Expand Up @@ -55,6 +57,18 @@ final class NotificationTemplateSummary implements BaseModel
#[Required(list: 'string')]
public array $tags;

/**
* The linked subscription (preference) topic of the published version. Omitted when no topic is linked or the template has never been published.
*/
#[Optional('subscription_topic_id')]
public ?string $subscriptionTopicID;

/**
* Alias of subscription_topic_id, provided under the same name V1 list items use for the linked topic. Always carries the same value as subscription_topic_id.
*/
#[Optional('topic_id')]
public ?string $topicID;

/**
* Epoch milliseconds of last update.
*/
Expand Down Expand Up @@ -109,6 +123,8 @@ public static function with(
string $name,
State|string $state,
array $tags,
?string $subscriptionTopicID = null,
?string $topicID = null,
?int $updated = null,
?string $updater = null,
): self {
Expand All @@ -121,6 +137,8 @@ public static function with(
$self['state'] = $state;
$self['tags'] = $tags;

null !== $subscriptionTopicID && $self['subscriptionTopicID'] = $subscriptionTopicID;
null !== $topicID && $self['topicID'] = $topicID;
null !== $updated && $self['updated'] = $updated;
null !== $updater && $self['updater'] = $updater;

Expand Down Expand Up @@ -187,6 +205,28 @@ public function withTags(array $tags): self
return $self;
}

/**
* The linked subscription (preference) topic of the published version. Omitted when no topic is linked or the template has never been published.
*/
public function withSubscriptionTopicID(string $subscriptionTopicID): self
{
$self = clone $this;
$self['subscriptionTopicID'] = $subscriptionTopicID;

return $self;
}

/**
* Alias of subscription_topic_id, provided under the same name V1 list items use for the linked topic. Always carries the same value as subscription_topic_id.
*/
public function withTopicID(string $topicID): self
{
$self = clone $this;
$self['topicID'] = $topicID;

return $self;
}

/**
* Epoch milliseconds of last update.
*/
Expand Down
13 changes: 13 additions & 0 deletions src/ServiceContracts/NotificationsContract.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,19 @@ public function archive(
RequestOptions|array|null $requestOptions = null
): mixed;

/**
* @api
*
* @param string $id template ID (nt_ prefix)
* @param RequestOpts|null $requestOptions
*
* @throws APIException
*/
public function duplicate(
string $id,
RequestOptions|array|null $requestOptions = null
): NotificationTemplateResponse;

/**
* @api
*
Expand Down
15 changes: 15 additions & 0 deletions src/ServiceContracts/NotificationsRawContract.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,21 @@ public function archive(
RequestOptions|array|null $requestOptions = null
): BaseResponse;

/**
* @api
*
* @param string $id template ID (nt_ prefix)
* @param RequestOpts|null $requestOptions
*
* @return BaseResponse<NotificationTemplateResponse>
*
* @throws APIException
*/
public function duplicate(
string $id,
RequestOptions|array|null $requestOptions = null
): BaseResponse;

/**
* @api
*
Expand Down
25 changes: 25 additions & 0 deletions src/Services/NotificationsRawService.php
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,31 @@ public function archive(
);
}

/**
* @api
*
* Duplicate a notification template. Creates a standalone copy within the same workspace and environment, with " COPY" appended to the title. The copy clones the source draft's tags, brand, subscription topic, routing strategy, channels, and content, and is always created as a standalone template (it is not linked to any journey or broadcast, even if the source was). Templates that are scoped to a journey or a broadcast cannot be duplicated through this endpoint.
*
* @param string $id template ID (nt_ prefix)
* @param RequestOpts|null $requestOptions
*
* @return BaseResponse<NotificationTemplateResponse>
*
* @throws APIException
*/
public function duplicate(
string $id,
RequestOptions|array|null $requestOptions = null
): BaseResponse {
// @phpstan-ignore-next-line return.type
return $this->client->request(
method: 'post',
path: ['notifications/%1$s/duplicate', $id],
options: $requestOptions,
convert: NotificationTemplateResponse::class,
);
}

/**
* @api
*
Expand Down
20 changes: 20 additions & 0 deletions src/Services/NotificationsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,26 @@ public function archive(
return $response->parse();
}

/**
* @api
*
* Duplicate a notification template. Creates a standalone copy within the same workspace and environment, with " COPY" appended to the title. The copy clones the source draft's tags, brand, subscription topic, routing strategy, channels, and content, and is always created as a standalone template (it is not linked to any journey or broadcast, even if the source was). Templates that are scoped to a journey or a broadcast cannot be duplicated through this endpoint.
*
* @param string $id template ID (nt_ prefix)
* @param RequestOpts|null $requestOptions
*
* @throws APIException
*/
public function duplicate(
string $id,
RequestOptions|array|null $requestOptions = null
): NotificationTemplateResponse {
// @phpstan-ignore-next-line argument.type
$response = $this->raw->duplicate($id, requestOptions: $requestOptions);

return $response->parse();
}

/**
* @api
*
Expand Down
2 changes: 1 addition & 1 deletion src/Version.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
namespace Courier;

// x-release-please-start-version
const VERSION = '5.11.2';
const VERSION = '5.12.0';
// x-release-please-end
13 changes: 13 additions & 0 deletions tests/Services/NotificationsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,19 @@ public function testArchive(): void
$this->assertNull($result);
}

#[Test]
public function testDuplicate(): void
{
if (UnsupportedMockTests::$skip) {
$this->markTestSkipped('Mock server tests are disabled');
}

$result = $this->client->notifications->duplicate('id');

// @phpstan-ignore-next-line method.alreadyNarrowedType
$this->assertInstanceOf(NotificationTemplateResponse::class, $result);
}

#[Test]
public function testListVersions(): void
{
Expand Down