From f3f3bb8a97f9698a87428c6dcf777d4804613df4 Mon Sep 17 00:00:00 2001 From: Tobias Haimerl Date: Thu, 26 Feb 2026 09:52:57 +0100 Subject: [PATCH 1/2] docs: add CLAUDE.md with build commands and architecture overview --- CLAUDE.md | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..1116ffd --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,66 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Commands + +```bash +# Build +dotnet build src/postmark-dotnet.sln + +# Run all tests (requires API tokens - see Testing section) +dotnet test src/postmark-dotnet.sln + +# Run a single test class +dotnet test src/Postmark.Tests/Postmark.Tests.csproj --filter "ClassName=ClientSendingTests" + +# Run a single test method +dotnet test src/Postmark.Tests/Postmark.Tests.csproj --filter "FullyQualifiedName~ClientCanSendMessage" + +# Build NuGet package +dotnet pack src/Postmark/Postmark.csproj +``` + +## Architecture + +This is a .NET Standard 2.0 HTTP client library for the [Postmark](https://postmarkapp.com) transactional email API. There are no web servers, databases, or startup code — only the library and its integration tests. + +**Client hierarchy:** + +- `PostmarkClientBase` — abstract base handling all HTTP communication. Contains the two core methods (`ProcessRequestAsync` for POST/PUT with body, `ProcessNoBodyRequestAsync` for GET/DELETE with query params). Uses a static `ClientFactory` property returning `ISimpleHttpClient` — replace this to mock HTTP in tests. +- `PostmarkClient` — server-level API using `X-Postmark-Server-Token` header. Handles email sending, bounces, templates, message streams, suppressions, statistics, webhooks, and inbound messages. +- `PostmarkAdminClient` — account-level API using `X-Postmark-Account-Token` header. Handles servers, sender signatures, and domains. +- `PostmarkClientExtensions` — convenience overloads for `SendMessageAsync` (accepts primitives instead of a `PostmarkMessage` object). +- `LegacyClientExtensions` — deprecated IAsyncResult-style wrappers from the 1.x API. Do not use; all new code should use the `Async` task-based methods. + +**Serialization:** + +Uses `System.Text.Json` exclusively with two custom converters registered in `JsonExtensions.TryDeserializeObject`: +- `DateTimeConverter` — handles the Postmark API's `" (GMT)"` date format suffix. +- `UnicodeJsonStringConverter` — handles Unicode string encoding quirks. + +All models live in `src/Postmark/Model/` with subdirectories for `MessageStreams/`, `Suppressions/`, and `Webhooks/`. + +## Testing + +All tests are **integration tests** that make real HTTP calls to the Postmark API. They require API tokens configured either via a `testing_keys.json` file (searched up the directory tree from the test assembly) or environment variables. + +Required keys: +```json +{ + "READ_SELENIUM_TEST_SERVER_TOKEN": "...", + "READ_LINK_TRACKING_TEST_SERVER_TOKEN": "...", + "READ_SELENIUM_OPEN_TRACKING_TOKEN": "...", + "READ_BOUNCE_TEST_SERVER_TOKEN": "...", + "WRITE_ACCOUNT_TOKEN": "...", + "WRITE_TEST_SERVER_TOKEN": "...", + "WRITE_TEST_SENDER_EMAIL_ADDRESS": "...", + "WRITE_TEST_EMAIL_RECIPIENT_ADDRESS": "...", + "WRITE_TEST_SENDER_SIGNATURE_PROTOTYPE": "...", + "BASE_URL": "https://api.postmarkapp.com" +} +``` + +The config file location can be overridden with the `POSTMARK_SDK_CONFIG_FILE_NAME` environment variable. + +Tests that create or modify data (servers, signatures, domains) require `WRITE_*` tokens. Read-only tests use `READ_*` tokens pointing to pre-configured test servers. `ClientBaseFixture` is the abstract base class all test classes inherit from. From ba60d0b4d10638ceaaf8793ad568e00646fd4300 Mon Sep 17 00:00:00 2001 From: Tobias Haimerl Date: Thu, 26 Feb 2026 13:42:33 +0100 Subject: [PATCH 2/2] make the library use best practice patterns for httpclient resolution --- src/Postmark.Tests/AdminClientDomainsTests.cs | 4 +- .../AdminClientSenderSignatureTests.cs | 4 +- src/Postmark.Tests/AdminClientServersTests.cs | 4 +- src/Postmark.Tests/ClientBaseFixture.cs | 17 +- src/Postmark.Tests/ClientBounceTests.cs | 2 +- .../ClientMessageClickQueryTests.cs | 2 +- .../ClientMessageOpenQueryTests.cs | 2 +- .../ClientMessageSearchingTests.cs | 2 +- .../ClientMessageStreamTests.cs | 6 +- src/Postmark.Tests/ClientSendingTests.cs | 2 +- .../ClientServerInformationTests.cs | 2 +- src/Postmark.Tests/ClientStatisticsTests.cs | 2 +- src/Postmark.Tests/ClientSuppressionTests.cs | 6 +- src/Postmark.Tests/ClientTemplateTests.cs | 2 +- src/Postmark.Tests/ClientTriggersTests.cs | 2 +- src/Postmark.Tests/ClientWebhookTests.cs | 6 +- src/Postmark.Tests/Postmark.Tests.csproj | 2 +- src/Postmark.Tests/PostmarkClientTests.cs | 2 +- src/Postmark/IPostmarkAdminClient.cs | 32 + src/Postmark/IPostmarkClient.cs | 96 + src/Postmark/ISimpleHttpClient.cs | 10 - src/Postmark/LegacyClientExtensions.cs | 476 ---- src/Postmark/Postmark.csproj | 4 +- src/Postmark/PostmarkAdminClient.cs | 817 +++--- src/Postmark/PostmarkClient.cs | 2417 ++++++++--------- src/Postmark/PostmarkClientBase.cs | 248 +- src/Postmark/PostmarkClientExtensions.cs | 4 +- .../PostmarkServiceCollectionExtensions.cs | 55 + src/Postmark/SimpleHttpClient.cs | 24 - 29 files changed, 1929 insertions(+), 2323 deletions(-) create mode 100644 src/Postmark/IPostmarkAdminClient.cs create mode 100644 src/Postmark/IPostmarkClient.cs delete mode 100644 src/Postmark/ISimpleHttpClient.cs delete mode 100644 src/Postmark/LegacyClientExtensions.cs create mode 100644 src/Postmark/PostmarkServiceCollectionExtensions.cs delete mode 100644 src/Postmark/SimpleHttpClient.cs diff --git a/src/Postmark.Tests/AdminClientDomainsTests.cs b/src/Postmark.Tests/AdminClientDomainsTests.cs index 4bf76aa..697df37 100644 --- a/src/Postmark.Tests/AdminClientDomainsTests.cs +++ b/src/Postmark.Tests/AdminClientDomainsTests.cs @@ -9,13 +9,13 @@ namespace Postmark.Tests { public class AdminClientDomainsTests : ClientBaseFixture, IAsyncLifetime { - private PostmarkAdminClient _adminClient; + private IPostmarkAdminClient _adminClient; private string _domainName; private string _returnPath; public Task InitializeAsync() { - _adminClient = new PostmarkAdminClient(WriteAccountToken, BaseUrl); + _adminClient = BuildAdminClient(WriteAccountToken); _domainName = "dotnet-lib-test.com"; _returnPath = $"return-path.{_domainName}"; diff --git a/src/Postmark.Tests/AdminClientSenderSignatureTests.cs b/src/Postmark.Tests/AdminClientSenderSignatureTests.cs index 2a66e7c..0b8100e 100644 --- a/src/Postmark.Tests/AdminClientSenderSignatureTests.cs +++ b/src/Postmark.Tests/AdminClientSenderSignatureTests.cs @@ -11,7 +11,7 @@ namespace Postmark.Tests { public class AdminClientSenderSignatureTests : ClientBaseFixture, IAsyncLifetime { - private PostmarkAdminClient _adminClient; + private IPostmarkAdminClient _adminClient; private string _senderEmail; private string _replyToAddress; private string _senderName; @@ -20,7 +20,7 @@ public class AdminClientSenderSignatureTests : ClientBaseFixture, IAsyncLifetime public Task InitializeAsync() { - _adminClient = new PostmarkAdminClient(WriteAccountToken, BaseUrl); + _adminClient = BuildAdminClient(WriteAccountToken); var id = Guid.NewGuid(); _senderprefix = "test-sender-"; _returnPath = "test." + WriteTestSenderSignaturePrototype.Split('@')[1]; diff --git a/src/Postmark.Tests/AdminClientServersTests.cs b/src/Postmark.Tests/AdminClientServersTests.cs index 0cb8bc2..3225339 100644 --- a/src/Postmark.Tests/AdminClientServersTests.cs +++ b/src/Postmark.Tests/AdminClientServersTests.cs @@ -11,7 +11,7 @@ namespace Postmark.Tests { public class AdminClientServersTests : ClientBaseFixture, IAsyncLifetime { - private PostmarkAdminClient _adminClient; + private IPostmarkAdminClient _adminClient; private string _serverPrefix; private bool? _smtpActivated; private string _inboundHookUrl; @@ -29,7 +29,7 @@ public class AdminClientServersTests : ClientBaseFixture, IAsyncLifetime public Task InitializeAsync() { - _adminClient = new PostmarkAdminClient(WriteAccountToken, BaseUrl); + _adminClient = BuildAdminClient(WriteAccountToken); var id = Guid.NewGuid().ToString("n"); _serverPrefix = "admin-client-integration-test-server-"; diff --git a/src/Postmark.Tests/ClientBaseFixture.cs b/src/Postmark.Tests/ClientBaseFixture.cs index 692ddc1..53369b1 100644 --- a/src/Postmark.Tests/ClientBaseFixture.cs +++ b/src/Postmark.Tests/ClientBaseFixture.cs @@ -6,6 +6,7 @@ using System.Reflection; using System.Linq; using System.Text.Json; +using Microsoft.Extensions.DependencyInjection; namespace Postmark.Tests { @@ -83,7 +84,21 @@ private static string ConfigVariable(string variableName) protected static readonly string WriteTestSenderSignaturePrototype = ConfigVariable("WRITE_TEST_SENDER_SIGNATURE_PROTOTYPE"); protected static readonly string BaseUrl = ConfigVariable("BASE_URL"); - protected PostmarkClient Client; + protected IPostmarkClient Client; + + protected static IPostmarkClient BuildClient(string token) + { + var services = new ServiceCollection(); + services.AddPostmarkClient(token); + return services.BuildServiceProvider().GetRequiredService(); + } + + protected static IPostmarkAdminClient BuildAdminClient(string token) + { + var services = new ServiceCollection(); + services.AddPostmarkAdminClient(token); + return services.BuildServiceProvider().GetRequiredService(); + } protected ClientBaseFixture() { diff --git a/src/Postmark.Tests/ClientBounceTests.cs b/src/Postmark.Tests/ClientBounceTests.cs index 80dfb9d..67f98d6 100644 --- a/src/Postmark.Tests/ClientBounceTests.cs +++ b/src/Postmark.Tests/ClientBounceTests.cs @@ -13,7 +13,7 @@ public ClientBounceTests() var bounceToken = !string.IsNullOrWhiteSpace(ReadBounceTestServerToken) ? ReadBounceTestServerToken : WriteTestServerToken; - Client = new PostmarkClient(bounceToken, BaseUrl); + Client = BuildClient(bounceToken); } [Fact] diff --git a/src/Postmark.Tests/ClientMessageClickQueryTests.cs b/src/Postmark.Tests/ClientMessageClickQueryTests.cs index b13ad1a..cf43985 100644 --- a/src/Postmark.Tests/ClientMessageClickQueryTests.cs +++ b/src/Postmark.Tests/ClientMessageClickQueryTests.cs @@ -8,7 +8,7 @@ public class ClientMessageClickQueryTests : ClientBaseFixture { public ClientMessageClickQueryTests() { - Client = new PostmarkClient(ReadLinkTrackingTestServerToken, BaseUrl); + Client = BuildClient(ReadLinkTrackingTestServerToken); } [Fact] diff --git a/src/Postmark.Tests/ClientMessageOpenQueryTests.cs b/src/Postmark.Tests/ClientMessageOpenQueryTests.cs index 175b27f..7b0da52 100644 --- a/src/Postmark.Tests/ClientMessageOpenQueryTests.cs +++ b/src/Postmark.Tests/ClientMessageOpenQueryTests.cs @@ -8,7 +8,7 @@ public class ClientMessageOpenQueryTests : ClientBaseFixture { public ClientMessageOpenQueryTests() { - Client = new PostmarkClient(ReadSeleniumOpenTrackingToken, BaseUrl); + Client = BuildClient(ReadSeleniumOpenTrackingToken); } [Fact] diff --git a/src/Postmark.Tests/ClientMessageSearchingTests.cs b/src/Postmark.Tests/ClientMessageSearchingTests.cs index 3392f74..0bfa2d2 100644 --- a/src/Postmark.Tests/ClientMessageSearchingTests.cs +++ b/src/Postmark.Tests/ClientMessageSearchingTests.cs @@ -8,7 +8,7 @@ public class ClientMessageSearchingTests : ClientBaseFixture { public ClientMessageSearchingTests() { - Client = new PostmarkClient(ReadSeleniumTestServerToken, BaseUrl); + Client = BuildClient(ReadSeleniumTestServerToken); } [Fact] diff --git a/src/Postmark.Tests/ClientMessageStreamTests.cs b/src/Postmark.Tests/ClientMessageStreamTests.cs index 5e1e01a..86ec182 100644 --- a/src/Postmark.Tests/ClientMessageStreamTests.cs +++ b/src/Postmark.Tests/ClientMessageStreamTests.cs @@ -10,14 +10,14 @@ namespace Postmark.Tests { public class ClientMessageStreamTests : ClientBaseFixture, IAsyncLifetime { - private PostmarkAdminClient _adminClient; + private IPostmarkAdminClient _adminClient; private PostmarkServer _server; public async Task InitializeAsync() { - _adminClient = new PostmarkAdminClient(WriteAccountToken, BaseUrl); + _adminClient = BuildAdminClient(WriteAccountToken); _server = await _adminClient.CreateServerAsync($"integration-test-message-stream-{Guid.NewGuid()}"); - Client = new PostmarkClient(_server.ApiTokens.First(), BaseUrl); + Client = BuildClient(_server.ApiTokens.First()); } public async Task DisposeAsync() diff --git a/src/Postmark.Tests/ClientSendingTests.cs b/src/Postmark.Tests/ClientSendingTests.cs index 6ef349f..7bc7c56 100644 --- a/src/Postmark.Tests/ClientSendingTests.cs +++ b/src/Postmark.Tests/ClientSendingTests.cs @@ -13,7 +13,7 @@ public class ClientSendingTests : ClientBaseFixture { public ClientSendingTests() { - Client = new PostmarkClient(WriteTestServerToken, BaseUrl); + Client = BuildClient(WriteTestServerToken); } [Fact] diff --git a/src/Postmark.Tests/ClientServerInformationTests.cs b/src/Postmark.Tests/ClientServerInformationTests.cs index 8f2b8ec..b72b244 100644 --- a/src/Postmark.Tests/ClientServerInformationTests.cs +++ b/src/Postmark.Tests/ClientServerInformationTests.cs @@ -20,7 +20,7 @@ public class ClientServerInformationTests : ClientBaseFixture, IAsyncLifetime public Task InitializeAsync() { - Client = new PostmarkClient(WriteTestServerToken, BaseUrl); + Client = BuildClient(WriteTestServerToken); var id = Guid.NewGuid().ToString("n"); _serverBaseName = "dotnet-integration-test-server"; diff --git a/src/Postmark.Tests/ClientStatisticsTests.cs b/src/Postmark.Tests/ClientStatisticsTests.cs index d13671d..d3caa18 100644 --- a/src/Postmark.Tests/ClientStatisticsTests.cs +++ b/src/Postmark.Tests/ClientStatisticsTests.cs @@ -12,7 +12,7 @@ public class ClientStatisticsTests : ClientBaseFixture public ClientStatisticsTests() { - Client = new PostmarkClient(ReadSeleniumTestServerToken, BaseUrl); + Client = BuildClient(ReadSeleniumTestServerToken); _lastMonth = TestingDate - TimeSpan.FromDays(30); WindowStartDate = TestingDate - TimeSpan.FromDays(35); } diff --git a/src/Postmark.Tests/ClientSuppressionTests.cs b/src/Postmark.Tests/ClientSuppressionTests.cs index c7b4b4a..9aae27e 100644 --- a/src/Postmark.Tests/ClientSuppressionTests.cs +++ b/src/Postmark.Tests/ClientSuppressionTests.cs @@ -11,14 +11,14 @@ namespace Postmark.Tests { public class ClientSuppressionTests : ClientBaseFixture, IAsyncLifetime { - private PostmarkAdminClient _adminClient; + private IPostmarkAdminClient _adminClient; private PostmarkServer _server; public async Task InitializeAsync() { - _adminClient = new PostmarkAdminClient(WriteAccountToken, BaseUrl); + _adminClient = BuildAdminClient(WriteAccountToken); _server = await _adminClient.CreateServerAsync($"integration-test-suppressions-{Guid.NewGuid()}"); - Client = new PostmarkClient(_server.ApiTokens.First(), BaseUrl); + Client = BuildClient(_server.ApiTokens.First()); } public async Task DisposeAsync() diff --git a/src/Postmark.Tests/ClientTemplateTests.cs b/src/Postmark.Tests/ClientTemplateTests.cs index d353a28..fff7f17 100644 --- a/src/Postmark.Tests/ClientTemplateTests.cs +++ b/src/Postmark.Tests/ClientTemplateTests.cs @@ -15,7 +15,7 @@ public class ClientTemplateTests : ClientBaseFixture, IAsyncLifetime public Task InitializeAsync() { - Client = new PostmarkClient(WriteTestServerToken, BaseUrl); + Client = BuildClient(WriteTestServerToken); return Task.CompletedTask; } diff --git a/src/Postmark.Tests/ClientTriggersTests.cs b/src/Postmark.Tests/ClientTriggersTests.cs index 4b3411d..cc2fd62 100644 --- a/src/Postmark.Tests/ClientTriggersTests.cs +++ b/src/Postmark.Tests/ClientTriggersTests.cs @@ -14,7 +14,7 @@ public class ClientTriggersTests : ClientBaseFixture, IAsyncLifetime public Task InitializeAsync() { - Client = new PostmarkClient(WriteTestServerToken, BaseUrl); + Client = BuildClient(WriteTestServerToken); return Task.CompletedTask; } diff --git a/src/Postmark.Tests/ClientWebhookTests.cs b/src/Postmark.Tests/ClientWebhookTests.cs index 9a0e51c..59fd3a6 100644 --- a/src/Postmark.Tests/ClientWebhookTests.cs +++ b/src/Postmark.Tests/ClientWebhookTests.cs @@ -12,14 +12,14 @@ namespace Postmark.Tests { public class ClientWebhookTests : ClientBaseFixture, IAsyncLifetime { - private PostmarkAdminClient _adminClient; + private IPostmarkAdminClient _adminClient; private PostmarkServer _server; public async Task InitializeAsync() { - _adminClient = new PostmarkAdminClient(WriteAccountToken, BaseUrl); + _adminClient = BuildAdminClient(WriteAccountToken); _server = await _adminClient.CreateServerAsync($"integration-test-webhooks-{Guid.NewGuid()}"); - Client = new PostmarkClient(_server.ApiTokens.First(), BaseUrl); + Client = BuildClient(_server.ApiTokens.First()); } public async Task DisposeAsync() diff --git a/src/Postmark.Tests/Postmark.Tests.csproj b/src/Postmark.Tests/Postmark.Tests.csproj index 3913321..921ceb3 100755 --- a/src/Postmark.Tests/Postmark.Tests.csproj +++ b/src/Postmark.Tests/Postmark.Tests.csproj @@ -1,6 +1,6 @@ - netcoreapp3.1 + net10.0 library diff --git a/src/Postmark.Tests/PostmarkClientTests.cs b/src/Postmark.Tests/PostmarkClientTests.cs index cf0f261..0ca06cf 100644 --- a/src/Postmark.Tests/PostmarkClientTests.cs +++ b/src/Postmark.Tests/PostmarkClientTests.cs @@ -9,7 +9,7 @@ public class PostmarkClientTests : ClientBaseFixture { public PostmarkClientTests() { - Client = new PostmarkClient(WriteTestServerToken, BaseUrl); + Client = BuildClient(WriteTestServerToken); } [Fact] diff --git a/src/Postmark/IPostmarkAdminClient.cs b/src/Postmark/IPostmarkAdminClient.cs new file mode 100644 index 0000000..c9814fe --- /dev/null +++ b/src/Postmark/IPostmarkAdminClient.cs @@ -0,0 +1,32 @@ +using System.Threading.Tasks; +using PostmarkDotNet.Model; + +namespace PostmarkDotNet +{ + public interface IPostmarkAdminClient + { + Task GetServerAsync(int serverId); + Task DeleteServerAsync(int serverId); + Task CreateServerAsync(string name, string color = null, bool? rawEmailEnabled = null, bool? smtpApiActivated = null, string inboundHookUrl = null, string bounceHookUrl = null, string openHookUrl = null, bool? postFirstOpenOnly = null, bool? trackOpens = null, string inboundDomain = null, int? inboundSpamThreshold = null, LinkTrackingOptions? trackLinks = null, string clickHookUrl = null, string deliveryHookUrl = null, bool? enableSmtpApiErrorHooks = null, string deliveryType = null); + Task EditServerAsync(int serverId, string name = null, string color = null, bool? rawEmailEnabled = null, bool? smtpApiActivated = null, string inboundHookUrl = null, string bounceHookUrl = null, string openHookUrl = null, bool? postFirstOpenOnly = null, bool? trackOpens = null, string inboundDomain = null, int? inboundSpamThreshold = null, LinkTrackingOptions? trackLinks = null, string clickHookUrl = null, string deliveryHookUrl = null, bool? enableSmtpApiErrorHooks = null); + Task GetServersAsync(int offset = 0, int count = 100, string name = null); + Task GetSenderSignaturesAsync(int offset = 0, int count = 100); + Task GetSenderSignatureAsync(int signatureId); + Task DeleteSignatureAsync(int signatureId); + Task ResendSignatureVerificationEmailAsync(int signatureId); + Task RequestNewSignatureDKIMAsync(int signatureId); + Task CreateSignatureAsync(string fromEmail, string name, string replyToEmail = null, string returnPathDomain = null, string confirmationPersonalNote = null); + Task UpdateSignatureAsync(int signatureId, string name = null, string replyToEmail = null, string returnPathDomain = null, string confirmationPersonalNote = null); + Task GetDomainsAsync(int offset = 0, int count = 100); + Task GetDomainAsync(int domainId); + Task DeleteDomainAsync(int domainId); + Task RequestNewDomainDKIMAsync(int domainId); + Task CreateDomainAsync(string name, string returnPathDomain = null); + Task UpdateDomainAsync(int domainId, string returnPathDomain); + Task VerifyDomainDkim(int domainId); + Task VerifyDomainReturnPath(int domainId); + Task RequestDataRemoval(string requestedBy, string requestedFor, bool notifyWhenCompleted); + Task GetDataRemovalStatus(long id); + Task VerifySignatureSPF(int signatureId); + } +} \ No newline at end of file diff --git a/src/Postmark/IPostmarkClient.cs b/src/Postmark/IPostmarkClient.cs new file mode 100644 index 0000000..941e8f7 --- /dev/null +++ b/src/Postmark/IPostmarkClient.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Postmark.Model.MessageStreams; +using Postmark.Model.Suppressions; +using PostmarkDotNet.Model; +using PostmarkDotNet.Model.Webhooks; + +namespace PostmarkDotNet +{ + public interface IPostmarkClient + { + // Email Sending + Task SendMessageAsync(PostmarkMessage message); + Task> SendMessagesAsync(params PostmarkMessage[] messages); + Task SendMessageAsync(TemplatedPostmarkMessage message); + Task SendEmailWithTemplateAsync(TemplatedPostmarkMessage emailToSend); + Task> SendMessagesAsync(params TemplatedPostmarkMessage[] messages); + Task> SendEmailsWithTemplateAsync(params TemplatedPostmarkMessage[] messages); + Task SendEmailWithTemplateAsync(string templateAlias, T templateModel, string to, string from, bool? inlineCss = null, string cc = null, string bcc = null, string replyTo = null, bool? trackOpens = null, IDictionary headers = null, IDictionary metadata = null, string messageStream = null, params PostmarkMessageAttachment[] attachments); + Task SendEmailWithTemplateAsync(long templateId, T templateModel, string to, string from, bool? inlineCss = null, string cc = null, string bcc = null, string replyTo = null, bool? trackOpens = null, IDictionary headers = null, IDictionary metadata = null, string messageStream = null, params PostmarkMessageAttachment[] attachments); + + // Bounces + Task GetDeliveryStatsAsync(); + Task GetBouncesAsync(int offset = 0, int count = 100, PostmarkBounceType? type = null, bool? inactive = null, string emailFilter = null, string tag = null, string messageID = null, string fromDate = null, string toDate = null); + Task GetBounceAsync(long bounceId); + Task GetBounceDumpAsync(long bounceId); + Task ActivateBounceAsync(long bounceId); + + // Outbound Messages + Task GetOutboundMessagesAsync(int offset = 0, int count = 100, string recipient = null, string fromemail = null, string tag = null, string subject = null, OutboundMessageStatus status = OutboundMessageStatus.Sent, string toDate = null, string fromDate = null, IDictionary metadata = null, string messagestream = null); + Task GetOutboundMessageDetailsAsync(string messageID); + Task GetOutboundMessageDumpAsync(string messageID); + + // Inbound Messages + Task GetInboundMessagesAsync(int offset = 0, int count = 100, string recipient = null, string fromemail = null, string subject = null, string mailboxhash = null, InboundMessageStatus? status = InboundMessageStatus.Processed, string toDate = null, string fromDate = null); + Task GetInboundMessageDetailsAsync(string messageID); + Task BypassBlockedInboundMessage(string messageid); + Task RetryInboundHookForMessage(string messageId); + + // Server + Task GetServerAsync(); + Task EditServerAsync(string name = null, string color = null, bool? rawEmailEnabled = null, bool? smtpApiActivated = null, string inboundHookUrl = null, string bounceHookUrl = null, string openHookUrl = null, bool? postFirstOpenOnly = null, bool? trackOpens = null, string inboundDomain = null, int? inboundSpamThreshold = null, LinkTrackingOptions? trackLinks = null, string clickHookUrl = null, string deliveryHookUrl = null); + + // Stats + Task GetOpenEventsForMessagesAsync(int offset = 0, int count = 100, string recipient = null, string tag = null, string clientName = null, string clientCompany = null, string clientFamily = null, string operatingSystemName = null, string operatingSystemFamily = null, string operatingSystemCompany = null, string platform = null, string country = null, string region = null, string city = null); + Task GetClickEventsForMessagesAsync(int offset = 0, int count = 100, string recipient = null, string tag = null, string clientName = null, string clientCompany = null, string clientFamily = null, string operatingSystemName = null, string operatingSystemFamily = null, string operatingSystemCompany = null, string platform = null, string country = null, string region = null, string city = null); + Task GetOpenEventsForMessageAsync(string messageId, int offset = 0, int count = 100); + Task GetClickEventsForMessageAsync(string messageId, int offset = 0, int count = 100); + Task GetOutboundOverviewStatsAsync(string tag = null, DateTime? fromDate = null, DateTime? toDate = null); + Task GetOutboundSentCountsAsync(string tag = null, DateTime? fromDate = null, DateTime? toDate = null); + Task GetOutboundBounceCountsAsync(string tag = null, DateTime? fromDate = null, DateTime? toDate = null); + Task GetOutboundSpamComplaintCountsAsync(string tag = null, DateTime? fromDate = null, DateTime? toDate = null); + Task GetOutboundTrackingCountsAsync(string tag = null, DateTime? fromDate = null, DateTime? toDate = null); + Task GetOutboundOpenCountsAsync(string tag = null, DateTime? fromDate = null, DateTime? toDate = null); + Task GetOutboundPlatformCountsAsync(string tag = null, DateTime? fromDate = null, DateTime? toDate = null); + Task GetOutboundClientUsageCountsAsync(string tag = null, DateTime? fromDate = null, DateTime? toDate = null); + Task GetOutboundReadtimeStatsAsync(string tag = null, DateTime? fromDate = null, DateTime? toDate = null); + + // Inbound Triggers + Task CreateInboundRuleTriggerAsync(string rule); + Task DeleteInboundRuleTrigger(int triggerId); + Task GetAllInboundRuleTriggers(int offset = 0, int count = 100); + + // Templates + Task GetTemplateAsync(long templateId); + Task GetTemplateAsync(string alias); + Task GetTemplatesAsync(int offset = 0, int count = 100, TemplateTypeFilter templateType = TemplateTypeFilter.All, string layoutTemplate = null); + Task CreateTemplateAsync(string name, string subject, string htmlBody = null, string textBody = null, string alias = null, TemplateType templateType = TemplateType.Standard, string layoutTemplate = null); + Task EditTemplateAsync(string alias, string name = null, string subject = null, string htmlBody = null, string textBody = null, string layoutTemplate = null); + Task EditTemplateAsync(long templateId, string name = null, string subject = null, string htmlBody = null, string textBody = null, string alias = null, string layoutTemplate = null); + Task DeleteTemplateAsync(long templateId); + Task DeleteTemplateAsync(string templateAlias); + Task ValidateTemplateAsync(string subject = null, string htmlBody = null, string textBody = null, T testRenderModel = default, bool inlineCssForHtmlTestRender = true, TemplateType templateType = TemplateType.Standard, string layoutTemplate = null); + + // Webhooks + Task GetWebhookConfigurationAsync(long configurationId); + Task GetWebhookConfigurationsAsync(string messageStream = null); + Task DeleteWebhookConfigurationAsync(long configurationId); + Task CreateWebhookConfigurationAsync(string url, string messageStream = null, HttpAuth httpAuth = null, IEnumerable httpHeaders = null, WebhookConfigurationTriggers triggers = null); + Task EditWebhookConfigurationAsync(long configurationId, string url, HttpAuth httpAuth = null, IEnumerable httpHeaders = null, WebhookConfigurationTriggers triggers = null); + + // Suppressions + Task ListSuppressions(PostmarkSuppressionQuery query, string messageStream = "outbound"); + Task CreateSuppressions(IEnumerable suppressionChanges, string messageStream = "outbound"); + Task DeleteSuppressions(IEnumerable suppressionChanges, string messageStream = "outbound"); + + // Message Streams + Task CreateMessageStream(string id, MessageStreamType type, string name, string description = null, UnsubscribeHandlingType? unsubscriptionHandlingType = null); + Task EditMessageStream(string id, string name = null, string description = null, UnsubscribeHandlingType? unsubscriptionHandlingType = null); + Task GetMessageStream(string id); + Task ListMessageStreams(MessageStreamTypeFilter messageStreamType = MessageStreamTypeFilter.All, bool includeArchivedStreams = false); + Task ArchiveMessageStream(string id); + Task UnArchiveMessageStream(string id); + } +} \ No newline at end of file diff --git a/src/Postmark/ISimpleHttpClient.cs b/src/Postmark/ISimpleHttpClient.cs deleted file mode 100644 index 5cdd2fd..0000000 --- a/src/Postmark/ISimpleHttpClient.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Net.Http; -using System.Threading.Tasks; - -namespace PostmarkDotNet -{ - public interface ISimpleHttpClient - { - Task SendAsync(HttpRequestMessage request); - } -} \ No newline at end of file diff --git a/src/Postmark/LegacyClientExtensions.cs b/src/Postmark/LegacyClientExtensions.cs deleted file mode 100644 index c7f974e..0000000 --- a/src/Postmark/LegacyClientExtensions.cs +++ /dev/null @@ -1,476 +0,0 @@ -#pragma warning disable CS1591 -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using PostmarkDotNet.Model; - -namespace PostmarkDotNet.Legacy -{ - /// - /// A legacy shim, proxying the 1.x IAsyncResult style client calls. - /// - /// This shim is provided as a convenience to consumers of the 1.x client. - /// It is recommended that consumers use the 2.0 Task-based methods instead of these extensions. - [Obsolete("This shim is provided as a convenience to consumers of the 1.x client. " + - "It is recommended that consumers use the 2.0 Task-based methods instead of these extensions.")] - public static class LegacyClientExtensions - { - public static IAsyncResult BeginSendMessage(this PostmarkClient client, string from, string to, string subject, string textBody, string htmlBody) - { - return client.SendMessageAsync(from, to, subject, textBody, htmlBody); - } - - public static IAsyncResult BeginSendMessage(this PostmarkClient client, PostmarkMessage message) - { - return client.SendMessageAsync(message); - } - - public static IAsyncResult BeginSendMessages(this PostmarkClient client, IEnumerable messages) - { - return client.SendMessagesAsync(messages); - } - - public static IAsyncResult BeginSendMessages(this PostmarkClient client, params PostmarkMessage[] messages) - { - return client.SendMessagesAsync(messages); - } - - public static IEnumerable EndSendMessages(this PostmarkClient client, IAsyncResult asyncResult) - { - return asyncResult.UnwrapResult>(); - } - - private static T UnwrapResult(this IAsyncResult result) - { - return ((Task)result).Result; - } - - public static PostmarkResponse EndSendMessage(this PostmarkClient client, IAsyncResult asyncResult) - { - return asyncResult.UnwrapResult(); - } - - public static IAsyncResult BeginGetDeliveryStats(this PostmarkClient client) - { - return client.GetDeliveryStatsAsync(); - } - - public static PostmarkDeliveryStats EndGetDeliveryStats(this PostmarkClient client, IAsyncResult asyncResult) - { - return asyncResult.UnwrapResult(); - } - - public static IAsyncResult BeginGetBounces - (this PostmarkClient client, PostmarkBounceType type, bool? inactive, - string emailFilter, string tag, int offset, int count) - { - return client.GetBouncesAsync(offset, count, type, inactive, emailFilter, tag); - } - - public static IAsyncResult BeginGetBounces(this PostmarkClient client, bool? inactive, - string emailFilter, string tag, int offset, int count) - { - return client.GetBouncesAsync(offset, count, null, inactive, emailFilter, tag); - } - - public static IAsyncResult BeginGetBounces(this PostmarkClient client, PostmarkBounceType type, int offset, int count) - { - return client.GetBouncesAsync(offset, count, type); - } - - public static IAsyncResult BeginGetBounces(this PostmarkClient client, int offset, int count) - { - return client.GetBouncesAsync(offset, count); - } - - public static IAsyncResult BeginGetBounces - (this PostmarkClient client, PostmarkBounceType type, - bool? inactive, int offset, int count) - { - return client.GetBouncesAsync(offset, count, type, inactive); - } - - public static IAsyncResult BeginGetBounces(this PostmarkClient client, - bool? inactive, int offset, int count) - { - return client.GetBouncesAsync(offset, count, inactive: inactive); - } - - public static IAsyncResult BeginGetBounces(this PostmarkClient client, - PostmarkBounceType type, string emailFilter, int offset, int count) - { - return client.GetBouncesAsync(offset, count, type, emailFilter: emailFilter); - } - - public static IAsyncResult BeginGetBounces(this PostmarkClient client, string emailFilter, int offset, int count) - { - return client.GetBouncesAsync(offset, count, emailFilter: emailFilter); - } - - public static IAsyncResult BeginGetBounces(this PostmarkClient client, - PostmarkBounceType type, string emailFilter, string tag, int offset, int count) - { - return client.GetBouncesAsync(offset, count, type, emailFilter: emailFilter, tag: tag); - } - - public static IAsyncResult BeginGetBounces(this PostmarkClient client, string emailFilter, string tag, int offset, int count) - { - return client.GetBouncesAsync(offset, count, emailFilter: emailFilter, tag: tag); - } - - public static IAsyncResult BeginGetBounces(this PostmarkClient client, - PostmarkBounceType type, bool? inactive, string emailFilter, int offset, int count) - { - return client.GetBouncesAsync(offset, count, type, inactive, emailFilter); - } - - public static IAsyncResult BeginGetBounces(this PostmarkClient client, - bool? inactive, string emailFilter, int offset, int count) - { - return client.GetBouncesAsync(offset, count, inactive: inactive, emailFilter: emailFilter); - } - - public static PostmarkBounces EndGetBounces(this PostmarkClient client, IAsyncResult asyncResult) - { - return asyncResult.UnwrapResult(); - } - - public static IAsyncResult BeginGetBounce(this PostmarkClient client, string bounceId) - { - return client.GetBounceAsync(int.Parse(bounceId)); - } - - public static PostmarkBounce EndGetBounce(this PostmarkClient client, IAsyncResult asyncResult) - { - return asyncResult.UnwrapResult(); - } - - public static IAsyncResult BeginGetBounceDump(this PostmarkClient client, string bounceId) - { - return client.GetBounceDumpAsync(int.Parse(bounceId)); - } - - public static PostmarkBounceDump EndGetBounceDump(this PostmarkClient client, IAsyncResult asyncResult) - { - return asyncResult.UnwrapResult(); - } - - public static IAsyncResult BeginActivateBounce(this PostmarkClient client, string bounceId) - { - return client.ActivateBounceAsync(int.Parse(bounceId)); - } - - public static PostmarkBounceActivation EndActivateBounce(this PostmarkClient client, IAsyncResult asyncResult) - { - return asyncResult.UnwrapResult(); - } - - public static IAsyncResult BeginGetOutboundMessages(this PostmarkClient client, int count, string subject, int offset) - { - return client.GetOutboundMessagesAsync(offset, count, subject: subject); - } - - public static IAsyncResult BeginGetOutboundMessages(this PostmarkClient client, int count, int offset, string recipient) - { - return client.GetOutboundMessagesAsync(offset, count, recipient: recipient); - } - - public static IAsyncResult BeginGetOutboundMessages(this PostmarkClient client, int count, int offset) - { - return client.GetOutboundMessagesAsync(offset, count); - } - - public static IAsyncResult BeginGetOutboundMessages(this PostmarkClient client, - string recipient, string fromemail, int count, int offset) - { - return client.GetOutboundMessagesAsync(offset, count, recipient, fromemail); - } - - public static IAsyncResult BeginGetOutboundMessages(this PostmarkClient client, string subject, int count, int offset) - { - return client.GetOutboundMessagesAsync(offset, count, subject: subject); - } - - public static IAsyncResult BeginGetOutboundMessages - (this PostmarkClient client, string fromemail, string tag, - string subject, int count, int offset) - { - return client.GetOutboundMessagesAsync(offset, count, fromemail: fromemail, tag: tag, subject: subject); - } - - public static IAsyncResult BeginGetOutboundMessages - (this PostmarkClient client, string recipient, string fromemail, - string tag, string subject, int count, int offset) - { - return client.GetOutboundMessagesAsync(offset, count, recipient, fromemail, tag, subject); - } - - public static PostmarkOutboundMessageList EndGetOutboundMessages(this PostmarkClient client, IAsyncResult asyncResult) - { - return asyncResult.UnwrapResult(); - } - - public static IAsyncResult BeginGetOutboundMessageDetail(this PostmarkClient client, string messageID) - { - return client.GetOutboundMessageDetailsAsync(messageID); - } - - public static OutboundMessageDetail EndGetOutboundMessageDetail(this PostmarkClient client, IAsyncResult asyncResult) - { - return asyncResult.UnwrapResult(); - } - - public static IAsyncResult BeginGetOutboundMessageDump(this PostmarkClient client, string messageID) - { - return client.GetOutboundMessageDumpAsync(messageID); - } - - public static MessageDump EndGetOutboundMessageDump(this PostmarkClient client, IAsyncResult asyncResult) - { - return asyncResult.UnwrapResult(); - } - - public static IAsyncResult BeginGetInboundMessages(this PostmarkClient client, int count, int offset) - { - return client.GetInboundMessagesAsync(offset, count); - } - - public static IAsyncResult BeginGetInboundMessages - (this PostmarkClient client, string fromemail, int count, int offset) - { - return client.GetInboundMessagesAsync(offset, count, fromemail: fromemail); - } - - public static IAsyncResult BeginGetInboundMessages - (this PostmarkClient client, string fromemail, string subject, int count, int offset) - { - return client.GetInboundMessagesAsync(offset, count, fromemail: fromemail, subject: subject); - } - - public static IAsyncResult BeginGetInboundMessages - (this PostmarkClient client, string recipient, string fromemail, - string subject, int count, int offset) - { - return client.GetInboundMessagesAsync(offset, count, recipient, fromemail, subject); - } - - public static IAsyncResult BeginGetInboundMessages - (this PostmarkClient client, string recipient, string fromemail, - string subject, string mailboxhash, int count, int offset) - { - return client.GetInboundMessagesAsync(offset, count, recipient, fromemail, subject, mailboxhash); - } - - public static PostmarkInboundMessageList EndGetInboundMessages(this PostmarkClient client, IAsyncResult asyncResult) - { - return asyncResult.UnwrapResult(); - } - - public static IAsyncResult BeginGetInboundMessageDetail(this PostmarkClient client, string messageID) - { - return client.GetInboundMessageDetailsAsync(messageID); - } - - public static InboundMessageDetail EndGetInboundMessageDetail(this PostmarkClient client, IAsyncResult asyncResult) - { - return asyncResult.UnwrapResult(); - } - - public static PostmarkResponse SendMessage(this PostmarkClient client, string from, string to, string subject, string textBody, string htmlBody) - { - return Task.Run(async () => await client.SendMessageAsync(from, to, subject, textBody, htmlBody)).Result; - } - - public static PostmarkResponse SendMessage(this PostmarkClient client, PostmarkMessage message) - { - return Task.Run(async () => await client.SendMessageAsync(message)).Result; - } - - public static IEnumerable SendMessages(this PostmarkClient client, params PostmarkMessage[] messages) - { - return Task.Run(async () => await client.SendMessagesAsync(messages)).Result; - } - - public static IEnumerable SendMessages(this PostmarkClient client, IEnumerable messages) - { - return Task.Run(async () => await client.SendMessagesAsync(messages)).Result; - } - - public static PostmarkDeliveryStats GetDeliveryStats(this PostmarkClient client) - { - return Task.Run(async () => await client.GetDeliveryStatsAsync()).Result; - } - - public static PostmarkBounces GetBounces - (this PostmarkClient client, bool? inactive, string emailFilter, - string tag, int offset, int count) - { - return Task.Run(async () => await client.GetBouncesAsync(offset, count, null, inactive, emailFilter, tag)).Result; - } - - public static PostmarkBounces GetBounces(this PostmarkClient client, - PostmarkBounceType type, bool? inactive, string emailFilter, string tag, int offset, int count) - { - return Task.Run(async () => await client.GetBouncesAsync(offset, count, type, inactive, emailFilter, tag)).Result; - } - - public static PostmarkBounces GetBounces(this PostmarkClient client, PostmarkBounceType type, int offset, int count) - { - return Task.Run(async () => await client.GetBouncesAsync(offset, count, type)).Result; - } - - public static PostmarkBounces GetBounces(this PostmarkClient client, int offset, int count) - { - return Task.Run(async () => await client.GetBouncesAsync(offset, count)).Result; - } - - public static PostmarkBounces GetBounces - (this PostmarkClient client, PostmarkBounceType type, bool? inactive, int offset, int count) - { - return Task.Run(async () => await client.GetBouncesAsync(offset, count, type, inactive)).Result; - } - - public static PostmarkBounces GetBounces(this PostmarkClient client, bool? inactive, int offset, int count) - { - return Task.Run(async () => await client.GetBouncesAsync(offset, count, inactive: inactive)).Result; - } - - public static PostmarkBounces GetBounces(this PostmarkClient client, - PostmarkBounceType type, string emailFilter, int offset, int count) - { - return Task.Run(async () => await client.GetBouncesAsync(offset, count, type, emailFilter: emailFilter)).Result; - } - - public static PostmarkBounces GetBounces(this PostmarkClient client, - string emailFilter, int offset, int count) - { - return Task.Run(async () => await client.GetBouncesAsync(offset, count, emailFilter: emailFilter)).Result; - } - - public static PostmarkBounces GetBounces(this PostmarkClient client, - PostmarkBounceType type, string emailFilter, string tag, int offset, int count) - { - return Task.Run(async () => await client.GetBouncesAsync(offset, count, type, emailFilter: emailFilter, tag: tag)).Result; - } - - public static PostmarkBounces GetBounces(this PostmarkClient client, - string emailFilter, string tag, int offset, int count) - { - return Task.Run(async () => await client.GetBouncesAsync(offset, count, emailFilter: emailFilter, tag: tag)).Result; - } - - public static PostmarkBounces GetBounces(this PostmarkClient client, - PostmarkBounceType type, bool? inactive, string emailFilter, int offset, int count) - { - return Task.Run(async () => await client.GetBouncesAsync(offset, count, type, - inactive: inactive, emailFilter: emailFilter)).Result; - } - - public static PostmarkBounces GetBounces(this PostmarkClient client, - bool? inactive, string emailFilter, int offset, int count) - { - return Task.Run(async () => await client.GetBouncesAsync(offset, count, - inactive: inactive, emailFilter: emailFilter)).Result; - } - - public static PostmarkBounce GetBounce(this PostmarkClient client, string bounceId) - { - return Task.Run(async () => await client.GetBounceAsync(int.Parse(bounceId))).Result; - } - - public static PostmarkBounceDump GetBounceDump(this PostmarkClient client, string bounceId) - { - return Task.Run(async () => await client.GetBounceDumpAsync(int.Parse(bounceId))).Result; - } - - public static PostmarkBounceActivation ActivateBounce(this PostmarkClient client, string bounceId) - { - return Task.Run(async () => await client.ActivateBounceAsync(int.Parse(bounceId))).Result; - } - - public static PostmarkOutboundMessageList GetOutboundMessages(this PostmarkClient client, int count, string subject, int offset) - { - return Task.Run(async () => await client.GetOutboundMessagesAsync(offset, count, subject: subject)).Result; - } - - public static PostmarkOutboundMessageList GetOutboundMessages(this PostmarkClient client, int count, int offset, string recipient) - { - return Task.Run(async () => await client.GetOutboundMessagesAsync(offset, count, recipient)).Result; - } - - public static PostmarkOutboundMessageList GetOutboundMessages(this PostmarkClient client, int count, int offset) - { - return Task.Run(async () => await client.GetOutboundMessagesAsync(offset, count)).Result; - } - - public static PostmarkOutboundMessageList GetOutboundMessages(this PostmarkClient client, - string recipient, string fromemail, int count, int offset) - { - return Task.Run(async () => await client.GetOutboundMessagesAsync(offset, count, recipient, fromemail)).Result; - } - - public static PostmarkOutboundMessageList GetOutboundMessages(this PostmarkClient client, - string subject, int count, int offset) - { - return Task.Run(async () => await client.GetOutboundMessagesAsync(offset, count, subject: subject)).Result; - } - - public static PostmarkOutboundMessageList GetOutboundMessages(this PostmarkClient client, - string fromemail, string tag, string subject, int count, int offset) - { - return Task.Run(async () => await client.GetOutboundMessagesAsync(offset, count, - fromemail: fromemail, subject: subject, tag: tag)).Result; - } - - public static PostmarkOutboundMessageList GetOutboundMessages(this PostmarkClient client, - string recipient, string fromemail, string tag, string subject, int count, int offset) - { - return Task.Run(async () => await client.GetOutboundMessagesAsync(offset, count, recipient, fromemail, tag, subject)).Result; - } - - public static OutboundMessageDetail GetOutboundMessageDetail(this PostmarkClient client, string messageID) - { - return Task.Run(async () => await client.GetOutboundMessageDetailsAsync(messageID)).Result; - } - - public static MessageDump GetOutboundMessageDump(this PostmarkClient client, string messageID) - { - return Task.Run(async () => await client.GetOutboundMessageDumpAsync(messageID)).Result; - } - - public static PostmarkInboundMessageList GetInboundMessages(this PostmarkClient client, int count, int offset) - { - return Task.Run(async () => await client.GetInboundMessagesAsync(offset, count)).Result; - } - - public static PostmarkInboundMessageList GetInboundMessages(this PostmarkClient client, - string fromemail, int count, int offset) - { - return Task.Run(async () => await client.GetInboundMessagesAsync(offset, count, fromemail: fromemail)).Result; - } - - public static PostmarkInboundMessageList GetInboundMessages(this PostmarkClient client, - string fromemail, string subject, int count, int offset) - { - return Task.Run(async () => await client.GetInboundMessagesAsync(offset, count, fromemail: fromemail, subject: subject)).Result; - } - - public static PostmarkInboundMessageList GetInboundMessages(this PostmarkClient client, - string recipient, string fromemail, string subject, int count, int offset) - { - return Task.Run(async () => await client.GetInboundMessagesAsync(offset, count, recipient, fromemail, subject)).Result; - } - - public static PostmarkInboundMessageList GetInboundMessages(this PostmarkClient client, - string recipient, string fromemail, string subject, string mailboxhash, int count, int offset) - { - return Task.Run(async () => await client.GetInboundMessagesAsync(offset, count, recipient, fromemail, subject, mailboxhash)).Result; - } - - public static PostmarkInboundMessage GetInboundMessageDetail(this PostmarkClient client, string messageID) - { - return Task.Run(async () => await client.GetInboundMessageDetailsAsync(messageID)).Result; - } - } -} -#pragma warning restore CS1591 \ No newline at end of file diff --git a/src/Postmark/Postmark.csproj b/src/Postmark/Postmark.csproj index 4295423..0099195 100644 --- a/src/Postmark/Postmark.csproj +++ b/src/Postmark/Postmark.csproj @@ -14,7 +14,9 @@ + + - + \ No newline at end of file diff --git a/src/Postmark/PostmarkAdminClient.cs b/src/Postmark/PostmarkAdminClient.cs index dcd297a..63b4057 100644 --- a/src/Postmark/PostmarkAdminClient.cs +++ b/src/Postmark/PostmarkAdminClient.cs @@ -1,418 +1,417 @@ -using PostmarkDotNet.Model; using System; using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; +using PostmarkDotNet.Model; -namespace PostmarkDotNet +namespace PostmarkDotNet; + +/// +/// Postmark Client that supports access to the Administrative APIs, +/// to send email, use the PostmarkClient, instead. +/// +/// +/// Make sure to include "using PostmarkDotNet;" in your class file, which will include extension methods on the base +/// client. +/// +public class PostmarkAdminClient : PostmarkClientBase, IPostmarkAdminClient { + /// + /// Construct a PostmarkAdminClient. + /// + /// + public PostmarkAdminClient(HttpClient client) + : base(client) + { + } + + /// + /// Get a server with the associated serverId. + /// + /// + /// + public async Task GetServerAsync(int serverId) + { + var retval = await ProcessNoBodyRequestAsync("/servers/" + serverId); + //the API doesn't return the server ID here, which would be helpful. + retval.ID = serverId; + return retval; + } + + /// + /// Get a server with the associated serverId. + /// + /// + /// To protected your account, you must first request access to use this endpont from support@postmarkapp.com + /// + public async Task DeleteServerAsync(int serverId) + { + // Adding a retry mechanism because server deletion currently fails intermittently due to deadlocks. + // This is a temporary fix, and should be removed once the server delete operation is more reliable. + var attemptsRemain = 5; + while (true) + try + { + return await ProcessNoBodyRequestAsync("/servers/" + serverId, verb: HttpMethod.Delete); + } + catch + { + if (--attemptsRemain == 0) + throw; + } + } + + /// + /// Create a new Server. + /// + /// + public async Task CreateServerAsync(string name, string color = null, + bool? rawEmailEnabled = null, bool? smtpApiActivated = null, string inboundHookUrl = null, + string bounceHookUrl = null, string openHookUrl = null, bool? postFirstOpenOnly = null, + bool? trackOpens = null, string inboundDomain = null, int? inboundSpamThreshold = null, + LinkTrackingOptions? trackLinks = null, string clickHookUrl = null, string deliveryHookUrl = null, + bool? enableSmtpApiErrorHooks = null, string deliveryType = null) + { + var body = new Dictionary(); + body["Name"] = name; + body["Color"] = color; + body["RawEmailEnabled"] = rawEmailEnabled; + body["SmtpApiActivated"] = smtpApiActivated; + body["InboundHookUrl"] = inboundHookUrl; + body["BounceHookUrl"] = bounceHookUrl; + body["OpenHookUrl"] = openHookUrl; + body["PostFirstOpenOnly"] = postFirstOpenOnly; + body["TrackOpens"] = trackOpens; + body["InboundDomain"] = inboundDomain; + body["InboundSpamThreshold"] = inboundSpamThreshold; + body["TrackLinks"] = trackLinks; + body["ClickHookUrl"] = clickHookUrl; + body["DeliveryHookUrl"] = deliveryHookUrl; + body["EnableSmtpApiErrorHooks"] = enableSmtpApiErrorHooks; + body["DeliveryType"] = deliveryType; + + return await ProcessRequestAsync, PostmarkServer>("/servers/", HttpMethod.Post, body); + } + + /// + /// Update a Server. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public async Task EditServerAsync(int serverId, string name = null, string color = null, + bool? rawEmailEnabled = null, bool? smtpApiActivated = null, string inboundHookUrl = null, + string bounceHookUrl = null, string openHookUrl = null, bool? postFirstOpenOnly = null, + bool? trackOpens = null, string inboundDomain = null, int? inboundSpamThreshold = null, + LinkTrackingOptions? trackLinks = null, string clickHookUrl = null, string deliveryHookUrl = null, bool? enableSmtpApiErrorHooks = null) + { + var body = new Dictionary(); + body["Name"] = name; + body["Color"] = color; + body["RawEmailEnabled"] = rawEmailEnabled; + body["SmtpApiActivated"] = smtpApiActivated; + body["InboundHookUrl"] = inboundHookUrl; + body["BounceHookUrl"] = bounceHookUrl; + body["OpenHookUrl"] = openHookUrl; + body["PostFirstOpenOnly"] = postFirstOpenOnly; + body["TrackOpens"] = trackOpens; + body["InboundDomain"] = inboundDomain; + body["InboundSpamThreshold"] = inboundSpamThreshold; + body["TrackLinks"] = trackLinks; + body["ClickHookUrl"] = clickHookUrl; + body["DeliveryHookUrl"] = deliveryHookUrl; + body["EnableSmtpApiErrorHooks"] = enableSmtpApiErrorHooks; + + return await ProcessRequestAsync, PostmarkServer> + ("/servers/" + serverId, HttpMethod.Put, body); + } + + /// + /// Get a specific sender signature. + /// + /// + /// + /// + public async Task GetSenderSignaturesAsync(int offset = 0, int count = 100) + { + var parameters = new Dictionary(); + parameters["offset"] = offset; + parameters["count"] = count; + return await ProcessNoBodyRequestAsync("/senders", parameters); + } + + + /// + /// Retrieve a sender signature. + /// + /// + /// + public async Task GetSenderSignatureAsync(int signatureId) + { + return await ProcessNoBodyRequestAsync("/senders/" + signatureId); + } + + /// + /// Delete a sender signature. + /// + /// + /// + public async Task DeleteSignatureAsync(int signatureId) + { + return await ProcessNoBodyRequestAsync + ("/senders/" + signatureId, verb: HttpMethod.Delete); + } + + /// + /// Cause a new sender signature verification email to be sent to the associated email address. + /// + /// + /// + public async Task ResendSignatureVerificationEmailAsync(int signatureId) + { + return await ProcessNoBodyRequestAsync + ("/senders/" + signatureId + "/resend", verb: HttpMethod.Post); + } + + /// + /// Creates a new DKIM key to be created. Until the DNS entries are confirmed, the new values will be in the + /// DKIMPendingHost and DKIMPendingTextValue fields. After the new DKIM value is verified in DNS, the pending values + /// will migrate to DKIMTextValue and DKIMPendingTextValue and Postmark will begin to sign emails with the new DKIM + /// key. + /// + /// + /// + public async Task RequestNewSignatureDKIMAsync(int signatureId) + { + return await ProcessNoBodyRequestAsync + ("/senders/" + signatureId + "/requestnewdkim", verb: HttpMethod.Post); + } + + /// + /// Create a new sender signature. Note that you'll need to + /// verify this by clicking on the verification link sent to the associated email address. + /// + /// + /// + /// + /// + /// + /// + public async Task CreateSignatureAsync(string fromEmail, string name, string replyToEmail = null, string returnPathDomain = null, string confirmationPersonalNote = null) + { + var parameters = new Dictionary(); + parameters["FromEmail"] = fromEmail; + parameters["Name"] = name; + parameters["ReplyToEmail"] = replyToEmail; + parameters["ReturnPathDomain"] = returnPathDomain; + parameters["ConfirmationPersonalNote"] = confirmationPersonalNote; + + return await ProcessRequestAsync, PostmarkCompleteSenderSignature> + ("/senders/", HttpMethod.Post, parameters); + } + + /// + /// Modift an existing sender signature. + /// + /// + /// + /// + /// + /// + /// + public async Task UpdateSignatureAsync + (int signatureId, string name = null, string replyToEmail = null, string returnPathDomain = null, string confirmationPersonalNote = null) + { + var parameters = new Dictionary(); + parameters["Name"] = name; + parameters["ReplyToEmail"] = replyToEmail; + parameters["ReturnPathDomain"] = returnPathDomain; + parameters["ConfirmationPersonalNote"] = confirmationPersonalNote; + + return await ProcessRequestAsync, PostmarkCompleteSenderSignature> + ("/senders/" + signatureId, HttpMethod.Put, parameters); + } + + /// + /// Get a list of domains. + /// + /// + /// + /// + public async Task GetDomainsAsync(int offset = 0, int count = 100) + { + var parameters = new Dictionary(); + parameters["offset"] = offset; + parameters["count"] = count; + return await ProcessNoBodyRequestAsync("/domains", parameters); + } + + + /// + /// Retrieve a domain. + /// + /// + /// + public async Task GetDomainAsync(int domainId) + { + return await ProcessNoBodyRequestAsync($"/domains/{domainId}"); + } + + /// + /// Delete a domain. + /// + /// + /// + public async Task DeleteDomainAsync(int domainId) + { + return await ProcessNoBodyRequestAsync + ("/domains/" + domainId, verb: HttpMethod.Delete); + } + + /// + /// Creates a new DKIM key to be created. Until the DNS entries are confirmed, the new values will be in the + /// DKIMPendingHost and DKIMPendingTextValue fields. After the new DKIM value is verified in DNS, the pending values + /// will migrate to DKIMTextValue and DKIMPendingTextValue and Postmark will begin to sign emails with the new DKIM + /// key. + /// + /// + /// + public async Task RequestNewDomainDKIMAsync(int domainId) + { + return await ProcessNoBodyRequestAsync + ("/domains/" + domainId + "/rotatedkim", verb: HttpMethod.Post); + } + + /// + /// Create a new domain. + /// + /// + /// + /// + public async Task CreateDomainAsync(string name, string returnPathDomain = null) + { + var parameters = new Dictionary(); + parameters["Name"] = name; + parameters["ReturnPathDomain"] = returnPathDomain; + + return await ProcessRequestAsync, PostmarkCompleteDomain> + ("/domains/", HttpMethod.Post, parameters); + } + + /// + /// Modify an existing domain. + /// + /// + /// + /// Setting this to null or empty will clear your Return-Path + /// + public async Task UpdateDomainAsync + (int domainId, string returnPathDomain) + { + var parameters = new Dictionary(); + parameters["ReturnPathDomain"] = returnPathDomain ?? ""; + + return await ProcessRequestAsync, PostmarkCompleteDomain> + ($"/domains/{domainId}", HttpMethod.Put, parameters); + } + + /// + /// Verify DKIM record for a domain + /// + /// + /// + public async Task VerifyDomainDkim(int domainId) + { + return await ProcessNoBodyRequestAsync + ($"/domains/{domainId}/verifydkim", verb: HttpMethod.Put); + } + + /// + /// Verify DKIM record for a domain + /// + /// + /// + public async Task VerifyDomainReturnPath(int domainId) + { + return await ProcessNoBodyRequestAsync + ($"/domains/{domainId}/verifyreturnpath", verb: HttpMethod.Put); + } + + + /// + /// List all servers that are currently configured for this account. + /// + /// + /// + /// + /// + public async Task GetServersAsync(int offset = 0, int count = 100, string name = null) + { + var parameters = new Dictionary(); + parameters["offset"] = offset; + parameters["count"] = count; + parameters["name"] = name; + + return await ProcessNoBodyRequestAsync("/servers", parameters); + } + + /// + /// Create a data removal request. + /// + /// The email address of the user that is making the request. + /// + /// The email address of the recipient who's asking for their data to be removed. This must be a + /// valid email address. + /// + /// + /// Specifies whether the RequestedBy email address is notified when the data removal + /// request is complete. + /// + /// + public async Task RequestDataRemoval(string requestedBy, string requestedFor, bool notifyWhenCompleted) + { + var parameters = new Dictionary(); + parameters["RequestedBy"] = requestedBy; + parameters["RequestedFor"] = requestedFor; + parameters["NotifyWhenCompleted"] = notifyWhenCompleted; + + return await ProcessRequestAsync, PostmarkDataRemoval>("/data-removals", HttpMethod.Post, parameters); + } + + /// + /// Check a data removal request status. + /// + /// + /// + public async Task GetDataRemovalStatus(long id) + { + return await ProcessNoBodyRequestAsync($"/data-removals/{id}"); + } + /// - /// Postmark Client that supports access to the Administrative APIs, - /// to send email, use the PostmarkClient, instead. + /// Will query DNS for your domain and attempt to verify the SPF record contains the information for Postmark's + /// servers. /// - /// - /// Make sure to include "using PostmarkDotNet;" in your class file, which will include extension methods on the base client. - /// - public class PostmarkAdminClient : PostmarkClientBase - { - /// - /// Construct a PostmarkAdminClient. - /// - /// The "accountToken" can be found by logging into your Postmark and navigating to https://postmarkapp.com/account/edit - Keep this token secret and safe. - /// Optionally override the base url to the API. For example, you may fallback to HTTP (non-SSL) if your app requires it, though, this is not recommended. - public PostmarkAdminClient(string accountToken, string apiBaseUri = "https://api.postmarkapp.com") - : base(apiBaseUri) - { - _authToken = accountToken; - } - - /// - /// The authentication header required for Admin API interactions, in this case: "X-Postmark-Account-Token" - /// - protected override string AuthHeaderName => "X-Postmark-Account-Token"; - - /// - /// Get a server with the associated serverId. - /// - /// - /// - public async Task GetServerAsync(int serverId) - { - var retval = await this.ProcessNoBodyRequestAsync("/servers/" + serverId); - //the API doesn't return the server ID here, which would be helpful. - retval.ID = serverId; - return retval; - } - - - /// - /// Get a server with the associated serverId. - /// - /// - /// To protected your account, you must first request access to use this endpont from support@postmarkapp.com - /// - public async Task DeleteServerAsync(int serverId) - { - // Adding a retry mechanism because server deletion currently fails intermittently due to deadlocks. - // This is a temporary fix, and should be removed once the server delete operation is more reliable. - var attemptsRemain = 5; - while (true) - { - try - { - return await this.ProcessNoBodyRequestAsync("/servers/" + serverId, verb: HttpMethod.Delete); - } - catch - { - if (--attemptsRemain == 0) - { - throw; - } - } - } - } - - /// - /// Create a new Server. - /// - /// - public async Task CreateServerAsync(String name, string color = null, - bool? rawEmailEnabled = null, bool? smtpApiActivated = null, string inboundHookUrl = null, - string bounceHookUrl = null, string openHookUrl = null, bool? postFirstOpenOnly = null, - bool? trackOpens = null, string inboundDomain = null, int? inboundSpamThreshold = null, - LinkTrackingOptions? trackLinks = null, string clickHookUrl = null, string deliveryHookUrl = null, - bool? enableSmtpApiErrorHooks = null, string deliveryType = null) - { - - var body = new Dictionary(); - body["Name"] = name; - body["Color"] = color; - body["RawEmailEnabled"] = rawEmailEnabled; - body["SmtpApiActivated"] = smtpApiActivated; - body["InboundHookUrl"] = inboundHookUrl; - body["BounceHookUrl"] = bounceHookUrl; - body["OpenHookUrl"] = openHookUrl; - body["PostFirstOpenOnly"] = postFirstOpenOnly; - body["TrackOpens"] = trackOpens; - body["InboundDomain"] = inboundDomain; - body["InboundSpamThreshold"] = inboundSpamThreshold; - body["TrackLinks"] = trackLinks; - body["ClickHookUrl"] = clickHookUrl; - body["DeliveryHookUrl"] = deliveryHookUrl; - body["EnableSmtpApiErrorHooks"] = enableSmtpApiErrorHooks; - body["DeliveryType"] = deliveryType; - - return await this.ProcessRequestAsync, PostmarkServer>("/servers/", HttpMethod.Post, body); - } - - /// - /// Update a Server. - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public async Task EditServerAsync(int serverId, String name = null, string color = null, - bool? rawEmailEnabled = null, bool? smtpApiActivated = null, string inboundHookUrl = null, - string bounceHookUrl = null, string openHookUrl = null, bool? postFirstOpenOnly = null, - bool? trackOpens = null, string inboundDomain = null, int? inboundSpamThreshold = null, - LinkTrackingOptions? trackLinks = null, string clickHookUrl = null, string deliveryHookUrl = null, bool? enableSmtpApiErrorHooks = null) - { - - var body = new Dictionary(); - body["Name"] = name; - body["Color"] = color; - body["RawEmailEnabled"] = rawEmailEnabled; - body["SmtpApiActivated"] = smtpApiActivated; - body["InboundHookUrl"] = inboundHookUrl; - body["BounceHookUrl"] = bounceHookUrl; - body["OpenHookUrl"] = openHookUrl; - body["PostFirstOpenOnly"] = postFirstOpenOnly; - body["TrackOpens"] = trackOpens; - body["InboundDomain"] = inboundDomain; - body["InboundSpamThreshold"] = inboundSpamThreshold; - body["TrackLinks"] = trackLinks; - body["ClickHookUrl"] = clickHookUrl; - body["DeliveryHookUrl"] = deliveryHookUrl; - body["EnableSmtpApiErrorHooks"] = enableSmtpApiErrorHooks; - - return await this.ProcessRequestAsync, PostmarkServer> - ("/servers/" + serverId, HttpMethod.Put, body); - } - - /// - /// Get a specific sender signature. - /// - /// - /// - /// - public async Task GetSenderSignaturesAsync(int offset = 0, int count = 100) - { - var parameters = new Dictionary(); - parameters["offset"] = offset; - parameters["count"] = count; - return await this.ProcessNoBodyRequestAsync("/senders", parameters); - } - - - /// - /// Retrieve a sender signature. - /// - /// - /// - public async Task GetSenderSignatureAsync(int signatureId) - { - return await this.ProcessNoBodyRequestAsync("/senders/" + signatureId); - } - - /// - /// Delete a sender signature. - /// - /// - /// - public async Task DeleteSignatureAsync(int signatureId) - { - return await this.ProcessNoBodyRequestAsync - ("/senders/" + signatureId, verb: HttpMethod.Delete); - } - - /// - /// Cause a new sender signature verification email to be sent to the associated email address. - /// - /// - /// - public async Task ResendSignatureVerificationEmailAsync(int signatureId) - { - return await this.ProcessNoBodyRequestAsync - ("/senders/" + signatureId + "/resend", verb: HttpMethod.Post); - } - - /// - /// Creates a new DKIM key to be created. Until the DNS entries are confirmed, the new values will be in the DKIMPendingHost and DKIMPendingTextValue fields. After the new DKIM value is verified in DNS, the pending values will migrate to DKIMTextValue and DKIMPendingTextValue and Postmark will begin to sign emails with the new DKIM key. - /// - /// - /// - public async Task RequestNewSignatureDKIMAsync(int signatureId) - { - return await this.ProcessNoBodyRequestAsync - ("/senders/" + signatureId + "/requestnewdkim", verb: HttpMethod.Post); - } - - /// - /// Will query DNS for your domain and attempt to verify the SPF record contains the information for Postmark's servers. - /// - /// - /// - [Obsolete("We no longer require SPF verification. See here for details: https://postmarkapp.com/blog/why-we-no-longer-ask-for-spf-records")] - public async Task VerifySignatureSPF(int signatureId) - { - return await this.ProcessNoBodyRequestAsync - ("/senders/" + signatureId + "/verifyspf", verb: HttpMethod.Post); - } - - /// - /// Create a new sender signature. Note that you'll need to - /// verify this by clicking on the verification link sent to the associated email address. - /// - /// - /// - /// - /// - /// - /// - public async Task CreateSignatureAsync(string fromEmail, string name, string replyToEmail = null, string returnPathDomain = null, string confirmationPersonalNote = null) - { - var parameters = new Dictionary(); - parameters["FromEmail"] = fromEmail; - parameters["Name"] = name; - parameters["ReplyToEmail"] = replyToEmail; - parameters["ReturnPathDomain"] = returnPathDomain; - parameters["ConfirmationPersonalNote"] = confirmationPersonalNote; - - return await this.ProcessRequestAsync, PostmarkCompleteSenderSignature> - ("/senders/", HttpMethod.Post, parameters); - } - - /// - /// Modift an existing sender signature. - /// - /// - /// - /// - /// - /// - /// - public async Task UpdateSignatureAsync - (int signatureId, string name = null, string replyToEmail = null, string returnPathDomain = null, string confirmationPersonalNote = null) - { - var parameters = new Dictionary(); - parameters["Name"] = name; - parameters["ReplyToEmail"] = replyToEmail; - parameters["ReturnPathDomain"] = returnPathDomain; - parameters["ConfirmationPersonalNote"] = confirmationPersonalNote; - - return await this.ProcessRequestAsync, PostmarkCompleteSenderSignature> - ("/senders/" + signatureId, HttpMethod.Put, parameters); - } - - /// - /// Get a list of domains. - /// - /// - /// - /// - public async Task GetDomainsAsync(int offset = 0, int count = 100) - { - var parameters = new Dictionary(); - parameters["offset"] = offset; - parameters["count"] = count; - return await this.ProcessNoBodyRequestAsync("/domains", parameters); - } - - - /// - /// Retrieve a domain. - /// - /// - /// - public async Task GetDomainAsync(int domainId) - { - return await this.ProcessNoBodyRequestAsync($"/domains/{domainId}"); - } - - /// - /// Delete a domain. - /// - /// - /// - public async Task DeleteDomainAsync(int domainId) - { - return await this.ProcessNoBodyRequestAsync - ("/domains/" + domainId, verb: HttpMethod.Delete); - } - - /// - /// Creates a new DKIM key to be created. Until the DNS entries are confirmed, the new values will be in the DKIMPendingHost and DKIMPendingTextValue fields. After the new DKIM value is verified in DNS, the pending values will migrate to DKIMTextValue and DKIMPendingTextValue and Postmark will begin to sign emails with the new DKIM key. - /// - /// - /// - public async Task RequestNewDomainDKIMAsync(int domainId) - { - return await this.ProcessNoBodyRequestAsync - ("/domains/" + domainId + "/rotatedkim", verb: HttpMethod.Post); - } - - /// - /// Create a new domain. - /// - /// - /// - /// - public async Task CreateDomainAsync(string name, string returnPathDomain = null) - { - var parameters = new Dictionary(); - parameters["Name"] = name; - parameters["ReturnPathDomain"] = returnPathDomain; - - return await this.ProcessRequestAsync, PostmarkCompleteDomain> - ("/domains/", HttpMethod.Post, parameters); - } - - /// - /// Modify an existing domain. - /// - /// - /// - /// Setting this to null or empty will clear your Return-Path - /// - public async Task UpdateDomainAsync - (int domainId, string returnPathDomain) - { - var parameters = new Dictionary(); - parameters["ReturnPathDomain"] = returnPathDomain ?? ""; - - return await this.ProcessRequestAsync, PostmarkCompleteDomain> - ($"/domains/{domainId}", HttpMethod.Put, parameters); - } - - /// - /// Verify DKIM record for a domain - /// - /// - /// - public async Task VerifyDomainDkim(int domainId) - { - return await this.ProcessNoBodyRequestAsync - ($"/domains/{domainId}/verifydkim", verb: HttpMethod.Put); - } - - /// - /// Verify DKIM record for a domain - /// - /// - /// - public async Task VerifyDomainReturnPath(int domainId) - { - return await this.ProcessNoBodyRequestAsync - ($"/domains/{domainId}/verifyreturnpath", verb: HttpMethod.Put); - } - - - /// - /// List all servers that are currently configured for this account. - /// - /// - /// - /// - /// - public async Task GetServersAsync(int offset = 0, int count = 100, string name = null) - { - var parameters = new Dictionary(); - parameters["offset"] = offset; - parameters["count"] = count; - parameters["name"] = name; - - return await this.ProcessNoBodyRequestAsync("/servers", parameters); - } - - /// - /// Create a data removal request. - /// - /// The email address of the user that is making the request. - /// The email address of the recipient who's asking for their data to be removed. This must be a valid email address. - /// Specifies whether the RequestedBy email address is notified when the data removal request is complete. - /// - public async Task RequestDataRemoval(string requestedBy, string requestedFor, bool notifyWhenCompleted) - { - var parameters = new Dictionary(); - parameters["RequestedBy"] = requestedBy; - parameters["RequestedFor"] = requestedFor; - parameters["NotifyWhenCompleted"] = notifyWhenCompleted; - - return await this.ProcessRequestAsync, PostmarkDataRemoval>("/data-removals", HttpMethod.Post, parameters); - } - - /// - /// Check a data removal request status. - /// - /// - /// - public async Task GetDataRemovalStatus(long id) - { - return await this.ProcessNoBodyRequestAsync($"/data-removals/{id}"); - } - } + /// + /// + [Obsolete("We no longer require SPF verification. See here for details: https://postmarkapp.com/blog/why-we-no-longer-ask-for-spf-records")] + public async Task VerifySignatureSPF(int signatureId) + { + return await ProcessNoBodyRequestAsync + ("/senders/" + signatureId + "/verifyspf", verb: HttpMethod.Post); + } } \ No newline at end of file diff --git a/src/Postmark/PostmarkClient.cs b/src/Postmark/PostmarkClient.cs index bed3c60..7f6f3cc 100644 --- a/src/Postmark/PostmarkClient.cs +++ b/src/Postmark/PostmarkClient.cs @@ -1,4 +1,3 @@ -using PostmarkDotNet.Model; using System; using System.Collections.Generic; using System.Linq; @@ -7,1232 +6,1196 @@ using System.Threading.Tasks; using Postmark.Model.MessageStreams; using Postmark.Model.Suppressions; +using PostmarkDotNet.Model; using PostmarkDotNet.Model.Webhooks; -namespace PostmarkDotNet +namespace PostmarkDotNet; + +/// +/// The standard Postmark API, allows all interactions with a Postmark "Server" (send/recieve/process/analyze emails). +/// This client supports normal API interactions, for Administrative API interactions, use the PostmarkAdminClient. +/// +/// +/// Make sure to include "using PostmarkDotNet;" in your class file, which will include extension methods on the base +/// client. +/// +public class PostmarkClient : PostmarkClientBase, IPostmarkClient { - /// - /// The standard Postmark API, allows all interactions with a Postmark "Server" (send/recieve/process/analyze emails). - /// This client supports normal API interactions, for Administrative API interactions, use the PostmarkAdminClient. - /// - /// - /// Make sure to include "using PostmarkDotNet;" in your class file, which will include extension methods on the base client. - /// - public class PostmarkClient : PostmarkClientBase - { - /// - /// The authorization header required, in this case, "X-Postmark-Server-Token" - /// - protected override string AuthHeaderName - { - get { return "X-Postmark-Server-Token"; } - } - - /// - /// Instantiate the client. - /// - /// The base uri to use when connecting to Postmark. You should rarely need to modify this, except if you want to disable TLS (not recommended), or you are using a proxy of some sort to connect to the API. - /// Used for requests that require server level privileges. This token can be found on the Credentials tab under your Postmark server. - public PostmarkClient(string serverToken, string apiBaseUri = "https://api.postmarkapp.com") - : base(apiBaseUri) - { - _authToken = serverToken; - } - - #region Email Sending - /// - /// Sends a message through the Postmark API. - /// All email addresses must be valid, and the sender must be - /// a valid sender signature according to Postmark. To obtain a valid - /// sender signature, log in to Postmark and navigate to: - /// http://postmarkapp.com/signatures. - /// - /// A prepared message. - /// - public async Task SendMessageAsync(PostmarkMessage message) - { - return await ProcessRequestAsync("/email", HttpMethod.Post, message); - } - - /// - /// Sends a batch of up to 500 messages through the Postmark API. - /// All email addresses must be valid, and the sender must be - /// a valid sender signature according to Postmark. To obtain a valid - /// sender signature, log in to Postmark and navigate to: - /// http://postmarkapp.com/signatures. - /// - /// A prepared batch of messages. - /// The processed messages (Complete with system assigned message IDs) - public async Task> SendMessagesAsync(params PostmarkMessage[] messages) - { - return await ProcessRequestAsync("/email/batch", HttpMethod.Post, messages); - } - #endregion - - #region Bounces - - /// - /// Retrieves the bounce-related results for the - /// associated mail server. - /// - /// - public async Task GetDeliveryStatsAsync() - { - return await this.ProcessNoBodyRequestAsync("/deliverystats"); - } - - /// - /// Retrieves a collection of instances along - /// with a sum total of bounces recorded by the server, based on filter parameters. - /// - /// The type of bounces to filter on. - /// Whether to return only inactive or active bounces; use null to return all bounces. - /// Filters based on whether the filter value is contained in the bounce source's email. - /// Filters on the bounce tag. - /// Filter by MessageID. - /// The page offset for the returned results; defaults to 0. - /// The number of results to return by the page offset; defaults to 100. - /// Filter messages starting from the date specified (inclusive). e.g. 2014-02-01 - /// Filter messages up to the date specified (inclusive). e.g. 2014-02-01 - /// - /// - public async Task GetBouncesAsync(int offset = 0, int count = 100, PostmarkBounceType? type = null, - bool? inactive = null, string emailFilter = null, string tag = null, string messageID = null, string fromDate = null, string toDate = null) - { - var parameters = new Dictionary(); - parameters["type"] = type; - parameters["inactive"] = inactive; - parameters["emailFilter"] = emailFilter; - parameters["tag"] = tag; - parameters["messageID"] = messageID; - parameters["offset"] = offset; - parameters["count"] = count; - parameters["fromDate"] = fromDate; - parameters["toDate"] = toDate; - - return await ProcessNoBodyRequestAsync("/bounces", parameters); - } - - /// - /// Retrieves a single based on a specified ID. - /// - /// The bounce ID of the bounce to retrieve. - /// - /// - public async Task GetBounceAsync(long bounceId) - { - return await ProcessNoBodyRequestAsync("/bounces/" + bounceId); - } - - /// - /// Returns the raw source of the bounce we accepted. - /// If Postmark does not have a dump for that bounce, it will return an empty string. - /// - /// The bounce ID of the bounce dump to retrieve. - /// - /// - public async Task GetBounceDumpAsync(long bounceId) - { - return await ProcessNoBodyRequestAsync("/bounces/" + bounceId + "/dump"); - } - - /// - /// Activates a deactivated bounce. - /// - /// The bounce ID of the bounce to Activate - /// - /// - public async Task ActivateBounceAsync(long bounceId) - { - return await ProcessNoBodyRequestAsync("/bounces/" + bounceId + "/activate", verb: HttpMethod.Put); - } - - #endregion - - #region Outbound Message Retrieval - - /// - /// Return a listing of Outbound sent messages using the filters supported by the API. - /// - /// Filter by the recipient(s) of the message. - /// Filter by the email address the message is sent from. - /// Filter by a tag used for the message (messages sent directly through the API only) - /// Filter by message subject. - /// Number of messages to return per call. (required) - /// Number of messages to offset/page per call. (required) - /// The status of the outbound message. - /// Get messages on or after YYYY-MM-DD. - /// Get messages on or before YYYY-MM-DD. - /// PostmarkOutboundMessageList - public async Task GetOutboundMessagesAsync(int offset = 0, int count = 100, - string recipient = null, string fromemail = null, string tag = null, string subject = null, - OutboundMessageStatus status = OutboundMessageStatus.Sent, string toDate = null, string fromDate = null, IDictionary metadata = null, string messagestream = null) - { - var parameters = new Dictionary(); - parameters["count"] = count; - parameters["offset"] = offset; - parameters["recipient"] = recipient; - parameters["fromemail"] = fromemail; - parameters["tag"] = tag; - parameters["subject"] = subject; - parameters["todate"] = toDate; - parameters["fromdate"] = fromDate; - parameters["status"] = status.ToString().ToLower(); - parameters["messagestream"] = messagestream; - - if (metadata != null) - { - foreach (var a in metadata) - { - parameters[$"metadata_{a.Key}"] = a.Value; - } - } - - return await ProcessNoBodyRequestAsync("/messages/outbound", parameters); - } - - /// - /// Get the full details of a sent message including all fields, raw body, attachment names, etc - /// - /// The MessageID of a message which can be optained either from the initial API send call or a GetOutboundMessages call. - /// OutboundMessageDetail - public async Task GetOutboundMessageDetailsAsync(string messageID) - { - return await ProcessNoBodyRequestAsync("/messages/outbound/" + messageID + "/details"); - } - - /// - /// Get the original raw message dump of on outbound message including all SMTP headers and data. - /// - /// The MessageID of a message which can be optained either from the initial API send call or a GetOutboundMessages call. - /// MessageDump - public async Task GetOutboundMessageDumpAsync(string messageID) - { - return await ProcessNoBodyRequestAsync("/messages/outbound/" + messageID + "/dump"); - } - - #endregion - - #region Inbound Message Retrieval - /// - /// Return a listing of Inbound sent messages using the filters supported by the API. - /// - /// Filter by the recipient(s) of the message. - /// Filter by the email address the message is sent from. - /// Filter by message subject. - /// Filter by mailbox hash that was parsed from the inbound message. - /// Number of messages to return per call. (required) - /// Number of messages to offset/page per call. (required) - /// The status of the inbound message. - /// Get messages on or after YYYY-MM-DD. - /// Get messages on or before YYYY-MM-DD. - /// PostmarkInboundMessageList - public async Task GetInboundMessagesAsync(int offset = 0, int count = 100, - string recipient = null, string fromemail = null, string subject = null, - string mailboxhash = null, InboundMessageStatus? status = InboundMessageStatus.Processed, String toDate = null, String fromDate = null) - { - var parameters = new Dictionary(); - parameters["count"] = count; - parameters["offset"] = offset; - parameters["recipient"] = recipient; - parameters["fromemail"] = fromemail; - parameters["subject"] = subject; - parameters["mailboxhash"] = mailboxhash; - parameters["todate"] = toDate; - parameters["fromdate"] = fromDate; - parameters["status"] = status.ToString().ToLower(); - - return await ProcessNoBodyRequestAsync("/messages/inbound", parameters); - } - - /// - /// Get the full details of a processed inbound message including all fields, attachment names, etc. - /// - /// The MessageID of a message which can be optained either from the initial API send call or a GetInboundMessages call. - /// InboundMessageDetail - public async Task GetInboundMessageDetailsAsync(string messageID) - { - return await ProcessNoBodyRequestAsync("/messages/inbound/" + messageID + "/details"); - } - - /// - /// Bypass rules for a blocked inbound message. - /// - /// - /// - public async Task BypassBlockedInboundMessage(string messageid) - { - return await this.ProcessNoBodyRequestAsync(String.Format("/messages/inbound/{0}/bypass", messageid), verb: HttpMethod.Put); - } - - /// - /// Request that Postmark retries POSTing to your Inbound Hook for the specified inbound message. - /// - /// - /// - public async Task RetryInboundHookForMessage(string messageId) - { - return await this.ProcessNoBodyRequestAsync(String.Format("/messages/inbound/{0}/retry", messageId), verb: HttpMethod.Put); - } - - - #endregion - - #region Servers - - /// - /// Gets the server associated with this client based on - /// the ServerToken supplied when the client was constructed. - /// - /// - public async Task GetServerAsync() - { - return await this.ProcessNoBodyRequestAsync("/server"); - } - - /// - /// Updates the server associated with this client. Only parameters that are passed into this method are modified. - /// Any parameters that are left null will use the current value for the server. - /// - /// - public async Task EditServerAsync( - String name = null, string color = null, - bool? rawEmailEnabled = null, bool? smtpApiActivated = null, string inboundHookUrl = null, - string bounceHookUrl = null, string openHookUrl = null, bool? postFirstOpenOnly = null, - bool? trackOpens = null, string inboundDomain = null, int? inboundSpamThreshold = null, - LinkTrackingOptions? trackLinks = null, - string clickHookUrl = null, string deliveryHookUrl = null) - { - var body = new Dictionary(); - body["Name"] = name; - body["Color"] = color; - body["RawEmailEnabled"] = rawEmailEnabled; - body["SmtpApiActivated"] = smtpApiActivated; - body["InboundHookUrl"] = inboundHookUrl; - body["BounceHookUrl"] = bounceHookUrl; - body["OpenHookUrl"] = openHookUrl; - body["PostFirstOpenOnly"] = postFirstOpenOnly; - body["TrackOpens"] = trackOpens; - body["InboundDomain"] = inboundDomain; - body["InboundSpamThreshold"] = inboundSpamThreshold; - body["TrackLinks"] = trackLinks; - body["ClickHookUrl"] = clickHookUrl; - body["DeliveryHookUrl"] = deliveryHookUrl; - - body = body.Where(kv => kv.Value != null).ToDictionary(k => k.Key, v => v.Value); - - return await this.ProcessRequestAsync, PostmarkServer>("/server", HttpMethod.Put, body); - } - - #endregion - - #region Stats - - /// - /// Create parameters for the stats filtering from normal params. - /// - /// - /// - /// - /// - private IDictionary - ConstructSentStatsFilter(string tag, DateTime? fromDate, DateTime? toDate) - { - var parameters = new Dictionary(); - if (!string.IsNullOrWhiteSpace(tag)) - { - parameters["tag"] = tag; - } - if (fromDate.HasValue) - { - parameters["fromdate"] = fromDate.Value.ToString(DATE_FORMAT); - } - if (toDate.HasValue) - { - parameters["todate"] = toDate.Value.ToString(DATE_FORMAT); - } - return parameters; - } - - /// - /// Get the Open Events for messages, optionally filtering by various - /// attributes of the Open Events and Messages. - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public async Task GetOpenEventsForMessagesAsync( - int offset = 0, int count = 100, string recipient = null, string tag = null, - string clientName = null, string clientCompany = null, string clientFamily = null, - string operatingSystemName = null, string operatingSystemFamily = null, string operatingSystemCompany = null, - string platform = null, string country = null, string region = null, string city = null) - { - var parameters = new Dictionary(); - parameters["offset"] = offset; - parameters["count"] = count; - parameters["recipient"] = recipient; - parameters["tag"] = tag; - parameters["client_name"] = clientName; - parameters["client_company"] = clientCompany; - parameters["client_family"] = clientFamily; - parameters["os_name"] = operatingSystemName; - parameters["os_family"] = operatingSystemFamily; - parameters["os_company"] = operatingSystemCompany; - parameters["platform"] = platform; - parameters["country"] = country; - parameters["region"] = region; - parameters["city"] = city; - - return await this - .ProcessNoBodyRequestAsync("/messages/outbound/opens", parameters); - } - - - /// - /// Get the Click Events for messages, optionally filtering by various - /// attributes of the Click Events and Messages. - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public async Task GetClickEventsForMessagesAsync( - int offset = 0, int count = 100, string recipient = null, string tag = null, - string clientName = null, string clientCompany = null, string clientFamily = null, - string operatingSystemName = null, string operatingSystemFamily = null, string operatingSystemCompany = null, - string platform = null, string country = null, string region = null, string city = null) - { - var parameters = new Dictionary(); - parameters["offset"] = offset; - parameters["count"] = count; - parameters["recipient"] = recipient; - parameters["tag"] = tag; - parameters["client_name"] = clientName; - parameters["client_company"] = clientCompany; - parameters["client_family"] = clientFamily; - parameters["os_name"] = operatingSystemName; - parameters["os_family"] = operatingSystemFamily; - parameters["os_company"] = operatingSystemCompany; - parameters["platform"] = platform; - parameters["country"] = country; - parameters["region"] = region; - parameters["city"] = city; - - return await this - .ProcessNoBodyRequestAsync("/messages/outbound/clicks", parameters); - } - - - /// - /// Get the Open events for a specific message. - /// - /// - /// - /// - /// - public async Task GetOpenEventsForMessageAsync( - string messageId, int offset = 0, int count = 100) - { - - var parameters = new Dictionary(); - parameters["offset"] = offset; - parameters["count"] = count; - - return await this.ProcessNoBodyRequestAsync - (String.Format("/messages/outbound/opens/{0}", messageId), parameters); - } - - /// - /// Get the Clicks events for a specific message. - /// - /// - /// - /// - /// - public async Task GetClickEventsForMessageAsync( - string messageId, int offset = 0, int count = 100) - { - - var parameters = new Dictionary(); - parameters["offset"] = offset; - parameters["count"] = count; - - return await this.ProcessNoBodyRequestAsync - (String.Format("/messages/outbound/clicks/{0}", messageId), parameters); - } - - /// - /// Get an overview of outbound statistics, optionally limiting by tag or time window. - /// - /// - /// - /// - /// - public async Task GetOutboundOverviewStatsAsync( - string tag = null, DateTime? fromDate = null, DateTime? toDate = null) - { - var parameters = ConstructSentStatsFilter(tag, fromDate, toDate); - return await this.ProcessNoBodyRequestAsync - ("/stats/outbound", parameters); - } - - /// - /// Retrieve sent counts for outbound emails, optionally including a time or tag filter. - /// - /// - /// - /// - /// - public async Task - GetOutboundSentCountsAsync(string tag = null, DateTime? fromDate = null, DateTime? toDate = null) - { - var parameters = ConstructSentStatsFilter(tag, fromDate, toDate); - return await this.ProcessNoBodyRequestAsync - ("/stats/outbound/sends", parameters); - } - - /// - /// Retrieve bounce counts for outbound emails, optionally including a time or tag filter. - /// - /// - /// - /// - /// - public async Task - GetOutboundBounceCountsAsync(string tag = null, DateTime? fromDate = null, DateTime? toDate = null) - { - var parameters = ConstructSentStatsFilter(tag, fromDate, toDate); - return await this.ProcessNoBodyRequestAsync - ("/stats/outbound/bounces", parameters); - } - - /// - /// Retrieve SPAM complaint counts for outbound emails, optionally including a time or tag filter. - /// - /// - /// - /// - /// - public async Task GetOutboundSpamComplaintCountsAsync(string tag = null, DateTime? fromDate = null, DateTime? toDate = null) - { - var parameters = ConstructSentStatsFilter(tag, fromDate, toDate); - return await this.ProcessNoBodyRequestAsync - ("/stats/outbound/spam", parameters); - } - - /// - /// Retrieve open tracking for outbound emails, optionally including a time or tag filter. - /// - /// - /// - /// - /// - public async Task GetOutboundTrackingCountsAsync(string tag = null, DateTime? fromDate = null, DateTime? toDate = null) - { - var parameters = ConstructSentStatsFilter(tag, fromDate, toDate); - return await this.ProcessNoBodyRequestAsync - ("/stats/outbound/tracked", parameters); - } - - /// - /// Retrieve open counts for outbound emails, optionally including a time or tag filter. - /// - /// - /// - /// - /// - public async Task GetOutboundOpenCountsAsync(string tag = null, DateTime? fromDate = null, DateTime? toDate = null) - { - var parameters = ConstructSentStatsFilter(tag, fromDate, toDate); - return await this.ProcessNoBodyRequestAsync - ("/stats/outbound/opens", parameters); - } - - /// - /// Retrieve platform statistics for outbound emails, optionally including a time or tag filter. - /// - /// - /// - /// - /// - public async Task GetOutboundPlatformCountsAsync(string tag = null, DateTime? fromDate = null, DateTime? toDate = null) - { - var parameters = ConstructSentStatsFilter(tag, fromDate, toDate); - return await this.ProcessNoBodyRequestAsync - ("/stats/outbound/opens/platforms", parameters); - } - - /// - /// Retrieve client usage statistics for outbound emails, optionally including a time or tag filter. - /// - /// - /// - /// - /// - public async Task GetOutboundClientUsageCountsAsync(string tag = null, DateTime? fromDate = null, DateTime? toDate = null) - { - var parameters = ConstructSentStatsFilter(tag, fromDate, toDate); - var result = await this.ProcessNoBodyRequestAsync>("/stats/outbound/opens/emailclients", parameters); - - var retval = new PostmarkOutboundClientStats(); - var clientCounts = new Dictionary(); - foreach (var a in result) - { - if (a.Key != "Days") - { - clientCounts[a.Key] = a.Value.GetInt32(); - } - } - retval.ClientCounts = clientCounts; - - var dayList = new List(); - foreach (var obj in result["Days"].EnumerateArray()) - { - var newCount = new PostmarkOutboundClientStats.DatedClientCount(); - foreach (var i in obj.EnumerateObject()) - { - if (i.Name == "Date") - { - newCount.Date = DateTime.Parse(i.Value.GetString()); - } - else - { - newCount.ClientCounts[i.Name] = i.Value.GetInt32(); - } - } - dayList.Add(newCount); - } - - retval.Days = dayList; - - return retval; - } - - /// - /// Retrieve read time statistics for outbound emails, optionally including a time or tag filter. - /// - /// - /// - /// - /// - public async Task GetOutboundReadtimeStatsAsync(string tag = null, DateTime? fromDate = null, DateTime? toDate = null) - { - var parameters = ConstructSentStatsFilter(tag, fromDate, toDate); - var result = await this.ProcessNoBodyRequestAsync>("/stats/outbound/opens/readtimes", parameters); - - var retval = new PostmarkOutboundReadStats(); - var clientCounts = new Dictionary(); - foreach (var a in result) - { - if (a.Key != "Days") - { - clientCounts[a.Key] = a.Value.GetInt32(); - } - } - retval.ReadCounts = clientCounts; - - var dayList = new List(); - foreach (var obj in result["Days"].EnumerateArray()) - { - var newCount = new PostmarkOutboundReadStats.DatedReadCount(); - foreach (var i in obj.EnumerateObject()) - { - if (i.Name == "Date") - { - newCount.Date = DateTime.Parse(i.Value.ToString()); - } - else - { - newCount.ReadCounts[i.Name] = i.Value.GetInt32(); - } - } - dayList.Add(newCount); - } - - retval.Days = dayList; - - return retval; - } - #endregion - - #region Inbound Triggers - - /// - /// Define a new Inbound Rule Trigger - /// - /// - /// - public async Task CreateInboundRuleTriggerAsync(string rule) - { - var parameters = new Dictionary(); - parameters["Rule"] = rule; - - return await this.ProcessRequestAsync, PostmarkInboundRuleTriggerInfo> - ("/triggers/inboundrules", HttpMethod.Post, parameters); - } - - /// - /// Delete an Inbound Rule Trigger - /// - /// - /// - public async Task DeleteInboundRuleTrigger(int triggerId) - { - return await this - .ProcessNoBodyRequestAsync("/triggers/inboundrules/" + triggerId, - verb: HttpMethod.Delete); - } - - /// - /// List Inbound Rule Triggers. - /// - /// - /// - /// - public async Task GetAllInboundRuleTriggers(int offset = 0, int count = 100) - { - var parameters = new Dictionary(); - parameters["offset"] = offset; - parameters["count"] = count; - - return await this.ProcessNoBodyRequestAsync("/triggers/inboundrules", parameters); - } - - #endregion - - #region Templates - - /// - /// Get basic info associated with the specified ID. - /// - /// The ID of the template you wish to retrive. - /// - public async Task GetTemplateAsync(long templateId) - { - return await ProcessNoBodyRequestAsync("/templates/" + templateId, null, HttpMethod.Get); - } - - /// - /// Get basic info associated with the specified Alias. - /// - /// The alias of the template you wish to retrive. - /// - public async Task GetTemplateAsync(string alias) - { - return await ProcessNoBodyRequestAsync("/templates/" + alias, null, HttpMethod.Get); - } - - /// - /// Get a listing of templates. - /// - /// The number of templates to return. Defaults to 0. - /// The number of templates to "skip" before returning results. Defaults to 100. - /// Filter the resulting templates by their TemplateType. Defaults to: All - /// Filter results by layout template alias. - /// - public async Task GetTemplatesAsync(int offset = 0, int count = 100, TemplateTypeFilter templateType = TemplateTypeFilter.All, - string layoutTemplate = null) - { - var query = new Dictionary(); - query["Count"] = count; - query["Offset"] = offset; - query["TemplateType"] = Enum.GetName(typeof(TemplateTypeFilter), templateType); - query["LayoutTemplate"] = layoutTemplate; - - return await ProcessNoBodyRequestAsync("/templates/", query, HttpMethod.Get); - } - - /// - /// Store a new template associated with this server. - /// - /// A display name for this template. - /// The subject to be used when sending with this template. - /// The HTMLBody to be used when sending with this template. Optional if TextBody is specified. - /// The TextBody to be used when sending with this template. Optional if HtmlBody is specified. - /// A friendly name to use for this template to access it, or to send with it. - /// The type of the template to create. - /// The alias of the Layout template that you want to use as layout for this Standard template. - /// - public async Task CreateTemplateAsync(string name, string subject, string htmlBody = null, string textBody = null, string alias = null, - TemplateType templateType = TemplateType.Standard, string layoutTemplate = null) - { - var body = new Dictionary(); - body["Name"] = name; - body["HTMLBody"] = htmlBody; - body["TextBody"] = textBody; - body["Subject"] = subject; - body["Alias"] = alias; - body["TemplateType"] = Enum.GetName(typeof(TemplateType), templateType); - body["LayoutTemplate"] = layoutTemplate; - - return await ProcessRequestAsync, BasicTemplateInformation>("/templates/", HttpMethod.Post, body); - } - - public async Task EditTemplateAsync(string alias, string name = null, string subject = null, string htmlBody = null, string textBody = null, - string layoutTemplate = null) - { - var body = new Dictionary(); - body["Name"] = name; - body["HTMLBody"] = htmlBody; - body["TextBody"] = textBody; - body["Subject"] = subject; - body["LayoutTemplate"] = layoutTemplate; - - return await ProcessRequestAsync, BasicTemplateInformation>("/templates/" + alias, HttpMethod.Put, body); - } - - public async Task EditTemplateAsync(long templateId, string name = null, string subject = null, string htmlBody = null, string textBody = null, string alias = null, - string layoutTemplate = null) - { - var body = new Dictionary(); - body["Name"] = name; - body["HTMLBody"] = htmlBody; - body["TextBody"] = textBody; - body["Subject"] = subject; - body["Alias"] = alias; - body["LayoutTemplate"] = layoutTemplate; - - return await ProcessRequestAsync, BasicTemplateInformation>("/templates/" + templateId, HttpMethod.Put, body); - } - - /// - /// Delete a template from the server. - /// - /// The ID of the template you wish to delete from the server. - /// - public async Task DeleteTemplateAsync(long templateId) - { - return await ProcessNoBodyRequestAsync("/templates/" + templateId, null, HttpMethod.Delete); - } - - /// - /// Delete a template from the server. - /// - /// The Alias of the template you wish to delete from the server. - /// - public async Task DeleteTemplateAsync(string templateAlias) - { - return await ProcessNoBodyRequestAsync("/templates/" + templateAlias, null, HttpMethod.Delete); - } - - public async Task SendMessageAsync(TemplatedPostmarkMessage emailToSend) - { - return await SendEmailWithTemplateAsync(emailToSend); - } - - public async Task SendEmailWithTemplateAsync(TemplatedPostmarkMessage emailToSend) - { - return await ProcessRequestAsync("/email/withTemplate", HttpMethod.Post, emailToSend); - } - - /// - /// Sends a batch of up to 500 templated messages through the Postmark API. - /// All email addresses must be valid, and the sender must be a valid sender signature according to Postmark. - /// Either the TemplateId or the TemplateAlias must be provided for each TemplatedPostmarkMessage. - /// - /// A prepared batch of templated messages. - /// The processed messages (Complete with system assigned message IDs) - public async Task> SendMessagesAsync(params TemplatedPostmarkMessage[] messages) - { - return await SendEmailsWithTemplateAsync(messages); - } - - /// - /// Sends a batch of up to 500 templated messages through the Postmark API. - /// All email addresses must be valid, and the sender must be a valid sender signature according to Postmark. - /// Either the TemplateId or the TemplateAlias must be provided for each TemplatedPostmarkMessage. - /// - /// A prepared batch of templated messages. - /// The processed messages (Complete with system assigned message IDs) - public async Task> SendEmailsWithTemplateAsync(params TemplatedPostmarkMessage[] messages) - { - var body = new Dictionary { ["Messages"] = messages.ToList() }; - - return await ProcessRequestAsync, PostmarkResponse[]>("/email/batchWithTemplates", HttpMethod.Post, body); - } - - public async Task SendEmailWithTemplateAsync(string templateAlias, T templateModel, - string to, string from, - bool? inlineCss = null, string cc = null, - string bcc = null, string replyTo = null, - bool? trackOpens = null, - IDictionary headers = null, - IDictionary metadata = null, - string messageStream = null, - params PostmarkMessageAttachment[] attachments) - { - return await InternalSendEmailWithTemplateAsync(templateAlias, templateModel, to, from, inlineCss, cc, - bcc, replyTo, trackOpens, headers, metadata, messageStream, attachments); - } - - public async Task SendEmailWithTemplateAsync(long templateId, T templateModel, - string to, string from, - bool? inlineCss = null, string cc = null, - string bcc = null, string replyTo = null, - bool? trackOpens = null, - IDictionary headers = null, - IDictionary metadata = null, - string messageStream = null, - params PostmarkMessageAttachment[] attachments) - { - return await InternalSendEmailWithTemplateAsync(templateId, templateModel, to, from, inlineCss, cc, - bcc, replyTo, trackOpens, headers, metadata, messageStream, attachments); - } - - private async Task InternalSendEmailWithTemplateAsync(object templateReference, T templateModel, - string to, string from, - bool? inlineCss = null, string cc = null, - string bcc = null, string replyTo = null, - bool? trackOpens = null, - IDictionary headers = null, - IDictionary metadata = null, - string messageStream = null, - params PostmarkMessageAttachment[] attachments) - { - - var email = new TemplatedPostmarkMessage(); - if (templateReference is long) - { - email.TemplateId = (long)templateReference; - } - else - { - email.TemplateAlias = (string)templateReference; - } - email.TemplateModel = templateModel; - email.MessageStream = messageStream; - email.To = to; - email.From = from; - if (inlineCss.HasValue) - { - email.InlineCss = inlineCss.Value; - } - email.Cc = cc; - email.Bcc = bcc; - if (trackOpens.HasValue) - { - email.TrackOpens = trackOpens.Value; - } - email.ReplyTo = replyTo; - if (headers != null) - { - email.Headers = new HeaderCollection(headers); - } - email.Metadata = metadata; - if (attachments != null) - { - email.Attachments = attachments; - } - return await SendEmailWithTemplateAsync(email); - } - - /// - /// Validate a template. - /// - /// - /// The subject content to validate. - /// The HTML body content to validate. - /// The plain text body content to validate. - /// The template model to be used when rendering test content. - /// Controls whether style blocks will be inlined as style attributes on matching html elements in HtmlBody. - /// Validate templates based on template type. - /// An optional string to specify which layout template alias to use to validate a standard template. - /// - public async Task ValidateTemplateAsync(string subject = null, string htmlBody = null, - string textBody = null, T testRenderModel = default(T), bool inlineCssForHtmlTestRender = true, - TemplateType templateType = TemplateType.Standard, string layoutTemplate = null) - { - var body = new Dictionary(); - body["TestRenderModel"] = testRenderModel; - body["Subject"] = subject; - body["HtmlBody"] = htmlBody; - body["TextBody"] = textBody; - body["InlineCssForHtmlTestRender"] = inlineCssForHtmlTestRender; - body["TemplateType"] = Enum.GetName(typeof(TemplateType), templateType); - body["LayoutTemplate"] = layoutTemplate; - - return await ProcessRequestAsync, TemplateValidationResponse>("/templates/validate", HttpMethod.Post, body); - } - #endregion - - #region Webhooks - - /// - /// Gets the webhook configuration for the provided configuration id - /// - /// Configuration Id to search for - /// - public async Task GetWebhookConfigurationAsync(long configurationId) - { - return await ProcessNoBodyRequestAsync($"/webhooks/{configurationId}", null, HttpMethod.Get); - } - - /// - /// Gets a listing of webhook configurations for the provided server - /// - /// Optional message stream to search for. - /// If not provided, all configurations for the server will be returned. - /// - public async Task GetWebhookConfigurationsAsync(string messageStream = null) - { - var query = new Dictionary { ["MessageStream"] = messageStream }; - - return await ProcessNoBodyRequestAsync("/webhooks/", query, HttpMethod.Get); - } - - /// - /// Delete a webhook configuration from the server. - /// - /// Configuration id to search for - /// - public async Task DeleteWebhookConfigurationAsync(long configurationId) - { - return await ProcessNoBodyRequestAsync($"/webhooks/{configurationId}", null, HttpMethod.Delete); - } - - /// - /// Creates a new webhook configuration - /// - /// The webhook URL - /// Message stream this configuration should belong to. - /// If not provided, it will belong to the default transactional stream. - /// Optional Basic HTTP Authentication - /// Optional list of custom HTTP headers - /// Optional triggers for this webhook configuration - /// - public async Task CreateWebhookConfigurationAsync(string url, string messageStream = null, - HttpAuth httpAuth = null, IEnumerable httpHeaders = null, WebhookConfigurationTriggers triggers = null) - { - var body = new Dictionary(); - body["Url"] = url; - body["MessageStream"] = messageStream; - body["HttpAuth"] = httpAuth; - body["HttpHeaders"] = httpHeaders; - body["Triggers"] = triggers; - - return await ProcessRequestAsync, WebhookConfiguration>("/webhooks/", HttpMethod.Post, body); - } - - /// - /// Update a webhook configuration for the provided configuration id. - /// - /// Configuration id to search for - /// The webhook URL - /// Optional Basic HTTP Authentication - /// Optional list of custom HTTP headers - /// Optional triggers for this webhook configuration - /// - public async Task EditWebhookConfigurationAsync(long configurationId, string url, - HttpAuth httpAuth = null, IEnumerable httpHeaders = null, WebhookConfigurationTriggers triggers = null) - { - var body = new Dictionary(); - body["Url"] = url; - body["HttpAuth"] = httpAuth; - body["HttpHeaders"] = httpHeaders; - body["Triggers"] = triggers; - - return await ProcessRequestAsync, WebhookConfiguration>($"/webhooks/{configurationId}", - HttpMethod.Put, body); - } - #endregion - - #region Suppressions - - /// - /// List Suppressions for the provided query parameters. - /// - /// Set of query parameters used to filter Suppressions. - /// MessageStream used to search for Suppressions. Defaults to "outbound". - /// Listing of active Suppressions matching the provided query parameters. - public async Task ListSuppressions(PostmarkSuppressionQuery query, string messageStream = DefaultTransactionalStream) - { - var parameters = new Dictionary - { - ["SuppressionReason"] = query.SuppressionReason, - ["Origin"] = query.Origin, - ["todate"] = query.ToDate?.ToString("O"), - ["fromdate"] = query.FromDate?.ToString("O"), - ["emailAddress"] = query.EmailAddress, - }; - - var apiUrl = $"/message-streams/{messageStream}/suppressions/dump"; - - return await ProcessNoBodyRequestAsync(apiUrl, parameters); - } - - /// - /// Create Suppressions for the specified recipients. - /// - /// List of SuppressionChangeRequest objects that specify what recipients to suppress. - /// Message stream where the recipients should be suppressed. Defaults to "outbound". - /// Suppressions will be generated with a Customer Origin and will have a ManualSuppression reason. - /// The status of the request for each recipient. - public async Task CreateSuppressions(IEnumerable suppressionChanges, - string messageStream = DefaultTransactionalStream) - { - var body = new Dictionary { ["Suppressions"] = suppressionChanges.ToList() }; - - var apiUrl = $"/message-streams/{messageStream}/suppressions"; - - return await ProcessRequestAsync, PostmarkBulkSuppressionResult>(apiUrl, HttpMethod.Post, body); - } - - /// - /// Reactivate Suppressions for the specified recipients. - /// - /// List of SuppressionChangeRequest objects that specify what recipients to reactivate. - /// Message stream where the recipients should be reactivated. Defaults to "outbound". - /// Suppressions will only be reactivated if you have the required authority. - /// The status of the request for each recipient. - public async Task DeleteSuppressions(IEnumerable suppressionChanges, - string messageStream = DefaultTransactionalStream) - { - var body = new Dictionary { ["Suppressions"] = suppressionChanges.ToList() }; - - var apiUrl = $"/message-streams/{messageStream}/suppressions/delete"; - - return await ProcessRequestAsync, PostmarkBulkReactivationResult>(apiUrl, HttpMethod.Post, body); - } - - #endregion - - #region MessageStreams - - /// - /// Create a new message stream on your server. - /// - /// Identifier for your message stream, unique at server level. - /// Type of the message stream. E.g.: Transactional or Broadcasts. - /// Friendly name for your message stream. - /// Friendly description for your message stream. (optional) - /// The unsubscribe management option for the stream. (optional) - /// Currently, you cannot create multiple inbound streams. - public async Task CreateMessageStream(string id, MessageStreamType type, string name, string description = null, UnsubscribeHandlingType? unsubscriptionHandlingType = null) - { - var body = new Dictionary - { - ["ID"] = id, - ["Name"] = name, - ["Description"] = description, - ["MessageStreamType"] = type.ToString() - }; - - if (unsubscriptionHandlingType.HasValue) - { - body["SubscriptionManagementConfiguration"] = new Dictionary - { - ["UnsubscribeHandlingType"] = unsubscriptionHandlingType.ToString() - }; - } - - var apiUrl = "/message-streams/"; - - return await ProcessRequestAsync, PostmarkMessageStream>(apiUrl, HttpMethod.Post, body); - } - - /// - /// Edit the properties of a message stream. - /// - /// The identifier for the stream you are trying to update. - /// New friendly name to use. (optional) - /// New description to use. (optional) - /// New unsubscribe management option for the stream. (optional) - public async Task EditMessageStream(string id, string name = null, string description = null, UnsubscribeHandlingType? unsubscriptionHandlingType = null) - { - var body = new Dictionary - { - ["Name"] = name, - ["Description"] = description - }; - - if (unsubscriptionHandlingType.HasValue) - { - body["SubscriptionManagementConfiguration"] = new Dictionary - { - ["UnsubscribeHandlingType"] = unsubscriptionHandlingType.ToString() - }; - } - - var apiUrl = $"/message-streams/{id}"; - - return await ProcessRequestAsync, PostmarkMessageStream>(apiUrl, new HttpMethod("PATCH"), body); - } - - /// - /// Retrieve details about a message stream. - /// - /// Identifier of the stream to retrieve details for. - public async Task GetMessageStream(string id) - { - return await ProcessNoBodyRequestAsync($"/message-streams/{id}"); - } - - /// - /// Retrieve all message streams on the server. - /// - /// Filter by stream type. E.g.: Transactional. Defaults to: All. - /// Include archived streams in the result. Defaults to: false. - public async Task ListMessageStreams(MessageStreamTypeFilter messageStreamType = MessageStreamTypeFilter.All, - bool includeArchivedStreams = false) - { - var parameters = new Dictionary - { - ["MessageStreamType"] = messageStreamType.ToString(), - ["IncludeArchivedStreams"] = includeArchivedStreams - }; - - return await ProcessNoBodyRequestAsync("/message-streams/", parameters); - } - - /// - /// Archive a message stream. This will disable sending/receiving messages via that stream. - /// The stream will also stop being shown in the Postmark UI. - /// Once a stream has been archived, it will be deleted (alongside associated data) at the ExpectedPurgeDate in the response. - /// - /// Identifier of the stream to archive. - public async Task ArchiveMessageStream(string id) - { - var apiUrl = $"/message-streams/{id}/archive"; - - return await ProcessNoBodyRequestAsync(apiUrl, verb: HttpMethod.Post); - } - - /// - /// UnArchive a message stream. This will resume sending/receiving via that stream. - /// The stream will also re-appear in the Postmark UI. - /// A stream can be unarchived only before the stream ExpectedPurgeDate. - /// - /// Identifier of the stream to unArchive. - public async Task UnArchiveMessageStream(string id) - { - var apiUrl = $"/message-streams/{id}/unarchive"; - - return await ProcessNoBodyRequestAsync(apiUrl, verb: HttpMethod.Post); - } - - #endregion - } -} + /// + /// Instantiate the client. + /// + /// + /// + /// The base uri to use when connecting to Postmark. You should rarely need to modify this, except + /// if you want to disable TLS (not recommended), or you are using a proxy of some sort to connect to the API. + /// + /// + /// Used for requests that require server level privileges. This token can be found on the + /// Credentials tab under your Postmark server. + /// + public PostmarkClient(HttpClient client) + : base(client) + { + } + + #region Email Sending + + /// + /// Sends a message through the Postmark API. + /// All email addresses must be valid, and the sender must be + /// a valid sender signature according to Postmark. To obtain a valid + /// sender signature, log in to Postmark and navigate to: + /// http://postmarkapp.com/signatures. + /// + /// A prepared message. + /// + public async Task SendMessageAsync(PostmarkMessage message) + { + return await ProcessRequestAsync("/email", HttpMethod.Post, message); + } + + /// + /// Sends a batch of up to 500 messages through the Postmark API. + /// All email addresses must be valid, and the sender must be + /// a valid sender signature according to Postmark. To obtain a valid + /// sender signature, log in to Postmark and navigate to: + /// http://postmarkapp.com/signatures. + /// + /// A prepared batch of messages. + /// The processed messages (Complete with system assigned message IDs) + public async Task> SendMessagesAsync(params PostmarkMessage[] messages) + { + return await ProcessRequestAsync("/email/batch", HttpMethod.Post, messages); + } + + #endregion + + #region Bounces + + /// + /// Retrieves the bounce-related results for the + /// associated mail server. + /// + /// + public async Task GetDeliveryStatsAsync() + { + return await ProcessNoBodyRequestAsync("/deliverystats"); + } + + /// + /// Retrieves a collection of instances along + /// with a sum total of bounces recorded by the server, based on filter parameters. + /// + /// The type of bounces to filter on. + /// Whether to return only inactive or active bounces; use null to return all bounces. + /// Filters based on whether the filter value is contained in the bounce source's email. + /// Filters on the bounce tag. + /// Filter by MessageID. + /// The page offset for the returned results; defaults to 0. + /// The number of results to return by the page offset; defaults to 100. + /// Filter messages starting from the date specified (inclusive). e.g. 2014-02-01 + /// Filter messages up to the date specified (inclusive). e.g. 2014-02-01 + /// + /// + public async Task GetBouncesAsync(int offset = 0, int count = 100, PostmarkBounceType? type = null, + bool? inactive = null, string emailFilter = null, string tag = null, string messageID = null, string fromDate = null, string toDate = null) + { + var parameters = new Dictionary(); + parameters["type"] = type; + parameters["inactive"] = inactive; + parameters["emailFilter"] = emailFilter; + parameters["tag"] = tag; + parameters["messageID"] = messageID; + parameters["offset"] = offset; + parameters["count"] = count; + parameters["fromDate"] = fromDate; + parameters["toDate"] = toDate; + + return await ProcessNoBodyRequestAsync("/bounces", parameters); + } + + /// + /// Retrieves a single based on a specified ID. + /// + /// The bounce ID of the bounce to retrieve. + /// + /// + public async Task GetBounceAsync(long bounceId) + { + return await ProcessNoBodyRequestAsync("/bounces/" + bounceId); + } + + /// + /// Returns the raw source of the bounce we accepted. + /// If Postmark does not have a dump for that bounce, it will return an empty string. + /// + /// The bounce ID of the bounce dump to retrieve. + /// + /// + public async Task GetBounceDumpAsync(long bounceId) + { + return await ProcessNoBodyRequestAsync("/bounces/" + bounceId + "/dump"); + } + + /// + /// Activates a deactivated bounce. + /// + /// The bounce ID of the bounce to Activate + /// + /// + public async Task ActivateBounceAsync(long bounceId) + { + return await ProcessNoBodyRequestAsync("/bounces/" + bounceId + "/activate", verb: HttpMethod.Put); + } + + #endregion + + #region Outbound Message Retrieval + + /// + /// Return a listing of Outbound sent messages using the filters supported by the API. + /// + /// Filter by the recipient(s) of the message. + /// Filter by the email address the message is sent from. + /// Filter by a tag used for the message (messages sent directly through the API only) + /// Filter by message subject. + /// Number of messages to return per call. (required) + /// Number of messages to offset/page per call. (required) + /// The status of the outbound message. + /// Get messages on or after YYYY-MM-DD. + /// Get messages on or before YYYY-MM-DD. + /// PostmarkOutboundMessageList + public async Task GetOutboundMessagesAsync(int offset = 0, int count = 100, + string recipient = null, string fromemail = null, string tag = null, string subject = null, + OutboundMessageStatus status = OutboundMessageStatus.Sent, string toDate = null, string fromDate = null, IDictionary metadata = null, string messagestream = null) + { + var parameters = new Dictionary(); + parameters["count"] = count; + parameters["offset"] = offset; + parameters["recipient"] = recipient; + parameters["fromemail"] = fromemail; + parameters["tag"] = tag; + parameters["subject"] = subject; + parameters["todate"] = toDate; + parameters["fromdate"] = fromDate; + parameters["status"] = status.ToString().ToLower(); + parameters["messagestream"] = messagestream; + + if (metadata != null) + foreach (var a in metadata) + parameters[$"metadata_{a.Key}"] = a.Value; + + return await ProcessNoBodyRequestAsync("/messages/outbound", parameters); + } + + /// + /// Get the full details of a sent message including all fields, raw body, attachment names, etc + /// + /// + /// The MessageID of a message which can be optained either from the initial API send call or a + /// GetOutboundMessages call. + /// + /// OutboundMessageDetail + public async Task GetOutboundMessageDetailsAsync(string messageID) + { + return await ProcessNoBodyRequestAsync("/messages/outbound/" + messageID + "/details"); + } + + /// + /// Get the original raw message dump of on outbound message including all SMTP headers and data. + /// + /// + /// The MessageID of a message which can be optained either from the initial API send call or a + /// GetOutboundMessages call. + /// + /// MessageDump + public async Task GetOutboundMessageDumpAsync(string messageID) + { + return await ProcessNoBodyRequestAsync("/messages/outbound/" + messageID + "/dump"); + } + + #endregion + + #region Inbound Message Retrieval + + /// + /// Return a listing of Inbound sent messages using the filters supported by the API. + /// + /// Filter by the recipient(s) of the message. + /// Filter by the email address the message is sent from. + /// Filter by message subject. + /// Filter by mailbox hash that was parsed from the inbound message. + /// Number of messages to return per call. (required) + /// Number of messages to offset/page per call. (required) + /// The status of the inbound message. + /// Get messages on or after YYYY-MM-DD. + /// Get messages on or before YYYY-MM-DD. + /// PostmarkInboundMessageList + public async Task GetInboundMessagesAsync(int offset = 0, int count = 100, + string recipient = null, string fromemail = null, string subject = null, + string mailboxhash = null, InboundMessageStatus? status = InboundMessageStatus.Processed, string toDate = null, string fromDate = null) + { + var parameters = new Dictionary(); + parameters["count"] = count; + parameters["offset"] = offset; + parameters["recipient"] = recipient; + parameters["fromemail"] = fromemail; + parameters["subject"] = subject; + parameters["mailboxhash"] = mailboxhash; + parameters["todate"] = toDate; + parameters["fromdate"] = fromDate; + parameters["status"] = status.ToString().ToLower(); + + return await ProcessNoBodyRequestAsync("/messages/inbound", parameters); + } + + /// + /// Get the full details of a processed inbound message including all fields, attachment names, etc. + /// + /// + /// The MessageID of a message which can be optained either from the initial API send call or a + /// GetInboundMessages call. + /// + /// InboundMessageDetail + public async Task GetInboundMessageDetailsAsync(string messageID) + { + return await ProcessNoBodyRequestAsync("/messages/inbound/" + messageID + "/details"); + } + + /// + /// Bypass rules for a blocked inbound message. + /// + /// + /// + public async Task BypassBlockedInboundMessage(string messageid) + { + return await ProcessNoBodyRequestAsync(string.Format("/messages/inbound/{0}/bypass", messageid), verb: HttpMethod.Put); + } + + /// + /// Request that Postmark retries POSTing to your Inbound Hook for the specified inbound message. + /// + /// + /// + public async Task RetryInboundHookForMessage(string messageId) + { + return await ProcessNoBodyRequestAsync(string.Format("/messages/inbound/{0}/retry", messageId), verb: HttpMethod.Put); + } + + #endregion + + #region Servers + + /// + /// Gets the server associated with this client based on + /// the ServerToken supplied when the client was constructed. + /// + /// + public async Task GetServerAsync() + { + return await ProcessNoBodyRequestAsync("/server"); + } + + /// + /// Updates the server associated with this client. Only parameters that are passed into this method are modified. + /// Any parameters that are left null will use the current value for the server. + /// + /// + public async Task EditServerAsync( + string name = null, string color = null, + bool? rawEmailEnabled = null, bool? smtpApiActivated = null, string inboundHookUrl = null, + string bounceHookUrl = null, string openHookUrl = null, bool? postFirstOpenOnly = null, + bool? trackOpens = null, string inboundDomain = null, int? inboundSpamThreshold = null, + LinkTrackingOptions? trackLinks = null, + string clickHookUrl = null, string deliveryHookUrl = null) + { + var body = new Dictionary(); + body["Name"] = name; + body["Color"] = color; + body["RawEmailEnabled"] = rawEmailEnabled; + body["SmtpApiActivated"] = smtpApiActivated; + body["InboundHookUrl"] = inboundHookUrl; + body["BounceHookUrl"] = bounceHookUrl; + body["OpenHookUrl"] = openHookUrl; + body["PostFirstOpenOnly"] = postFirstOpenOnly; + body["TrackOpens"] = trackOpens; + body["InboundDomain"] = inboundDomain; + body["InboundSpamThreshold"] = inboundSpamThreshold; + body["TrackLinks"] = trackLinks; + body["ClickHookUrl"] = clickHookUrl; + body["DeliveryHookUrl"] = deliveryHookUrl; + + body = body.Where(kv => kv.Value != null).ToDictionary(k => k.Key, v => v.Value); + + return await ProcessRequestAsync, PostmarkServer>("/server", HttpMethod.Put, body); + } + + #endregion + + #region Stats + + /// + /// Create parameters for the stats filtering from normal params. + /// + /// + /// + /// + /// + private IDictionary + ConstructSentStatsFilter(string tag, DateTime? fromDate, DateTime? toDate) + { + var parameters = new Dictionary(); + if (!string.IsNullOrWhiteSpace(tag)) parameters["tag"] = tag; + if (fromDate.HasValue) parameters["fromdate"] = fromDate.Value.ToString(DateFormat); + if (toDate.HasValue) parameters["todate"] = toDate.Value.ToString(DateFormat); + return parameters; + } + + /// + /// Get the Open Events for messages, optionally filtering by various + /// attributes of the Open Events and Messages. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public async Task GetOpenEventsForMessagesAsync( + int offset = 0, int count = 100, string recipient = null, string tag = null, + string clientName = null, string clientCompany = null, string clientFamily = null, + string operatingSystemName = null, string operatingSystemFamily = null, string operatingSystemCompany = null, + string platform = null, string country = null, string region = null, string city = null) + { + var parameters = new Dictionary(); + parameters["offset"] = offset; + parameters["count"] = count; + parameters["recipient"] = recipient; + parameters["tag"] = tag; + parameters["client_name"] = clientName; + parameters["client_company"] = clientCompany; + parameters["client_family"] = clientFamily; + parameters["os_name"] = operatingSystemName; + parameters["os_family"] = operatingSystemFamily; + parameters["os_company"] = operatingSystemCompany; + parameters["platform"] = platform; + parameters["country"] = country; + parameters["region"] = region; + parameters["city"] = city; + + return await ProcessNoBodyRequestAsync("/messages/outbound/opens", parameters); + } + + + /// + /// Get the Click Events for messages, optionally filtering by various + /// attributes of the Click Events and Messages. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public async Task GetClickEventsForMessagesAsync( + int offset = 0, int count = 100, string recipient = null, string tag = null, + string clientName = null, string clientCompany = null, string clientFamily = null, + string operatingSystemName = null, string operatingSystemFamily = null, string operatingSystemCompany = null, + string platform = null, string country = null, string region = null, string city = null) + { + var parameters = new Dictionary(); + parameters["offset"] = offset; + parameters["count"] = count; + parameters["recipient"] = recipient; + parameters["tag"] = tag; + parameters["client_name"] = clientName; + parameters["client_company"] = clientCompany; + parameters["client_family"] = clientFamily; + parameters["os_name"] = operatingSystemName; + parameters["os_family"] = operatingSystemFamily; + parameters["os_company"] = operatingSystemCompany; + parameters["platform"] = platform; + parameters["country"] = country; + parameters["region"] = region; + parameters["city"] = city; + + return await ProcessNoBodyRequestAsync("/messages/outbound/clicks", parameters); + } + + + /// + /// Get the Open events for a specific message. + /// + /// + /// + /// + /// + public async Task GetOpenEventsForMessageAsync( + string messageId, int offset = 0, int count = 100) + { + var parameters = new Dictionary(); + parameters["offset"] = offset; + parameters["count"] = count; + + return await ProcessNoBodyRequestAsync + (string.Format("/messages/outbound/opens/{0}", messageId), parameters); + } + + /// + /// Get the Clicks events for a specific message. + /// + /// + /// + /// + /// + public async Task GetClickEventsForMessageAsync( + string messageId, int offset = 0, int count = 100) + { + var parameters = new Dictionary(); + parameters["offset"] = offset; + parameters["count"] = count; + + return await ProcessNoBodyRequestAsync + (string.Format("/messages/outbound/clicks/{0}", messageId), parameters); + } + + /// + /// Get an overview of outbound statistics, optionally limiting by tag or time window. + /// + /// + /// + /// + /// + public async Task GetOutboundOverviewStatsAsync( + string tag = null, DateTime? fromDate = null, DateTime? toDate = null) + { + var parameters = ConstructSentStatsFilter(tag, fromDate, toDate); + return await ProcessNoBodyRequestAsync + ("/stats/outbound", parameters); + } + + /// + /// Retrieve sent counts for outbound emails, optionally including a time or tag filter. + /// + /// + /// + /// + /// + public async Task + GetOutboundSentCountsAsync(string tag = null, DateTime? fromDate = null, DateTime? toDate = null) + { + var parameters = ConstructSentStatsFilter(tag, fromDate, toDate); + return await ProcessNoBodyRequestAsync("/stats/outbound/sends", parameters); + } + + /// + /// Retrieve bounce counts for outbound emails, optionally including a time or tag filter. + /// + /// + /// + /// + /// + public async Task + GetOutboundBounceCountsAsync(string tag = null, DateTime? fromDate = null, DateTime? toDate = null) + { + var parameters = ConstructSentStatsFilter(tag, fromDate, toDate); + return await ProcessNoBodyRequestAsync("/stats/outbound/bounces", parameters); + } + + /// + /// Retrieve SPAM complaint counts for outbound emails, optionally including a time or tag filter. + /// + /// + /// + /// + /// + public async Task GetOutboundSpamComplaintCountsAsync(string tag = null, DateTime? fromDate = null, DateTime? toDate = null) + { + var parameters = ConstructSentStatsFilter(tag, fromDate, toDate); + return await ProcessNoBodyRequestAsync("/stats/outbound/spam", parameters); + } + + /// + /// Retrieve open tracking for outbound emails, optionally including a time or tag filter. + /// + /// + /// + /// + /// + public async Task GetOutboundTrackingCountsAsync(string tag = null, DateTime? fromDate = null, DateTime? toDate = null) + { + var parameters = ConstructSentStatsFilter(tag, fromDate, toDate); + return await ProcessNoBodyRequestAsync("/stats/outbound/tracked", parameters); + } + + /// + /// Retrieve open counts for outbound emails, optionally including a time or tag filter. + /// + /// + /// + /// + /// + public async Task GetOutboundOpenCountsAsync(string tag = null, DateTime? fromDate = null, DateTime? toDate = null) + { + var parameters = ConstructSentStatsFilter(tag, fromDate, toDate); + return await ProcessNoBodyRequestAsync("/stats/outbound/opens", parameters); + } + + /// + /// Retrieve platform statistics for outbound emails, optionally including a time or tag filter. + /// + /// + /// + /// + /// + public async Task GetOutboundPlatformCountsAsync(string tag = null, DateTime? fromDate = null, DateTime? toDate = null) + { + var parameters = ConstructSentStatsFilter(tag, fromDate, toDate); + return await ProcessNoBodyRequestAsync("/stats/outbound/opens/platforms", parameters); + } + + /// + /// Retrieve client usage statistics for outbound emails, optionally including a time or tag filter. + /// + /// + /// + /// + /// + public async Task GetOutboundClientUsageCountsAsync(string tag = null, DateTime? fromDate = null, DateTime? toDate = null) + { + var parameters = ConstructSentStatsFilter(tag, fromDate, toDate); + var result = await ProcessNoBodyRequestAsync>("/stats/outbound/opens/emailclients", parameters); + + var retval = new PostmarkOutboundClientStats(); + var clientCounts = new Dictionary(); + foreach (var a in result) + if (a.Key != "Days") + clientCounts[a.Key] = a.Value.GetInt32(); + + retval.ClientCounts = clientCounts; + + var dayList = new List(); + foreach (var obj in result["Days"].EnumerateArray()) + { + var newCount = new PostmarkOutboundClientStats.DatedClientCount(); + foreach (var i in obj.EnumerateObject()) + if (i.Name == "Date") + newCount.Date = DateTime.Parse(i.Value.GetString()); + else + newCount.ClientCounts[i.Name] = i.Value.GetInt32(); + + dayList.Add(newCount); + } + + retval.Days = dayList; + + return retval; + } + + /// + /// Retrieve read time statistics for outbound emails, optionally including a time or tag filter. + /// + /// + /// + /// + /// + public async Task GetOutboundReadtimeStatsAsync(string tag = null, DateTime? fromDate = null, DateTime? toDate = null) + { + var parameters = ConstructSentStatsFilter(tag, fromDate, toDate); + var result = await ProcessNoBodyRequestAsync>("/stats/outbound/opens/readtimes", parameters); + + var retval = new PostmarkOutboundReadStats(); + var clientCounts = new Dictionary(); + foreach (var a in result) + if (a.Key != "Days") + clientCounts[a.Key] = a.Value.GetInt32(); + + retval.ReadCounts = clientCounts; + + var dayList = new List(); + foreach (var obj in result["Days"].EnumerateArray()) + { + var newCount = new PostmarkOutboundReadStats.DatedReadCount(); + foreach (var i in obj.EnumerateObject()) + if (i.Name == "Date") + newCount.Date = DateTime.Parse(i.Value.ToString()); + else + newCount.ReadCounts[i.Name] = i.Value.GetInt32(); + + dayList.Add(newCount); + } + + retval.Days = dayList; + + return retval; + } + + #endregion + + #region Inbound Triggers + + /// + /// Define a new Inbound Rule Trigger + /// + /// + /// + public async Task CreateInboundRuleTriggerAsync(string rule) + { + var parameters = new Dictionary(); + parameters["Rule"] = rule; + + return await ProcessRequestAsync, PostmarkInboundRuleTriggerInfo>("/triggers/inboundrules", HttpMethod.Post, parameters); + } + + /// + /// Delete an Inbound Rule Trigger + /// + /// + /// + public async Task DeleteInboundRuleTrigger(int triggerId) + { + return await ProcessNoBodyRequestAsync("/triggers/inboundrules/" + triggerId, verb: HttpMethod.Delete); + } + + /// + /// List Inbound Rule Triggers. + /// + /// + /// + /// + public async Task GetAllInboundRuleTriggers(int offset = 0, int count = 100) + { + var parameters = new Dictionary(); + parameters["offset"] = offset; + parameters["count"] = count; + + return await ProcessNoBodyRequestAsync("/triggers/inboundrules", parameters); + } + + #endregion + + #region Templates + + /// + /// Get basic info associated with the specified ID. + /// + /// The ID of the template you wish to retrive. + /// + public async Task GetTemplateAsync(long templateId) + { + return await ProcessNoBodyRequestAsync("/templates/" + templateId, null, HttpMethod.Get); + } + + /// + /// Get basic info associated with the specified Alias. + /// + /// The alias of the template you wish to retrive. + /// + public async Task GetTemplateAsync(string alias) + { + return await ProcessNoBodyRequestAsync("/templates/" + alias, null, HttpMethod.Get); + } + + /// + /// Get a listing of templates. + /// + /// The number of templates to return. Defaults to 0. + /// The number of templates to "skip" before returning results. Defaults to 100. + /// Filter the resulting templates by their TemplateType. Defaults to: All + /// Filter results by layout template alias. + /// + public async Task GetTemplatesAsync(int offset = 0, int count = 100, TemplateTypeFilter templateType = TemplateTypeFilter.All, + string layoutTemplate = null) + { + var query = new Dictionary(); + query["Count"] = count; + query["Offset"] = offset; + query["TemplateType"] = Enum.GetName(typeof(TemplateTypeFilter), templateType); + query["LayoutTemplate"] = layoutTemplate; + + return await ProcessNoBodyRequestAsync("/templates/", query, HttpMethod.Get); + } + + /// + /// Store a new template associated with this server. + /// + /// A display name for this template. + /// The subject to be used when sending with this template. + /// The HTMLBody to be used when sending with this template. Optional if TextBody is specified. + /// The TextBody to be used when sending with this template. Optional if HtmlBody is specified. + /// A friendly name to use for this template to access it, or to send with it. + /// The type of the template to create. + /// + /// The alias of the Layout template that you want to use as layout for this Standard + /// template. + /// + /// + public async Task CreateTemplateAsync(string name, string subject, string htmlBody = null, string textBody = null, string alias = null, + TemplateType templateType = TemplateType.Standard, string layoutTemplate = null) + { + var body = new Dictionary(); + body["Name"] = name; + body["HTMLBody"] = htmlBody; + body["TextBody"] = textBody; + body["Subject"] = subject; + body["Alias"] = alias; + body["TemplateType"] = Enum.GetName(typeof(TemplateType), templateType); + body["LayoutTemplate"] = layoutTemplate; + + return await ProcessRequestAsync, BasicTemplateInformation>("/templates/", HttpMethod.Post, body); + } + + public async Task EditTemplateAsync(string alias, string name = null, string subject = null, string htmlBody = null, string textBody = null, + string layoutTemplate = null) + { + var body = new Dictionary(); + body["Name"] = name; + body["HTMLBody"] = htmlBody; + body["TextBody"] = textBody; + body["Subject"] = subject; + body["LayoutTemplate"] = layoutTemplate; + + return await ProcessRequestAsync, BasicTemplateInformation>("/templates/" + alias, HttpMethod.Put, body); + } + + public async Task EditTemplateAsync(long templateId, string name = null, string subject = null, string htmlBody = null, string textBody = null, string alias = null, + string layoutTemplate = null) + { + var body = new Dictionary(); + body["Name"] = name; + body["HTMLBody"] = htmlBody; + body["TextBody"] = textBody; + body["Subject"] = subject; + body["Alias"] = alias; + body["LayoutTemplate"] = layoutTemplate; + + return await ProcessRequestAsync, BasicTemplateInformation>("/templates/" + templateId, HttpMethod.Put, body); + } + + /// + /// Delete a template from the server. + /// + /// The ID of the template you wish to delete from the server. + /// + public async Task DeleteTemplateAsync(long templateId) + { + return await ProcessNoBodyRequestAsync("/templates/" + templateId, null, HttpMethod.Delete); + } + + /// + /// Delete a template from the server. + /// + /// The Alias of the template you wish to delete from the server. + /// + public async Task DeleteTemplateAsync(string templateAlias) + { + return await ProcessNoBodyRequestAsync("/templates/" + templateAlias, null, HttpMethod.Delete); + } + + public async Task SendMessageAsync(TemplatedPostmarkMessage emailToSend) + { + return await SendEmailWithTemplateAsync(emailToSend); + } + + public async Task SendEmailWithTemplateAsync(TemplatedPostmarkMessage emailToSend) + { + return await ProcessRequestAsync("/email/withTemplate", HttpMethod.Post, emailToSend); + } + + /// + /// Sends a batch of up to 500 templated messages through the Postmark API. + /// All email addresses must be valid, and the sender must be a valid sender signature according to Postmark. + /// Either the TemplateId or the TemplateAlias must be provided for each TemplatedPostmarkMessage. + /// + /// A prepared batch of templated messages. + /// The processed messages (Complete with system assigned message IDs) + public async Task> SendMessagesAsync(params TemplatedPostmarkMessage[] messages) + { + return await SendEmailsWithTemplateAsync(messages); + } + + /// + /// Sends a batch of up to 500 templated messages through the Postmark API. + /// All email addresses must be valid, and the sender must be a valid sender signature according to Postmark. + /// Either the TemplateId or the TemplateAlias must be provided for each TemplatedPostmarkMessage. + /// + /// A prepared batch of templated messages. + /// The processed messages (Complete with system assigned message IDs) + public async Task> SendEmailsWithTemplateAsync(params TemplatedPostmarkMessage[] messages) + { + var body = new Dictionary { ["Messages"] = messages.ToList() }; + + return await ProcessRequestAsync, PostmarkResponse[]>("/email/batchWithTemplates", HttpMethod.Post, body); + } + + public async Task SendEmailWithTemplateAsync(string templateAlias, T templateModel, + string to, string from, + bool? inlineCss = null, string cc = null, + string bcc = null, string replyTo = null, + bool? trackOpens = null, + IDictionary headers = null, + IDictionary metadata = null, + string messageStream = null, + params PostmarkMessageAttachment[] attachments) + { + return await InternalSendEmailWithTemplateAsync(templateAlias, templateModel, to, from, inlineCss, cc, + bcc, replyTo, trackOpens, headers, metadata, messageStream, attachments); + } + + public async Task SendEmailWithTemplateAsync(long templateId, T templateModel, + string to, string from, + bool? inlineCss = null, string cc = null, + string bcc = null, string replyTo = null, + bool? trackOpens = null, + IDictionary headers = null, + IDictionary metadata = null, + string messageStream = null, + params PostmarkMessageAttachment[] attachments) + { + return await InternalSendEmailWithTemplateAsync(templateId, templateModel, to, from, inlineCss, cc, + bcc, replyTo, trackOpens, headers, metadata, messageStream, attachments); + } + + private async Task InternalSendEmailWithTemplateAsync(object templateReference, T templateModel, + string to, string from, + bool? inlineCss = null, string cc = null, + string bcc = null, string replyTo = null, + bool? trackOpens = null, + IDictionary headers = null, + IDictionary metadata = null, + string messageStream = null, + params PostmarkMessageAttachment[] attachments) + { + var email = new TemplatedPostmarkMessage(); + if (templateReference is long) + email.TemplateId = (long)templateReference; + else + email.TemplateAlias = (string)templateReference; + email.TemplateModel = templateModel; + email.MessageStream = messageStream; + email.To = to; + email.From = from; + if (inlineCss.HasValue) email.InlineCss = inlineCss.Value; + email.Cc = cc; + email.Bcc = bcc; + if (trackOpens.HasValue) email.TrackOpens = trackOpens.Value; + email.ReplyTo = replyTo; + if (headers != null) email.Headers = new HeaderCollection(headers); + email.Metadata = metadata; + if (attachments != null) email.Attachments = attachments; + return await SendEmailWithTemplateAsync(email); + } + + /// + /// Validate a template. + /// + /// + /// The subject content to validate. + /// The HTML body content to validate. + /// The plain text body content to validate. + /// The template model to be used when rendering test content. + /// + /// Controls whether style blocks will be inlined as style attributes on matching + /// html elements in HtmlBody. + /// + /// Validate templates based on template type. + /// + /// An optional string to specify which layout template alias to use to validate a standard + /// template. + /// + /// + public async Task ValidateTemplateAsync(string subject = null, string htmlBody = null, + string textBody = null, T testRenderModel = default, bool inlineCssForHtmlTestRender = true, + TemplateType templateType = TemplateType.Standard, string layoutTemplate = null) + { + var body = new Dictionary(); + body["TestRenderModel"] = testRenderModel; + body["Subject"] = subject; + body["HtmlBody"] = htmlBody; + body["TextBody"] = textBody; + body["InlineCssForHtmlTestRender"] = inlineCssForHtmlTestRender; + body["TemplateType"] = Enum.GetName(typeof(TemplateType), templateType); + body["LayoutTemplate"] = layoutTemplate; + + return await ProcessRequestAsync, TemplateValidationResponse>("/templates/validate", HttpMethod.Post, body); + } + + #endregion + + #region Webhooks + + /// + /// Gets the webhook configuration for the provided configuration id + /// + /// Configuration Id to search for + /// + public async Task GetWebhookConfigurationAsync(long configurationId) + { + return await ProcessNoBodyRequestAsync($"/webhooks/{configurationId}", null, HttpMethod.Get); + } + + /// + /// Gets a listing of webhook configurations for the provided server + /// + /// + /// Optional message stream to search for. + /// If not provided, all configurations for the server will be returned. + /// + /// + public async Task GetWebhookConfigurationsAsync(string messageStream = null) + { + var query = new Dictionary { ["MessageStream"] = messageStream }; + + return await ProcessNoBodyRequestAsync("/webhooks/", query, HttpMethod.Get); + } + + /// + /// Delete a webhook configuration from the server. + /// + /// Configuration id to search for + /// + public async Task DeleteWebhookConfigurationAsync(long configurationId) + { + return await ProcessNoBodyRequestAsync($"/webhooks/{configurationId}", null, HttpMethod.Delete); + } + + /// + /// Creates a new webhook configuration + /// + /// The webhook URL + /// + /// Message stream this configuration should belong to. + /// If not provided, it will belong to the default transactional stream. + /// + /// Optional Basic HTTP Authentication + /// Optional list of custom HTTP headers + /// Optional triggers for this webhook configuration + /// + public async Task CreateWebhookConfigurationAsync(string url, string messageStream = null, + HttpAuth httpAuth = null, IEnumerable httpHeaders = null, WebhookConfigurationTriggers triggers = null) + { + var body = new Dictionary(); + body["Url"] = url; + body["MessageStream"] = messageStream; + body["HttpAuth"] = httpAuth; + body["HttpHeaders"] = httpHeaders; + body["Triggers"] = triggers; + + return await ProcessRequestAsync, WebhookConfiguration>("/webhooks/", HttpMethod.Post, body); + } + + /// + /// Update a webhook configuration for the provided configuration id. + /// + /// Configuration id to search for + /// The webhook URL + /// Optional Basic HTTP Authentication + /// Optional list of custom HTTP headers + /// Optional triggers for this webhook configuration + /// + public async Task EditWebhookConfigurationAsync(long configurationId, string url, + HttpAuth httpAuth = null, IEnumerable httpHeaders = null, WebhookConfigurationTriggers triggers = null) + { + var body = new Dictionary(); + body["Url"] = url; + body["HttpAuth"] = httpAuth; + body["HttpHeaders"] = httpHeaders; + body["Triggers"] = triggers; + + return await ProcessRequestAsync, WebhookConfiguration>($"/webhooks/{configurationId}", + HttpMethod.Put, body); + } + + #endregion + + #region Suppressions + + /// + /// List Suppressions for the provided query parameters. + /// + /// Set of query parameters used to filter Suppressions. + /// MessageStream used to search for Suppressions. Defaults to "outbound". + /// Listing of active Suppressions matching the provided query parameters. + public async Task ListSuppressions(PostmarkSuppressionQuery query, string messageStream = DefaultTransactionalStream) + { + var parameters = new Dictionary + { + ["SuppressionReason"] = query.SuppressionReason, + ["Origin"] = query.Origin, + ["todate"] = query.ToDate?.ToString("O"), + ["fromdate"] = query.FromDate?.ToString("O"), + ["emailAddress"] = query.EmailAddress + }; + + var apiUrl = $"/message-streams/{messageStream}/suppressions/dump"; + + return await ProcessNoBodyRequestAsync(apiUrl, parameters); + } + + /// + /// Create Suppressions for the specified recipients. + /// + /// List of SuppressionChangeRequest objects that specify what recipients to suppress. + /// Message stream where the recipients should be suppressed. Defaults to "outbound". + /// Suppressions will be generated with a Customer Origin and will have a ManualSuppression reason. + /// The status of the request for each recipient. + public async Task CreateSuppressions(IEnumerable suppressionChanges, + string messageStream = DefaultTransactionalStream) + { + var body = new Dictionary { ["Suppressions"] = suppressionChanges.ToList() }; + + var apiUrl = $"/message-streams/{messageStream}/suppressions"; + + return await ProcessRequestAsync, PostmarkBulkSuppressionResult>(apiUrl, HttpMethod.Post, body); + } + + /// + /// Reactivate Suppressions for the specified recipients. + /// + /// List of SuppressionChangeRequest objects that specify what recipients to reactivate. + /// Message stream where the recipients should be reactivated. Defaults to "outbound". + /// Suppressions will only be reactivated if you have the required authority. + /// The status of the request for each recipient. + public async Task DeleteSuppressions(IEnumerable suppressionChanges, + string messageStream = DefaultTransactionalStream) + { + var body = new Dictionary { ["Suppressions"] = suppressionChanges.ToList() }; + + var apiUrl = $"/message-streams/{messageStream}/suppressions/delete"; + + return await ProcessRequestAsync, PostmarkBulkReactivationResult>(apiUrl, HttpMethod.Post, body); + } + + #endregion + + #region MessageStreams + + /// + /// Create a new message stream on your server. + /// + /// Identifier for your message stream, unique at server level. + /// Type of the message stream. E.g.: Transactional or Broadcasts. + /// Friendly name for your message stream. + /// Friendly description for your message stream. (optional) + /// The unsubscribe management option for the stream. (optional) + /// Currently, you cannot create multiple inbound streams. + public async Task CreateMessageStream(string id, MessageStreamType type, string name, string description = null, UnsubscribeHandlingType? unsubscriptionHandlingType = null) + { + var body = new Dictionary + { + ["ID"] = id, + ["Name"] = name, + ["Description"] = description, + ["MessageStreamType"] = type.ToString() + }; + + if (unsubscriptionHandlingType.HasValue) + body["SubscriptionManagementConfiguration"] = new Dictionary + { + ["UnsubscribeHandlingType"] = unsubscriptionHandlingType.ToString() + }; + + var apiUrl = "/message-streams/"; + + return await ProcessRequestAsync, PostmarkMessageStream>(apiUrl, HttpMethod.Post, body); + } + + /// + /// Edit the properties of a message stream. + /// + /// The identifier for the stream you are trying to update. + /// New friendly name to use. (optional) + /// New description to use. (optional) + /// New unsubscribe management option for the stream. (optional) + public async Task EditMessageStream(string id, string name = null, string description = null, UnsubscribeHandlingType? unsubscriptionHandlingType = null) + { + var body = new Dictionary + { + ["Name"] = name, + ["Description"] = description + }; + + if (unsubscriptionHandlingType.HasValue) + body["SubscriptionManagementConfiguration"] = new Dictionary + { + ["UnsubscribeHandlingType"] = unsubscriptionHandlingType.ToString() + }; + + var apiUrl = $"/message-streams/{id}"; + + return await ProcessRequestAsync, PostmarkMessageStream>(apiUrl, new HttpMethod("PATCH"), body); + } + + /// + /// Retrieve details about a message stream. + /// + /// Identifier of the stream to retrieve details for. + public async Task GetMessageStream(string id) + { + return await ProcessNoBodyRequestAsync($"/message-streams/{id}"); + } + + /// + /// Retrieve all message streams on the server. + /// + /// Filter by stream type. E.g.: Transactional. Defaults to: All. + /// Include archived streams in the result. Defaults to: false. + public async Task ListMessageStreams(MessageStreamTypeFilter messageStreamType = MessageStreamTypeFilter.All, + bool includeArchivedStreams = false) + { + var parameters = new Dictionary + { + ["MessageStreamType"] = messageStreamType.ToString(), + ["IncludeArchivedStreams"] = includeArchivedStreams + }; + + return await ProcessNoBodyRequestAsync("/message-streams/", parameters); + } + + /// + /// Archive a message stream. This will disable sending/receiving messages via that stream. + /// The stream will also stop being shown in the Postmark UI. + /// Once a stream has been archived, it will be deleted (alongside associated data) at the ExpectedPurgeDate in the + /// response. + /// + /// Identifier of the stream to archive. + public async Task ArchiveMessageStream(string id) + { + var apiUrl = $"/message-streams/{id}/archive"; + + return await ProcessNoBodyRequestAsync(apiUrl, verb: HttpMethod.Post); + } + + /// + /// UnArchive a message stream. This will resume sending/receiving via that stream. + /// The stream will also re-appear in the Postmark UI. + /// A stream can be unarchived only before the stream ExpectedPurgeDate. + /// + /// Identifier of the stream to unArchive. + public async Task UnArchiveMessageStream(string id) + { + var apiUrl = $"/message-streams/{id}/unarchive"; + + return await ProcessNoBodyRequestAsync(apiUrl, verb: HttpMethod.Post); + } + + #endregion +} \ No newline at end of file diff --git a/src/Postmark/PostmarkClientBase.cs b/src/Postmark/PostmarkClientBase.cs index 4fe9a8b..4af8591 100644 --- a/src/Postmark/PostmarkClientBase.cs +++ b/src/Postmark/PostmarkClientBase.cs @@ -1,155 +1,109 @@ -using PostmarkDotNet.Converters; -using PostmarkDotNet.Exceptions; -using PostmarkDotNet.Utility; -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Threading.Tasks; using Postmark.Exceptions; +using PostmarkDotNet.Converters; +using PostmarkDotNet.Exceptions; +using PostmarkDotNet.Utility; -namespace PostmarkDotNet +namespace PostmarkDotNet; + +/// +/// The core postmark client code. +/// +public abstract class PostmarkClientBase { + protected const string DateFormat = "yyyy-MM-dd"; + + /// + /// Default transactional message stream id. + /// + protected const string DefaultTransactionalStream = "outbound"; + + private readonly HttpClient _client; + + public PostmarkClientBase(HttpClient client) + { + _client = client; + } + + /// + /// The core delivery method for all other API methods. + /// + /// + /// + /// + /// + /// + /// + /// + protected async Task ProcessRequestAsync(string apiPath, HttpMethod verb, TRequestBody message = null) + where TRequestBody : class + { + var request = new HttpRequestMessage(verb, apiPath.TrimStart('/')); + + //if the message is not a string, or the message is a non-empty string, + //set a body for this request. + if (message != null) + { + var content = new JsonContent(message); + request.Content = content; + } + + var result = await _client.SendAsync(request); + + var body = await result.Content.ReadAsStringAsync(); + + if (JsonExtensions.TryDeserializeObject(body, out TResponse parsedResponse) + && result.StatusCode == HttpStatusCode.OK) + return parsedResponse; + + if (!JsonExtensions.TryDeserializeObject(body, out PostmarkResponse error)) + throw new PostmarkResponseException("The response from the API could not be parsed.", body); + + switch ((int)result.StatusCode) + { + case 422: + error.Status = PostmarkStatus.UserError; + break; + case 500: + error.Status = PostmarkStatus.ServerError; + break; + default: + error.Status = PostmarkStatus.Unknown; + break; + } + + throw new PostmarkValidationException(error); + } + /// - /// The core postmark client code. + /// Core implementation of HTTP interaction for "no body" requests (i.e. GET/DELETE) /// - public abstract class PostmarkClientBase - { - private static Lazy _staticClient = - new Lazy(()=>new SimpleHttpClient()) ; - - /// - /// Configure a global connection factory to to process HTTP interactions. - /// - /// - /// In most cases, you should not need to modify this property, but it's useful - /// in cases where you want to use another http client, or to mock the http processing - /// (for tests). - /// - public static Func ClientFactory {get;set;} = () => _staticClient.Value; - - protected static readonly string DATE_FORMAT = "yyyy-MM-dd"; - - /// - /// Default transactional message stream id. - /// - protected const string DefaultTransactionalStream = "outbound"; - - private static readonly string _agent = "Postmark.NET 2.x (" + - typeof(PostmarkClient).AssemblyQualifiedName + ")"; - - private Uri baseUri; - - /// - /// Provides a base implementation of core request/response interactions. - /// - /// - /// - public PostmarkClientBase(string apiBaseUri = "https://api.postmarkapp.com") - { - baseUri = new Uri(apiBaseUri); - } - - protected abstract string AuthHeaderName { get; } - - protected string _authToken; - - /// - /// The core delivery method for all other API methods. - /// - /// - /// - /// - /// - /// - /// - /// - protected async Task ProcessRequestAsync( - string apiPath, - HttpMethod verb, TRequestBody message = null) where TRequestBody : class - { - var client = ClientFactory(); - - var request = new HttpRequestMessage(verb, baseUri + apiPath.TrimStart('/')); - - //if the message is not a string, or the message is a non-empty string, - //set a body for this request. - if (message != null) - { - var content = new JsonContent(message); - request.Content = content; - } - - request.Headers.Add("Accept", "application/json"); - request.Headers.Add(AuthHeaderName, _authToken); - request.Headers.Add("User-Agent", _agent); - - var result = await client.SendAsync(request); - - var body = await result.Content.ReadAsStringAsync(); - - if (JsonExtensions.TryDeserializeObject(body, out TResponse parsedResponse) - && result.StatusCode == HttpStatusCode.OK) - { - return parsedResponse; - } - - if (!JsonExtensions.TryDeserializeObject(body, out PostmarkResponse error)) - { - throw new PostmarkResponseException("The response from the API could not be parsed.", body); - } - - switch ((int) result.StatusCode) - { - case 422: - error.Status = PostmarkStatus.UserError; - break; - case 500: - error.Status = PostmarkStatus.ServerError; - break; - default: - error.Status = PostmarkStatus.Unknown; - break; - } - - throw new PostmarkValidationException(error); - } - - /// - /// Core implementation of HTTP interaction for "no body" requests (i.e. GET/DELETE) - /// - /// - /// - /// - /// The http verb to use for the request. - /// - protected async Task ProcessNoBodyRequestAsync( - string apiPath, - IDictionary parameters = null, - HttpMethod verb = null) - { - parameters = parameters ?? new Dictionary(); - - var query = ""; - foreach (var param in parameters) - { - if (param.Value != null) - { - if (query != "") - { - query += "&"; - } - query += String.Format("{0}={1}", WebUtility.UrlEncode(param.Key), WebUtility.UrlEncode(param.Value.ToString())); - } - } - - if (!String.IsNullOrWhiteSpace(query)) - { - apiPath = apiPath + "?" + query; - } - - return await ProcessRequestAsync(apiPath, verb ?? HttpMethod.Get); - } - - } -} + /// + /// + /// + /// The http verb to use for the request. + /// + protected async Task ProcessNoBodyRequestAsync( + string apiPath, + IDictionary parameters = null, + HttpMethod verb = null) + { + parameters ??= new Dictionary(); + + var query = ""; + foreach (var param in parameters) + if (param.Value != null) + { + if (query != "") query += "&"; + query += $"{WebUtility.UrlEncode(param.Key)}={WebUtility.UrlEncode(param.Value.ToString())}"; + } + + if (!string.IsNullOrWhiteSpace(query)) + apiPath = $"{apiPath}?{query}"; + + return await ProcessRequestAsync(apiPath, verb ?? HttpMethod.Get); + } +} \ No newline at end of file diff --git a/src/Postmark/PostmarkClientExtensions.cs b/src/Postmark/PostmarkClientExtensions.cs index 09775cb..69e40f6 100644 --- a/src/Postmark/PostmarkClientExtensions.cs +++ b/src/Postmark/PostmarkClientExtensions.cs @@ -26,7 +26,7 @@ public static class PostmarkClientExtensions /// A collection of additional mail headers to send with the message. /// The message stream used to send this message. /// A with details about the transaction. - public static async Task SendMessageAsync(this PostmarkClient client, + public static async Task SendMessageAsync(this IPostmarkClient client, string from, string to, string subject, string textBody, string htmlBody, IDictionary headers = null, IDictionary metadata = null, @@ -47,7 +47,7 @@ public static async Task SendMessageAsync(this PostmarkClient /// The client to use when sending the batch. /// A prepared message batch. /// - public static async Task> SendMessagesAsync(this PostmarkClient client, IEnumerable messages) + public static async Task> SendMessagesAsync(this IPostmarkClient client, IEnumerable messages) { return await client.SendMessagesAsync(messages.ToArray()); } diff --git a/src/Postmark/PostmarkServiceCollectionExtensions.cs b/src/Postmark/PostmarkServiceCollectionExtensions.cs new file mode 100644 index 0000000..0fceb0f --- /dev/null +++ b/src/Postmark/PostmarkServiceCollectionExtensions.cs @@ -0,0 +1,55 @@ +using System; +using Microsoft.Extensions.DependencyInjection; + +namespace PostmarkDotNet +{ + public static class PostmarkServiceCollectionExtensions + { + private const string BaseUri = "https://api.postmarkapp.com"; + private static readonly string UserAgent = $"Postmark.NET 2.x ({typeof(PostmarkClient).AssemblyQualifiedName})"; + + public static IHttpClientBuilder AddPostmarkClient( + this IServiceCollection services, + string serverToken) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + if (string.IsNullOrWhiteSpace(serverToken)) + throw new ArgumentException("serverToken is required.", nameof(serverToken)); + + return services + .AddHttpClient() + .ConfigureHttpClient((_, client) => + { + client.BaseAddress = new Uri(BaseUri); + client.Timeout = TimeSpan.FromSeconds(60); + + client.DefaultRequestHeaders.Add("Accept", "application/json"); + client.DefaultRequestHeaders.Add("User-Agent", UserAgent); + + client.DefaultRequestHeaders.Add("X-Postmark-Server-Token", serverToken); + }); + } + + public static IHttpClientBuilder AddPostmarkAdminClient( + this IServiceCollection services, + string accountToken) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + if (string.IsNullOrWhiteSpace(accountToken)) + throw new ArgumentException("accountToken is required.", nameof(accountToken)); + + return services + .AddHttpClient() + .ConfigureHttpClient((_, client) => + { + client.BaseAddress = new Uri(BaseUri); + client.Timeout = TimeSpan.FromSeconds(60); + + client.DefaultRequestHeaders.Add("Accept", "application/json"); + client.DefaultRequestHeaders.Add("User-Agent", UserAgent); + + client.DefaultRequestHeaders.Add("X-Postmark-Account-Token", accountToken); + }); + } + } +} \ No newline at end of file diff --git a/src/Postmark/SimpleHttpClient.cs b/src/Postmark/SimpleHttpClient.cs deleted file mode 100644 index f816f36..0000000 --- a/src/Postmark/SimpleHttpClient.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Net.Http; -using System.Threading.Tasks; - -namespace PostmarkDotNet -{ - internal class SimpleHttpClient : ISimpleHttpClient - { - private HttpClient _client = new HttpClient(); - - public SimpleHttpClient(TimeSpan? timeoutLength = null){ - _client.Timeout = timeoutLength ?? TimeSpan.FromSeconds(60); - } - - public Task SendAsync(HttpRequestMessage request) - { - return _client.SendAsync(request); - } - - ~SimpleHttpClient(){ - _client.Dispose(); - } - } -}