diff --git a/src/Resend.Webhooks/DomainEventData.cs b/src/Resend.Webhooks/DomainEventData.cs index a556286..65fed39 100644 --- a/src/Resend.Webhooks/DomainEventData.cs +++ b/src/Resend.Webhooks/DomainEventData.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace Resend.Webhooks; @@ -34,6 +34,26 @@ public class DomainEventData : IWebhookData [JsonPropertyName( "region" )] public DeliveryRegion Region { get; set; } + /// + [JsonPropertyName( "open_tracking" )] + [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] + public bool? OpenTracking { get; set; } + + /// + [JsonPropertyName( "click_tracking" )] + [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] + public bool? ClickTracking { get; set; } + + /// + [JsonPropertyName( "tracking_subdomain" )] + [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] + public string? TrackingSubdomain { get; set; } + + /// + [JsonPropertyName( "capabilities" )] + [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] + public DomainCapabilities? Capabilities { get; set; } + /// /// DNS records used for domain validation. /// diff --git a/src/Resend/Domain.cs b/src/Resend/Domain.cs index 3f32a55..04797dd 100644 --- a/src/Resend/Domain.cs +++ b/src/Resend/Domain.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace Resend; @@ -36,6 +36,34 @@ public class Domain [JsonPropertyName( "region" )] public DeliveryRegion Region { get; set; } + /// + /// Whether open tracking is enabled for this domain. + /// + [JsonPropertyName( "open_tracking" )] + [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] + public bool? OpenTracking { get; set; } + + /// + /// Whether click tracking is enabled for this domain. + /// + [JsonPropertyName( "click_tracking" )] + [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] + public bool? ClickTracking { get; set; } + + /// + /// Subdomain used for click and open tracking URLs (for example, links for links.example.com). + /// + [JsonPropertyName( "tracking_subdomain" )] + [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] + public string? TrackingSubdomain { get; set; } + + /// + /// Whether this domain can send and receive email. + /// + [JsonPropertyName( "capabilities" )] + [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] + public DomainCapabilities? Capabilities { get; set; } + /// /// DNS records used for domain validation. /// diff --git a/src/Resend/DomainAddData.cs b/src/Resend/DomainAddData.cs index d7a116b..7524ded 100644 --- a/src/Resend/DomainAddData.cs +++ b/src/Resend/DomainAddData.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace Resend; @@ -29,4 +29,39 @@ public class DomainAddData [JsonPropertyName( "custom_return_path" )] [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] public string? CustomReturnPath { get; set; } + + /// + /// TLS mode for outbound mail from this domain. + /// + [JsonPropertyName( "tls" )] + [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] + public TlsMode? TlsMode { get; set; } + + /// + /// Configure sending and receiving for this domain. At least one capability must remain enabled. + /// + [JsonPropertyName( "capabilities" )] + [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] + public DomainCapabilities? Capabilities { get; set; } + + /// + /// Track the open rate of each email. + /// + [JsonPropertyName( "open_tracking" )] + [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] + public bool? OpenTracking { get; set; } + + /// + /// Track clicks within the body of each HTML email. + /// + [JsonPropertyName( "click_tracking" )] + [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] + public bool? ClickTracking { get; set; } + + /// + /// Subdomain for click and open tracking (for example, links for links.example.com). + /// + [JsonPropertyName( "tracking_subdomain" )] + [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] + public string? TrackingSubdomain { get; set; } } \ No newline at end of file diff --git a/src/Resend/DomainCapabilities.cs b/src/Resend/DomainCapabilities.cs new file mode 100644 index 0000000..7fa61c3 --- /dev/null +++ b/src/Resend/DomainCapabilities.cs @@ -0,0 +1,23 @@ +using System.Text.Json.Serialization; + +namespace Resend; + +/// +/// Sending and receiving capabilities for a domain. +/// +public class DomainCapabilities +{ + /// + /// Whether sending is enabled for this domain. Example values: enabled, disabled. + /// + [JsonPropertyName( "sending" )] + [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] + public string? Sending { get; set; } + + /// + /// Whether receiving is enabled for this domain. Example values: enabled, disabled. + /// + [JsonPropertyName( "receiving" )] + [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] + public string? Receiving { get; set; } +} diff --git a/src/Resend/DomainRecord.cs b/src/Resend/DomainRecord.cs index 37156de..8aa7eeb 100644 --- a/src/Resend/DomainRecord.cs +++ b/src/Resend/DomainRecord.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace Resend; @@ -7,7 +7,7 @@ public class DomainRecord { /// /// - /// Example values: SPF, DKIM. + /// Example values: SPF, DKIM, Receiving, Tracking. /// [JsonPropertyName( "record" )] public string Record { get; set; } = default!; diff --git a/src/Resend/DomainUpdateData.cs b/src/Resend/DomainUpdateData.cs index 303297f..3e6f724 100644 --- a/src/Resend/DomainUpdateData.cs +++ b/src/Resend/DomainUpdateData.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace Resend; @@ -23,6 +23,24 @@ public class DomainUpdateData [JsonPropertyName( "tls" )] [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] public TlsMode? TlsMode { get; set; } + + /// + /// Configure sending and receiving for this domain. At least one capability must remain enabled. + /// + [JsonPropertyName( "capabilities" )] + [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] + public DomainCapabilities? Capabilities { get; set; } + + /// + /// Subdomain for click and open tracking (for example, links for links.example.com). + /// + /// + /// This value can only be set after it has been specified, never cleared. After changing the tracking + /// subdomain, verify DNS again; until then, the previous value may still be used for tracked links. + /// + [JsonPropertyName( "tracking_subdomain" )] + [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] + public string? TrackingSubdomain { get; set; } } diff --git a/tests/Resend.Tests/DomainTests.cs b/tests/Resend.Tests/DomainTests.cs new file mode 100644 index 0000000..b6f47d6 --- /dev/null +++ b/tests/Resend.Tests/DomainTests.cs @@ -0,0 +1,113 @@ +using System.Net.Http.Json; +using System.Text.Json; + +namespace Resend.Tests; + +/// +public class DomainTests +{ + /// + [Fact] + public void DomainAddData_serializes_capabilities_and_tls() + { + var data = new DomainAddData() + { + DomainName = "example.com", + TlsMode = TlsMode.Enforced, + Capabilities = new DomainCapabilities() + { + Sending = "enabled", + Receiving = "disabled", + }, + }; + + var json = JsonSerializer.Serialize( data ); + Assert.Contains( "\"tls\":\"enforced\"", json ); + Assert.Contains( "\"capabilities\"", json ); + Assert.Contains( "\"sending\":\"enabled\"", json ); + } + + + /// + [Fact] + public void DomainAddData_serializes_tracking_fields() + { + var data = new DomainAddData() + { + DomainName = "example.com", + ClickTracking = true, + TrackingSubdomain = "links", + }; + + var json = JsonSerializer.Serialize( data ); + Assert.Contains( "\"click_tracking\":true", json ); + Assert.Contains( "\"tracking_subdomain\":\"links\"", json ); + Assert.DoesNotContain( "open_tracking", json ); + } + + + /// + [Fact] + public void Domain_deserializes_tracking_and_capabilities() + { + const string json = """ + { + "id": "d91cd9bd-1176-453e-8fc1-35364d380206", + "name": "example.com", + "status": "not_started", + "created_at": "2023-04-26T20:21:26.347412+00:00", + "region": "us-east-1", + "open_tracking": true, + "click_tracking": false, + "tracking_subdomain": "links", + "capabilities": { + "sending": "enabled", + "receiving": "disabled" + }, + "records": [] + } + """; + + var domain = JsonSerializer.Deserialize( json ); + Assert.NotNull( domain ); + Assert.Equal( "links", domain!.TrackingSubdomain ); + Assert.True( domain.OpenTracking ); + Assert.False( domain.ClickTracking ); + Assert.NotNull( domain.Capabilities ); + Assert.Equal( "enabled", domain.Capabilities!.Sending ); + Assert.Equal( "disabled", domain.Capabilities.Receiving ); + } + + + /// + [Fact] + public void DomainUpdateData_serializes_tracking_subdomain() + { + var data = new DomainUpdateData() + { + TrackClicks = true, + TrackOpen = false, + TrackingSubdomain = "links", + }; + + var json = JsonSerializer.Serialize( data ); + Assert.Contains( "\"tracking_subdomain\":\"links\"", json ); + } + + + /// + [Fact] + public async Task JsonContent_Create_omits_null_tracking_subdomain_on_update() + { + var data = new DomainUpdateData() + { + TrackClicks = true, + TrackOpen = true, + TrackingSubdomain = null, + }; + + using var content = JsonContent.Create( data ); + var json = await content.ReadAsStringAsync(); + Assert.DoesNotContain( "tracking_subdomain", json ); + } +}