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: 4 additions & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export MAILTRAP_ACCOUNT_ID="op://Mailtrap Dev/Mailtrap SDK Dev API Key/account_id"
export MAILTRAP_ORGANIZATION_ID="op://Mailtrap Dev/Mailtrap SDK Dev API Key/organization_id"
export MAILTRAP_API_KEY="op://Mailtrap Dev/Mailtrap SDK Dev API Key/account_api_token"
export MAILTRAP_ORGANIZATION_API_KEY="op://Mailtrap Dev/Mailtrap SDK Dev API Key/organization_api_token"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Changelog

## [Unreleased]

### Features

- **API Tokens API** — Added full CRUD support:
- `IAccountResource.ApiTokens().GetAll()` — `GET /api/accounts/{account_id}/api_tokens`
- `IAccountResource.ApiTokens().Create(request)` — `POST /api/accounts/{account_id}/api_tokens`
- `IAccountResource.ApiToken(id).GetDetails()` — `GET /api/accounts/{account_id}/api_tokens/{id}`
- `IAccountResource.ApiToken(id).Delete()` — `DELETE /api/accounts/{account_id}/api_tokens/{id}`
- `IAccountResource.ApiToken(id).Reset()` — `POST /api/accounts/{account_id}/api_tokens/{id}/reset`
- **Organizations API** — Added new top-level `IMailtrapOrganizationClient` (spawned via `MailtrapClientFactory.CreateOrganizationClient()`) for organization-scoped operations:
- `IOrganizationResource.SubAccounts().GetAll()` — `GET /api/organizations/{organization_id}/sub_accounts`
- `IOrganizationResource.SubAccounts().Create(request)` — `POST /api/organizations/{organization_id}/sub_accounts`

## [3.1.1] - 2026-03-30

### Fixes & Maintenance
Expand Down
21 changes: 21 additions & 0 deletions Mailtrap.sln
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mailtrap.Example.Stats", "e
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mailtrap.Example.EmailLogs", "examples\Mailtrap.Example.EmailLogs\Mailtrap.Example.EmailLogs.csproj", "{C4F8B2D3-5E6A-7F9B-0C1D-2E3F4A5B6C7D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mailtrap.Example.ApiTokens", "examples\Mailtrap.Example.ApiTokens\Mailtrap.Example.ApiTokens.csproj", "{D5E6F7A8-1234-5678-9ABC-DEF012345601}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mailtrap.Example.Webhooks", "examples\Mailtrap.Example.Webhooks\Mailtrap.Example.Webhooks.csproj", "{D5E6F7A8-1234-5678-9ABC-DEF012345602}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mailtrap.Example.SubAccount", "examples\Mailtrap.Example.SubAccount\Mailtrap.Example.SubAccount.csproj", "{D5E6F7A8-1234-5678-9ABC-DEF012345603}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -228,6 +234,18 @@ Global
{C4F8B2D3-5E6A-7F9B-0C1D-2E3F4A5B6C7D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C4F8B2D3-5E6A-7F9B-0C1D-2E3F4A5B6C7D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C4F8B2D3-5E6A-7F9B-0C1D-2E3F4A5B6C7D}.Release|Any CPU.Build.0 = Release|Any CPU
{D5E6F7A8-1234-5678-9ABC-DEF012345601}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D5E6F7A8-1234-5678-9ABC-DEF012345601}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D5E6F7A8-1234-5678-9ABC-DEF012345601}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D5E6F7A8-1234-5678-9ABC-DEF012345601}.Release|Any CPU.Build.0 = Release|Any CPU
{D5E6F7A8-1234-5678-9ABC-DEF012345602}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D5E6F7A8-1234-5678-9ABC-DEF012345602}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D5E6F7A8-1234-5678-9ABC-DEF012345602}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D5E6F7A8-1234-5678-9ABC-DEF012345602}.Release|Any CPU.Build.0 = Release|Any CPU
{D5E6F7A8-1234-5678-9ABC-DEF012345603}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D5E6F7A8-1234-5678-9ABC-DEF012345603}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D5E6F7A8-1234-5678-9ABC-DEF012345603}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D5E6F7A8-1234-5678-9ABC-DEF012345603}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -261,6 +279,9 @@ Global
{11894B20-F780-4FC9-8140-3601EBF54C10} = {09E18837-1DDE-4EAF-80EC-DA55557C81EB}
{B3E7A1C2-4D5F-6E8A-9B0C-1D2E3F4A5B6C} = {09E18837-1DDE-4EAF-80EC-DA55557C81EB}
{C4F8B2D3-5E6A-7F9B-0C1D-2E3F4A5B6C7D} = {09E18837-1DDE-4EAF-80EC-DA55557C81EB}
{D5E6F7A8-1234-5678-9ABC-DEF012345601} = {09E18837-1DDE-4EAF-80EC-DA55557C81EB}
{D5E6F7A8-1234-5678-9ABC-DEF012345602} = {09E18837-1DDE-4EAF-80EC-DA55557C81EB}
{D5E6F7A8-1234-5678-9ABC-DEF012345603} = {09E18837-1DDE-4EAF-80EC-DA55557C81EB}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0FF614CC-FEBC-4C66-B3FC-FCB73EE511D7}
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ private static SendEmailRequest TemplateBasedRequest()
- Suppressions management – [`examples/Mailtrap.Example.Suppression`](examples/Mailtrap.Example.Suppression/)
- Email logs (list with filters and pagination; get message details with events) – [`examples/Mailtrap.Example.EmailLogs`](examples/Mailtrap.Example.EmailLogs/)
- Email sending statistics – [`examples/Mailtrap.Example.Stats`](examples/Mailtrap.Example.Stats/)
- Webhooks management – [`examples/Mailtrap.Example.Webhooks`](examples/Mailtrap.Example.Webhooks/)

### Email Sandbox (Testing)

Expand All @@ -301,6 +302,8 @@ private static SendEmailRequest TemplateBasedRequest()
- 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/)
- Sub accounts management – [`examples/Mailtrap.Example.SubAccount`](examples/Mailtrap.Example.SubAccount/)
Comment thread
IgorDobryn marked this conversation as resolved.
- API tokens management – [`examples/Mailtrap.Example.ApiTokens`](examples/Mailtrap.Example.ApiTokens/)
- Billing information – [`examples/Mailtrap.Example.Billing`](examples/Mailtrap.Example.Billing/)
- Comprehensive API Usage – [`examples/Mailtrap.Example.ApiUsage`](examples/Mailtrap.Example.ApiUsage/)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<Project Sdk="Microsoft.NET.Sdk" />
83 changes: 83 additions & 0 deletions examples/Mailtrap.Example.ApiTokens/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using Mailtrap;
using Mailtrap.Accounts;
using Mailtrap.ApiTokens;
using Mailtrap.ApiTokens.Models;
using Mailtrap.ApiTokens.Requests;
using Mailtrap.ApiTokens.Responses;
using Mailtrap.Core.Models;
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<Program> logger = host.Services.GetRequiredService<ILogger<Program>>();
IMailtrapClient mailtrapClient = host.Services.GetRequiredService<IMailtrapClient>();

try
{
var accountId = 12345;
var inboxId = 67890;
IAccountResource accountResource = mailtrapClient.Account(accountId);

// Get resource for API tokens collection
IApiTokenCollectionResource apiTokensResource = accountResource.ApiTokens();

// List all API tokens visible to the current API token
IList<ApiToken> apiTokens = await apiTokensResource.GetAll();
logger.LogInformation("Found {Count} API token(s).", apiTokens.Count);

// Create a new API token scoped to a specific inbox with Viewer access
var createRequest = new CreateApiTokenRequest
{
Name = "Demo Viewer Token"
};
createRequest.Resources.Add(new ApiTokenAccessRequest(ResourceType.Inbox, inboxId, AccessLevel.Viewer));

CreateApiTokenResponse createdToken = await apiTokensResource.Create(createRequest);

// The full token value is only returned at creation time - store it securely
logger.LogInformation(
"Created API Token: Id={Id}, Name={Name}, Last4={Last4}",
createdToken.Id,
createdToken.Name,
createdToken.Last4Digits);
logger.LogInformation("Full token value (store securely, returned only once): {Token}", createdToken.Token);

Comment thread
IgorDobryn marked this conversation as resolved.
// Get resource for the specific API token
IApiTokenResource apiTokenResource = accountResource.ApiToken(createdToken.Id);

// Get details of the API token
ApiToken tokenDetails = await apiTokenResource.GetDetails();
logger.LogInformation("Token details: Id={Id}, Name={Name}, CreatedBy={CreatedBy}",
tokenDetails.Id,
tokenDetails.Name,
tokenDetails.CreatedBy);

// Reset the API token - expires the current token and returns a new one with the same permissions
ApiTokenResetResponse resetResponse = await apiTokenResource.Reset();
logger.LogInformation(
"Reset API Token: Id={Id}, NewLast4={Last4}",
resetResponse.Id,
resetResponse.Last4Digits);
logger.LogInformation("New token value (store securely, returned only once): {Token}", resetResponse.Token);

// Delete the API token
// The API token resource becomes invalid after deletion and should not be used anymore
await apiTokenResource.Delete();
logger.LogInformation("API Token Deleted.");
}
catch (Exception ex)
{
logger.LogError(ex, "An error occurred during API call.");
Environment.ExitCode = 1;
return;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"profiles": {
"Project": {
"commandName": "Project",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development"
}
}
}
}
17 changes: 17 additions & 0 deletions examples/Mailtrap.Example.ApiTokens/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"System": "Warning",
"Microsoft": "Warning"
},
"Debug": {
"LogLevel": {
"Default": "Debug"
}
}
},
"Mailtrap": {
"ApiToken": "<API_KEY>"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<Project Sdk="Microsoft.NET.Sdk" />
61 changes: 61 additions & 0 deletions examples/Mailtrap.Example.SubAccount/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using Mailtrap;
using Mailtrap.Organizations;
using Mailtrap.Organizations.Models;
using Mailtrap.Organizations.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<Program> logger = host.Services.GetRequiredService<ILogger<Program>>();

// Sub accounts live under /api/organizations/{organization_id}/... so they are
// accessed via the organization-scoped client, not IMailtrapClient.
IMailtrapOrganizationClient organizationClient = host.Services.GetRequiredService<IMailtrapOrganizationClient>();

try
{
var organizationId = 12345;
IOrganizationResource organizationResource = organizationClient.Organization(organizationId);

// Get resource for sub accounts collection
IOrganizationSubAccountCollectionResource subAccountsResource = organizationResource.SubAccounts();

// List sub accounts of the organization
IList<SubAccount> subAccounts = await subAccountsResource.GetAll();
logger.LogInformation("Found {Count} sub account(s).", subAccounts.Count);

foreach (SubAccount subAccount in subAccounts)
{
logger.LogInformation("Sub Account: Id={Id}, Name={Name}", subAccount.Id, subAccount.Name);
}

// Create a new sub account under the organization
var createRequest = new CreateSubAccountRequest
{
Account = new SubAccountAttributes
{
Name = "Demo Sub Account"
}
};
SubAccount createdSubAccount = await subAccountsResource.Create(createRequest);
logger.LogInformation(
"Created Sub Account: Id={Id}, Name={Name}",
createdSubAccount.Id,
createdSubAccount.Name);
}
catch (Exception ex)
{
logger.LogError(ex, "An error occurred during API call.");
Environment.ExitCode = 1;
return;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"profiles": {
"Project": {
"commandName": "Project",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development"
}
}
}
}
17 changes: 17 additions & 0 deletions examples/Mailtrap.Example.SubAccount/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"System": "Warning",
"Microsoft": "Warning"
},
"Debug": {
"LogLevel": {
"Default": "Debug"
}
}
},
"Mailtrap": {
"ApiToken": "<API_KEY>"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<Project Sdk="Microsoft.NET.Sdk" />
92 changes: 92 additions & 0 deletions examples/Mailtrap.Example.Webhooks/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using Mailtrap;
using Mailtrap.Accounts;
using Mailtrap.Webhooks;
using Mailtrap.Core.Models;
using Mailtrap.Webhooks.Models;
using Mailtrap.Webhooks.Requests;
using Mailtrap.Webhooks.Responses;
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<Program> logger = host.Services.GetRequiredService<ILogger<Program>>();
IMailtrapClient mailtrapClient = host.Services.GetRequiredService<IMailtrapClient>();

try
{
var accountId = 12345;
IAccountResource accountResource = mailtrapClient.Account(accountId);

// Get resource for webhooks collection
IWebhookCollectionResource webhooksResource = accountResource.Webhooks();

// List all webhooks for the account
IList<Webhook> webhooks = await webhooksResource.GetAll();
logger.LogInformation("Found {Count} webhook(s).", webhooks.Count);

// Create a new "email_sending" webhook subscribed to delivery + bounce events on the transactional stream
var createRequest = new CreateWebhookRequest
{
Url = new Uri("https://example.com/webhooks/mailtrap"),
WebhookType = WebhookType.EmailSending,
Active = true,
PayloadFormat = WebhookPayloadFormat.Json,
SendingStream = SendingStream.Transactional
};
createRequest.EventTypes.Add(WebhookEventType.Delivery);
createRequest.EventTypes.Add(WebhookEventType.Bounce);

CreateWebhookResponse createdWebhook = await webhooksResource.Create(createRequest);

// The signing secret is only returned at creation time - store it securely
logger.LogInformation(
"Created Webhook: Id={Id}, Url={Url}, Type={Type}",
createdWebhook.Id,
createdWebhook.Url,
createdWebhook.WebhookType);
logger.LogInformation("Signing secret (store securely, returned only once): {Secret}", createdWebhook.SigningSecret);

Comment thread
IgorDobryn marked this conversation as resolved.
// Get resource for the specific webhook
IWebhookResource webhookResource = accountResource.Webhook(createdWebhook.Id);

// Get details of the webhook
Webhook webhookDetails = await webhookResource.GetDetails();
logger.LogInformation("Webhook details: Id={Id}, Url={Url}, Active={Active}",
webhookDetails.Id,
webhookDetails.Url,
webhookDetails.Active);

// Update the webhook - swap the event types and disable it
var updateRequest = new UpdateWebhookRequest
{
Active = false,
EventTypes = [WebhookEventType.Delivery, WebhookEventType.Open, WebhookEventType.Click]
};
Webhook updatedWebhook = await webhookResource.Update(updateRequest);
logger.LogInformation(
"Updated Webhook: Id={Id}, Active={Active}, EventTypes={EventTypes}",
updatedWebhook.Id,
updatedWebhook.Active,
string.Join(",", updatedWebhook.EventTypes));

// Delete the webhook
// The webhook resource becomes invalid after deletion and should not be used anymore
Webhook deletedWebhook = await webhookResource.Delete();
logger.LogInformation("Webhook Deleted: Id={Id}", deletedWebhook.Id);
}
catch (Exception ex)
{
logger.LogError(ex, "An error occurred during API call.");
Environment.ExitCode = 1;
return;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"profiles": {
"Project": {
"commandName": "Project",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development"
}
}
}
}
Loading