diff --git a/tests/Resend.Webhooks.Tests/WebhookOutOfOrderTests.cs b/tests/Resend.Webhooks.Tests/WebhookOutOfOrderTests.cs new file mode 100644 index 0000000..6844b38 --- /dev/null +++ b/tests/Resend.Webhooks.Tests/WebhookOutOfOrderTests.cs @@ -0,0 +1,79 @@ +using System.Text.Json; + +namespace Resend.Webhooks.Tests; + +/// +public class WebhookOutOfOrderTests +{ + /// + /// + /// Real Resend payloads serialize type last (after created_at + /// and data); the converter must not assume type comes first. + /// + [Fact] + public void EmailEventTypeLastDeserializes() + { + var json = """ + { + "created_at": "2026-07-09T18:23:37.867Z", + "data": { + "created_at": "2026-07-09T18:23:37.236Z", + "email_id": "c4bd0130-17f1-4e49-90bc-856308df9bb5", + "failed": { "reason": "domain_not_verified" }, + "from": "Acme ", + "subject": "Test", + "template_id": "2dc48b66-eef5-4601-b509-ca33f3e10b60", + "to": [ "to@example.com" ] + }, + "type": "email.failed" + } + """; + + var evt = JsonSerializer.Deserialize( json ); + + Assert.NotNull( evt ); + Assert.Equal( WebhookEventType.EmailFailed, evt.EventType ); + + var data = evt.DataAs(); + Assert.Equal( "domain_not_verified", data.Failed?.Reason ); + Assert.Equal( "2dc48b66-eef5-4601-b509-ca33f3e10b60", data.TemplateId ); + } + + + /// + /// Contact events serialize created_at before type. + [Fact] + public void ContactEventCreatedAtFirstDeserializes() + { + var json = """ + { + "created_at": "2026-07-09T18:23:37.867Z", + "type": "contact.created", + "data": { + "id": "e169aa45-1ecf-4183-9955-b1499d5701d3", + "created_at": "2026-07-09T18:23:37.236Z", + "updated_at": "2026-07-09T18:23:37.236Z", + "email": "to@example.com", + "unsubscribed": false + } + } + """; + + var evt = JsonSerializer.Deserialize( json ); + + Assert.NotNull( evt ); + Assert.Equal( WebhookEventType.ContactCreated, evt.EventType ); + Assert.Equal( "to@example.com", evt.DataAs().Email ); + } + + + /// + [Theory] + [InlineData( """{ "type": "email.sent", "type": "email.sent", "created_at": "2026-07-09T18:23:37.867Z", "data": {} }""" )] + [InlineData( """{ "type": "email.sent", "created_at": "2026-07-09T18:23:37.867Z" }""" )] + [InlineData( """{ "type": "email.sent", "created_at": "2026-07-09T18:23:37.867Z", "data": {}, "extra": 1 }""" )] + public void MalformedEnvelopeThrows( string json ) + { + Assert.ThrowsAny( () => JsonSerializer.Deserialize( json ) ); + } +} diff --git a/tools/Resend.Cli/Email/EmailSendCommand.cs b/tools/Resend.Cli/Email/EmailSendCommand.cs index 11b4c08..4484cc3 100644 --- a/tools/Resend.Cli/Email/EmailSendCommand.cs +++ b/tools/Resend.Cli/Email/EmailSendCommand.cs @@ -122,7 +122,9 @@ public async Task OnExecuteAsync() */ if ( this.Verbose == true ) { - Console.WriteLine( "From: {0}", message.From.Email ); + if ( message.From != null ) + Console.WriteLine( "From: {0}", message.From.Email ); + Console.WriteLine( " To: {0}", string.Join( ", ", message.To ) ); Console.WriteLine( "Subj: {0}", message.Subject );