From 775fc31819ac1c2762c2196d4754f443facaaa24 Mon Sep 17 00:00:00 2001 From: IzikAJ Date: Mon, 5 Oct 2026 17:24:40 +0300 Subject: [PATCH 1/5] Add Templates resources for the paginated /api/templates endpoints - New `account.Templates()` / `account.Template(id)` are account-scoped and return `TemplateList` (data plus page-token pagination) for list; single results are unwrapped from the `data` envelope, and create/update bodies are sent flat. - Update requests are all-optional and only serialize the properties that are set. - The EmailTemplates resources, their request records and the `EmailTemplates()` / `EmailTemplate(id)` account methods stay working but are marked `[Obsolete]`. - Adds the `Mailtrap.Example.Templates` example and marks the Email Templates example as deprecated in the README. --- Mailtrap.sln | 15 + README.md | 3 +- .../Program.cs | 4 +- .../Mailtrap.Example.Templates.csproj | 1 + .../Mailtrap.Example.Templates/Program.cs | 87 +++++ .../Properties/launchSettings.json | 10 + .../appsettings.json | 17 + .../Accounts/IAccountResource.cs | 28 ++ src/Mailtrap.Abstractions/GlobalUsings.cs | 4 + .../Templates/ITemplateCollectionResource.cs | 42 +++ .../Templates/ITemplateResource.cs | 58 +++ .../Templates/Models/Template.cs | 114 ++++++ .../Templates/Models/TemplateList.cs | 31 ++ .../Templates/Models/TemplateListFilter.cs | 26 ++ .../Requests/CreateTemplateRequest.cs | 88 +++++ .../Requests/UpdateTemplateRequest.cs | 89 +++++ .../CreateTemplateRequestValidator.cs | 27 ++ .../UpdateTemplateRequestValidator.cs | 27 ++ src/Mailtrap/Accounts/AccountResource.cs | 16 + src/Mailtrap/Core/Constants/UrlSegments.cs | 1 + src/Mailtrap/GlobalUsings.cs | 4 + .../Responses/TemplateListResponseDto.cs | 31 ++ .../Responses/TemplateResponseDto.cs | 13 + .../Templates/TemplateCollectionResource.cs | 57 +++ src/Mailtrap/Templates/TemplateResource.cs | 28 ++ .../EmailTemplateIntegrationTests.cs | 2 + .../Mailtrap.IntegrationTests/GlobalUsings.cs | 2 + .../Templates/Create_Success.json | 13 + .../Templates/GetAll_Success.json | 35 ++ .../Templates/GetDetails_Success.json | 13 + .../Templates/TemplateIntegrationTests.cs | 343 ++++++++++++++++++ .../Templates/Update_Success.json | 13 + .../TestConstants/UrlSegmentsTestConstants.cs | 1 + .../Accounts/AccountResourceTests.cs | 48 +++ tests/Mailtrap.UnitTests/GlobalUsings.cs | 3 + .../Requests/CreateTemplateRequestTests.cs | 62 ++++ .../Requests/UpdateTemplateRequestTests.cs | 51 +++ .../TemplateCollectionResourceTests.cs | 71 ++++ .../TemplateRequestValidatorTests.cs | 137 +++++++ .../Templates/TemplateResourceTests.cs | 72 ++++ .../TestConstants/UrlSegmentsTestConstants.cs | 1 + 41 files changed, 1686 insertions(+), 2 deletions(-) create mode 100644 examples/Mailtrap.Example.Templates/Mailtrap.Example.Templates.csproj create mode 100644 examples/Mailtrap.Example.Templates/Program.cs create mode 100644 examples/Mailtrap.Example.Templates/Properties/launchSettings.json create mode 100644 examples/Mailtrap.Example.Templates/appsettings.json create mode 100644 src/Mailtrap.Abstractions/Templates/ITemplateCollectionResource.cs create mode 100644 src/Mailtrap.Abstractions/Templates/ITemplateResource.cs create mode 100644 src/Mailtrap.Abstractions/Templates/Models/Template.cs create mode 100644 src/Mailtrap.Abstractions/Templates/Models/TemplateList.cs create mode 100644 src/Mailtrap.Abstractions/Templates/Models/TemplateListFilter.cs create mode 100644 src/Mailtrap.Abstractions/Templates/Requests/CreateTemplateRequest.cs create mode 100644 src/Mailtrap.Abstractions/Templates/Requests/UpdateTemplateRequest.cs create mode 100644 src/Mailtrap.Abstractions/Templates/Validators/CreateTemplateRequestValidator.cs create mode 100644 src/Mailtrap.Abstractions/Templates/Validators/UpdateTemplateRequestValidator.cs create mode 100644 src/Mailtrap/Templates/Responses/TemplateListResponseDto.cs create mode 100644 src/Mailtrap/Templates/Responses/TemplateResponseDto.cs create mode 100644 src/Mailtrap/Templates/TemplateCollectionResource.cs create mode 100644 src/Mailtrap/Templates/TemplateResource.cs create mode 100644 tests/Mailtrap.IntegrationTests/Templates/Create_Success.json create mode 100644 tests/Mailtrap.IntegrationTests/Templates/GetAll_Success.json create mode 100644 tests/Mailtrap.IntegrationTests/Templates/GetDetails_Success.json create mode 100644 tests/Mailtrap.IntegrationTests/Templates/TemplateIntegrationTests.cs create mode 100644 tests/Mailtrap.IntegrationTests/Templates/Update_Success.json create mode 100644 tests/Mailtrap.UnitTests/Templates/Requests/CreateTemplateRequestTests.cs create mode 100644 tests/Mailtrap.UnitTests/Templates/Requests/UpdateTemplateRequestTests.cs create mode 100644 tests/Mailtrap.UnitTests/Templates/TemplateCollectionResourceTests.cs create mode 100644 tests/Mailtrap.UnitTests/Templates/TemplateRequestValidatorTests.cs create mode 100644 tests/Mailtrap.UnitTests/Templates/TemplateResourceTests.cs diff --git a/Mailtrap.sln b/Mailtrap.sln index 5bcf15c9..a4b8061b 100644 --- a/Mailtrap.sln +++ b/Mailtrap.sln @@ -102,6 +102,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mailtrap.Example.ContactEve EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mailtrap.Example.EmailTemplates", "examples\Mailtrap.Example.EmailTemplates\Mailtrap.Example.EmailTemplates.csproj", "{A52169E1-8653-4B7D-9F14-84837E237E43}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mailtrap.Example.Templates", "examples\Mailtrap.Example.Templates\Mailtrap.Example.Templates.csproj", "{9A59D8CF-688C-4DA6-9CF6-F9C892783910}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mailtrap.Example.Email.BatchSend", "examples\Mailtrap.Example.Email.BatchSend\Mailtrap.Example.Email.BatchSend.csproj", "{9922946A-03DA-4AC6-8AA6-ADEC5EF2E9EB}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mailtrap.Example.ContactExports", "examples\Mailtrap.Example.ContactExports\Mailtrap.Example.ContactExports.csproj", "{8791C343-7B1E-4206-A954-A0CFD573E460}" @@ -422,6 +424,18 @@ Global {A52169E1-8653-4B7D-9F14-84837E237E43}.Release|x64.Build.0 = Release|Any CPU {A52169E1-8653-4B7D-9F14-84837E237E43}.Release|x86.ActiveCfg = Release|Any CPU {A52169E1-8653-4B7D-9F14-84837E237E43}.Release|x86.Build.0 = Release|Any CPU + {9A59D8CF-688C-4DA6-9CF6-F9C892783910}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9A59D8CF-688C-4DA6-9CF6-F9C892783910}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9A59D8CF-688C-4DA6-9CF6-F9C892783910}.Debug|x64.ActiveCfg = Debug|Any CPU + {9A59D8CF-688C-4DA6-9CF6-F9C892783910}.Debug|x64.Build.0 = Debug|Any CPU + {9A59D8CF-688C-4DA6-9CF6-F9C892783910}.Debug|x86.ActiveCfg = Debug|Any CPU + {9A59D8CF-688C-4DA6-9CF6-F9C892783910}.Debug|x86.Build.0 = Debug|Any CPU + {9A59D8CF-688C-4DA6-9CF6-F9C892783910}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9A59D8CF-688C-4DA6-9CF6-F9C892783910}.Release|Any CPU.Build.0 = Release|Any CPU + {9A59D8CF-688C-4DA6-9CF6-F9C892783910}.Release|x64.ActiveCfg = Release|Any CPU + {9A59D8CF-688C-4DA6-9CF6-F9C892783910}.Release|x64.Build.0 = Release|Any CPU + {9A59D8CF-688C-4DA6-9CF6-F9C892783910}.Release|x86.ActiveCfg = Release|Any CPU + {9A59D8CF-688C-4DA6-9CF6-F9C892783910}.Release|x86.Build.0 = Release|Any CPU {9922946A-03DA-4AC6-8AA6-ADEC5EF2E9EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9922946A-03DA-4AC6-8AA6-ADEC5EF2E9EB}.Debug|Any CPU.Build.0 = Debug|Any CPU {9922946A-03DA-4AC6-8AA6-ADEC5EF2E9EB}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -583,6 +597,7 @@ Global {5CEEEC08-7F1F-4F75-BC8D-6E80C54C21FD} = {09E18837-1DDE-4EAF-80EC-DA55557C81EB} {AA91FC07-FE50-4DE1-A388-7AA0DB35C84A} = {09E18837-1DDE-4EAF-80EC-DA55557C81EB} {A52169E1-8653-4B7D-9F14-84837E237E43} = {09E18837-1DDE-4EAF-80EC-DA55557C81EB} + {9A59D8CF-688C-4DA6-9CF6-F9C892783910} = {09E18837-1DDE-4EAF-80EC-DA55557C81EB} {9922946A-03DA-4AC6-8AA6-ADEC5EF2E9EB} = {09E18837-1DDE-4EAF-80EC-DA55557C81EB} {8791C343-7B1E-4206-A954-A0CFD573E460} = {09E18837-1DDE-4EAF-80EC-DA55557C81EB} {11894B20-F780-4FC9-8140-3601EBF54C10} = {09E18837-1DDE-4EAF-80EC-DA55557C81EB} diff --git a/README.md b/README.md index b4768b0c..9f413117 100644 --- a/README.md +++ b/README.md @@ -308,7 +308,8 @@ private static SendEmailRequest TemplateBasedRequest() ### General -- Email Templates management – [`examples/Mailtrap.Example.EmailTemplates`](examples/Mailtrap.Example.EmailTemplates/) +- Templates management (paginated) – [`examples/Mailtrap.Example.Templates`](examples/Mailtrap.Example.Templates/) +- Email Templates management – [`examples/Mailtrap.Example.EmailTemplates`](examples/Mailtrap.Example.EmailTemplates/) (deprecated) - Account access management – [`examples/Mailtrap.Example.AccountAccess`](examples/Mailtrap.Example.AccountAccess/) - Permissions management – [`examples/Mailtrap.Example.Permissions`](examples/Mailtrap.Example.Permissions/) - Accounts management – [`examples/Mailtrap.Example.Account`](examples/Mailtrap.Example.Account/) diff --git a/examples/Mailtrap.Example.EmailTemplates/Program.cs b/examples/Mailtrap.Example.EmailTemplates/Program.cs index 7d131f67..01a3af19 100644 --- a/examples/Mailtrap.Example.EmailTemplates/Program.cs +++ b/examples/Mailtrap.Example.EmailTemplates/Program.cs @@ -1,4 +1,5 @@ -using Mailtrap; +#pragma warning disable CS0618 +using Mailtrap; using Mailtrap.Accounts; using Mailtrap.EmailTemplates; using Mailtrap.EmailTemplates.Models; @@ -78,3 +79,4 @@ Environment.ExitCode = 1; return; } +#pragma warning restore CS0618 diff --git a/examples/Mailtrap.Example.Templates/Mailtrap.Example.Templates.csproj b/examples/Mailtrap.Example.Templates/Mailtrap.Example.Templates.csproj new file mode 100644 index 00000000..6b512ec9 --- /dev/null +++ b/examples/Mailtrap.Example.Templates/Mailtrap.Example.Templates.csproj @@ -0,0 +1 @@ + diff --git a/examples/Mailtrap.Example.Templates/Program.cs b/examples/Mailtrap.Example.Templates/Program.cs new file mode 100644 index 00000000..e7823448 --- /dev/null +++ b/examples/Mailtrap.Example.Templates/Program.cs @@ -0,0 +1,87 @@ +using Mailtrap; +using Mailtrap.Accounts; +using Mailtrap.Templates; +using Mailtrap.Templates.Models; +using Mailtrap.Templates.Requests; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + + +HostApplicationBuilder hostBuilder = Host.CreateApplicationBuilder(args); + +IConfigurationSection config = hostBuilder.Configuration.GetSection("Mailtrap"); + +hostBuilder.Services.AddMailtrapClient(config); + +using IHost host = hostBuilder.Build(); + +ILogger logger = host.Services.GetRequiredService>(); +IMailtrapClient mailtrapClient = host.Services.GetRequiredService(); + +try +{ + var accountId = 12345; + IAccountResource accountResource = mailtrapClient.Account(accountId); + + // Get resource for templates collection + ITemplateCollectionResource templatesResource = accountResource.Templates(); + + // Get the first page of templates, 50 per page + var filter = new TemplateListFilter { Token = 1, PerPage = 50 }; + TemplateList page = await templatesResource.GetAll(filter); + + logger.LogInformation("Found {Count} templates on the first page.", page.Data.Count); + logger.LogInformation("Next page token: {NextToken}", page.Pagination?.NextToken); + + Template? template = page.Data.Count > 0 ? page.Data[0] : null; + + if (template is null) + { + logger.LogWarning("No template found. Creating."); + + // Create template + var createTemplateRequest = new CreateTemplateRequest + { + Name = "MyFirstTemplate", + Category = "TestCategory", + Subject = "TestSubject", + BodyHtml = "

This is HTML body

", + BodyText = "This is text body" + }; + template = await templatesResource.Create(createTemplateRequest); + } + else + { + logger.LogInformation("Template {Name} found.", template.Name); + } + + // Get resource for specific template + ITemplateResource templateResource = accountResource.Template(template.Id); + + // Get details + Template templateResponse = await templateResource.GetDetails(); + logger.LogInformation("Template Name from resource: {Name}", templateResponse.Name); + logger.LogInformation("Template Id from resource: {Id}", templateResponse.Id); + + // Update template details, only the properties that are set are changed + var updateTemplateRequest = new UpdateTemplateRequest + { + Name = "updatedTemplate", + BodyHtml = "

This is updated HTML body

" + }; + Template updateTemplateResponse = await templateResource.Update(updateTemplateRequest); + logger.LogInformation("Updated Template: Name={Name}, Id={Id}", updateTemplateResponse.Name, updateTemplateResponse.Id); + + // Delete template + // Beware that template resource becomes invalid after deletion and should not be used anymore + await templateResource.Delete(); + logger.LogInformation("Template Deleted."); +} +catch (Exception ex) +{ + logger.LogError(ex, "An error occurred during API call."); + Environment.ExitCode = 1; + return; +} diff --git a/examples/Mailtrap.Example.Templates/Properties/launchSettings.json b/examples/Mailtrap.Example.Templates/Properties/launchSettings.json new file mode 100644 index 00000000..b8daf491 --- /dev/null +++ b/examples/Mailtrap.Example.Templates/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "Project": { + "commandName": "Project", + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + } + } +} diff --git a/examples/Mailtrap.Example.Templates/appsettings.json b/examples/Mailtrap.Example.Templates/appsettings.json new file mode 100644 index 00000000..7cf4089d --- /dev/null +++ b/examples/Mailtrap.Example.Templates/appsettings.json @@ -0,0 +1,17 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "System": "Warning", + "Microsoft": "Warning" + }, + "Debug": { + "LogLevel": { + "Default": "Debug" + } + } + }, + "Mailtrap": { + "ApiToken": "" + } +} diff --git a/src/Mailtrap.Abstractions/Accounts/IAccountResource.cs b/src/Mailtrap.Abstractions/Accounts/IAccountResource.cs index 6ddfad51..51f399ce 100644 --- a/src/Mailtrap.Abstractions/Accounts/IAccountResource.cs +++ b/src/Mailtrap.Abstractions/Accounts/IAccountResource.cs @@ -166,6 +166,7 @@ public interface IAccountResource : IRestResource /// /// Email template collection resource for the account, represented by this resource instance. /// + [Obsolete("Use IAccountResource.Templates(), which serves the paginated /api/templates surface.")] public IEmailTemplateCollectionResource EmailTemplates(); /// @@ -183,8 +184,35 @@ public interface IAccountResource : IRestResource /// /// When is less than or equal to zero. /// + [Obsolete("Use IAccountResource.Templates(), which serves the paginated /api/templates surface.")] public IEmailTemplateResource EmailTemplate(long emailTemplateId); + /// + /// Gets template collection resource for the account, represented by this resource instance. + /// + /// + /// + /// Template collection resource for the account, represented by this resource instance. + /// + public ITemplateCollectionResource Templates(); + + /// + /// Gets resource for specific template, identified by . + /// + /// + /// + /// ID of template to get resource for. + /// + /// + /// + /// Resource for the template with specified ID. + /// + /// + /// + /// When is less than or equal to zero. + /// + public ITemplateResource Template(long templateId); + /// /// Gets suppression collection resource for the account, represented by this resource instance. /// diff --git a/src/Mailtrap.Abstractions/GlobalUsings.cs b/src/Mailtrap.Abstractions/GlobalUsings.cs index 72c1c1ae..b32001a5 100644 --- a/src/Mailtrap.Abstractions/GlobalUsings.cs +++ b/src/Mailtrap.Abstractions/GlobalUsings.cs @@ -95,6 +95,10 @@ global using Mailtrap.Suppressions.Models; global using Mailtrap.Suppressions.Requests; global using Mailtrap.Suppressions.Validators; +global using Mailtrap.Templates; +global using Mailtrap.Templates.Models; +global using Mailtrap.Templates.Requests; +global using Mailtrap.Templates.Validators; global using Mailtrap.TrackingOptOuts; global using Mailtrap.TrackingOptOuts.Models; global using Mailtrap.TrackingOptOuts.Requests; diff --git a/src/Mailtrap.Abstractions/Templates/ITemplateCollectionResource.cs b/src/Mailtrap.Abstractions/Templates/ITemplateCollectionResource.cs new file mode 100644 index 00000000..49e41fb4 --- /dev/null +++ b/src/Mailtrap.Abstractions/Templates/ITemplateCollectionResource.cs @@ -0,0 +1,42 @@ +namespace Mailtrap.Templates; + + +/// +/// Represents the templates collection resource, served by the paginated /api/templates endpoints. +/// +public interface ITemplateCollectionResource : IRestResource +{ + /// + /// Gets a page of template details. + /// + /// + /// + /// Pagination parameters, or to get the first page with the default page size. + /// + /// + /// + /// Token to control operation cancellation. + /// + /// + /// + /// Page of template details with pagination metadata. + /// + public Task GetAll(TemplateListFilter? filter = null, CancellationToken cancellationToken = default); + + /// + /// Creates a new template with details specified by . + /// + /// + /// + /// Request containing template details for creation. + /// + /// + /// + /// + /// + /// + /// + /// Created template details. + /// + public Task