diff --git a/examples/eventwebhook/consumer/Dockerfile b/examples/eventwebhook/consumer/Dockerfile new file mode 100644 index 000000000..bebfb684b --- /dev/null +++ b/examples/eventwebhook/consumer/Dockerfile @@ -0,0 +1,21 @@ +FROM microsoft/dotnet:2.1-sdk AS build +WORKDIR /App + +# copy csproj and restore as distinct layers +COPY *.sln . +COPY Src/EventWebhook/*.csproj ./Src/EventWebhook/ +COPY Tests/EventWebhook.Tests/*.csproj ./Tests/EventWebhook.Tests/ +RUN dotnet restore + +# copy everything else and build app +COPY Src/EventWebhook/. ./Src/EventWebhook/ +WORKDIR /App/Src/EventWebhook +RUN dotnet publish -c Release -o Out + +FROM microsoft/dotnet:2.1-aspnetcore-runtime AS runtime +WORKDIR /App +COPY --from=build /App/Src/EventWebhook/Out ./ + +RUN echo "ASPNETCORE_URLS=http://0.0.0.0:\$PORT\nDOTNET_RUNNING_IN_CONTAINER=true" > /App/SetupHerokuEnv.sh && chmod +x /App/SetupHerokuEnv.sh + +CMD /bin/bash -c "source /App/SetupHerokuEnv.sh && dotnet EventWebhook.dll" \ No newline at end of file diff --git a/examples/eventwebhook/consumer/README.md b/examples/eventwebhook/consumer/README.md new file mode 100644 index 000000000..9ec23f614 --- /dev/null +++ b/examples/eventwebhook/consumer/README.md @@ -0,0 +1,99 @@ +# sendgrid-events-webhook-consumer +This is dockerized SendGrid Event webhook consumer. +# Overview + +SendGrid has an [Event Webhook](https://sendgrid.com/docs/API_Reference/Event_Webhook/event.html) which posts events related to your email activity to a URL of your choice. This is an easily deployable solution that allows for customers to easiy get up and running processing (parse and save) their event webhooks. + +This is docker-based solution which can be deployed on cloud services like Heroku out of the box. + +# Table of Content +- [Prerequisite](#prerequisite) +- [Deploy locally](#deploy_locally) +- [Deploy Heroku](#deploy_heroku) +- [Testing the Source Code](#testing_the_source_code) + + +## Prerequisite + +Clone the repository +``` +git clone https://github.com/sendgrid/sendgrid-csharp.git +``` + +Move into the clonned repository +``` +cd sendgrid-csharp/examples/eventwebhook/consumer +``` + +Restore the Packages +``` +dotnet restore +``` + + +## Deploy locally +Setup your MX records. Depending on your domain name host, you may need to wait up to 48 hours for the settings to propagate. + +Run the Event Webhook Parse listener in your terminal: +``` +git clone https://github.com/sendgrid/sendgrid-csharp.git + +cd sendgrid-csharp/examples/eventwebhook/consumer + +dotnet restore + +dotnet run --project .\Src\EventWebhook\EventWebhook.csproj +``` +Above will start server listening on a random port like below + +In another terminal, use ngrok to allow external access to your machine: +``` +ngrok http PORT_NUMBER +``` + +You can use setup the Event Webhook please refer [this](https://sendgrid.com/docs/API_Reference/Event_Webhook/getting_started_event_webhook.html#-Getting-started) + + +## Deploy to Heroku + +[Create](https://signup.heroku.com/) Heruko account if not already present + +Install the Heroku CLI + +Download and install the [Heroku CLI](https://devcenter.heroku.com/articles/heroku-command-line). + +If you haven't already, log in to your Heroku account and follow the prompts to create a new SSH public key. +``` +$ heroku login +``` + +Now you can sign into Container Registry. +``` +$ heroku container:login +``` + +Create app in heroku +``` +$ heroku apps:create UNIQUE_APP_NAME +``` + +Create docker image +``` +$ docker image -t DOCKER_IMAGE_NAME . +``` + +Push your Docker-based app +Build the Dockerfile in the current directory and push the Docker image. +``` +$ heroku container:push web --app UNIQUE_APP_NAME +``` + +Deploy the changes +Release the newly pushed images to deploy your app. +``` +$ heroku container:release web --app UNIQUE_APP_NAME +``` + + +## Testing the Source Code +You can get all the test cases inside the `Tests` folder. diff --git a/examples/eventwebhook/consumer/SendGridEventWebhookConsumer.sln b/examples/eventwebhook/consumer/SendGridEventWebhookConsumer.sln new file mode 100644 index 000000000..edc1dedce --- /dev/null +++ b/examples/eventwebhook/consumer/SendGridEventWebhookConsumer.sln @@ -0,0 +1,56 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26124.0 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Src", "Src", "{B08185CF-3F2E-4638-877B-587F5F60CA74}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventWebhook", "Src\EventWebhook\EventWebhook.csproj", "{3897C29A-AE26-4FE5-8421-71896EC935C5}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{81CAC535-9854-47AD-9D3E-385AC2668C35}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventWebhook.Tests", "Tests\EventWebhook.Tests\EventWebhook.Tests.csproj", "{5C6AA0EB-57B7-4E76-804A-70F7A7DF4FC0}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3897C29A-AE26-4FE5-8421-71896EC935C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3897C29A-AE26-4FE5-8421-71896EC935C5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3897C29A-AE26-4FE5-8421-71896EC935C5}.Debug|x64.ActiveCfg = Debug|Any CPU + {3897C29A-AE26-4FE5-8421-71896EC935C5}.Debug|x64.Build.0 = Debug|Any CPU + {3897C29A-AE26-4FE5-8421-71896EC935C5}.Debug|x86.ActiveCfg = Debug|Any CPU + {3897C29A-AE26-4FE5-8421-71896EC935C5}.Debug|x86.Build.0 = Debug|Any CPU + {3897C29A-AE26-4FE5-8421-71896EC935C5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3897C29A-AE26-4FE5-8421-71896EC935C5}.Release|Any CPU.Build.0 = Release|Any CPU + {3897C29A-AE26-4FE5-8421-71896EC935C5}.Release|x64.ActiveCfg = Release|Any CPU + {3897C29A-AE26-4FE5-8421-71896EC935C5}.Release|x64.Build.0 = Release|Any CPU + {3897C29A-AE26-4FE5-8421-71896EC935C5}.Release|x86.ActiveCfg = Release|Any CPU + {3897C29A-AE26-4FE5-8421-71896EC935C5}.Release|x86.Build.0 = Release|Any CPU + {5C6AA0EB-57B7-4E76-804A-70F7A7DF4FC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5C6AA0EB-57B7-4E76-804A-70F7A7DF4FC0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5C6AA0EB-57B7-4E76-804A-70F7A7DF4FC0}.Debug|x64.ActiveCfg = Debug|Any CPU + {5C6AA0EB-57B7-4E76-804A-70F7A7DF4FC0}.Debug|x64.Build.0 = Debug|Any CPU + {5C6AA0EB-57B7-4E76-804A-70F7A7DF4FC0}.Debug|x86.ActiveCfg = Debug|Any CPU + {5C6AA0EB-57B7-4E76-804A-70F7A7DF4FC0}.Debug|x86.Build.0 = Debug|Any CPU + {5C6AA0EB-57B7-4E76-804A-70F7A7DF4FC0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5C6AA0EB-57B7-4E76-804A-70F7A7DF4FC0}.Release|Any CPU.Build.0 = Release|Any CPU + {5C6AA0EB-57B7-4E76-804A-70F7A7DF4FC0}.Release|x64.ActiveCfg = Release|Any CPU + {5C6AA0EB-57B7-4E76-804A-70F7A7DF4FC0}.Release|x64.Build.0 = Release|Any CPU + {5C6AA0EB-57B7-4E76-804A-70F7A7DF4FC0}.Release|x86.ActiveCfg = Release|Any CPU + {5C6AA0EB-57B7-4E76-804A-70F7A7DF4FC0}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {3897C29A-AE26-4FE5-8421-71896EC935C5} = {B08185CF-3F2E-4638-877B-587F5F60CA74} + {5C6AA0EB-57B7-4E76-804A-70F7A7DF4FC0} = {81CAC535-9854-47AD-9D3E-385AC2668C35} + EndGlobalSection +EndGlobal diff --git a/examples/eventwebhook/consumer/Src/EventWebhook/Controllers/EventWebhookController.cs b/examples/eventwebhook/consumer/Src/EventWebhook/Controllers/EventWebhookController.cs new file mode 100644 index 000000000..ea544a0f2 --- /dev/null +++ b/examples/eventwebhook/consumer/Src/EventWebhook/Controllers/EventWebhookController.cs @@ -0,0 +1,34 @@ +using EventWebhook.Models; +using EventWebhook.Parser; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace EventWebhook.Controllers +{ + [Route("/")] + public class EventWebhookController : Controller + { + /// + /// GET : Index page + /// + [Route("")] + public IActionResult Index() + { + return View(); + } + + /// + /// POST : Event webhook handler + /// + /// + [Route("/events")] + [HttpPost] + public async Task Events() + { + IEnumerable events = await EventParser.ParseAsync(Request.Body); + + return Ok(); + } + } +} diff --git a/examples/eventwebhook/consumer/Src/EventWebhook/Converters/CategoryConverter.cs b/examples/eventwebhook/consumer/Src/EventWebhook/Converters/CategoryConverter.cs new file mode 100644 index 000000000..966dc2af6 --- /dev/null +++ b/examples/eventwebhook/consumer/Src/EventWebhook/Converters/CategoryConverter.cs @@ -0,0 +1,50 @@ +using EventWebhook.Models; +using Newtonsoft.Json; +using System; +using System.Linq; + +namespace EventWebhook.Converters +{ + public class CategoryConverter : JsonConverter + { + private readonly Type[] _types; + + public CategoryConverter() + { + _types = new Type[] { typeof(string), typeof(string[]) }; + } + + public override bool CanConvert(Type objectType) + { + return _types.Any(t => t == objectType); + } + + public override bool CanWrite => true; + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.StartArray) + { + return new Category(serializer.Deserialize(reader), JsonToken.StartArray); + } + else + { + return new Category(new[] { serializer.Deserialize(reader) }, reader.TokenType); + } + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value is Category category) + { + if (category.IsArray) + { + serializer.Serialize(writer, category); + } else + { + serializer.Serialize(writer, category.Value[0]); + } + } + } + } +} diff --git a/examples/eventwebhook/consumer/Src/EventWebhook/Converters/UriConverter.cs b/examples/eventwebhook/consumer/Src/EventWebhook/Converters/UriConverter.cs new file mode 100644 index 000000000..06c55f1e8 --- /dev/null +++ b/examples/eventwebhook/consumer/Src/EventWebhook/Converters/UriConverter.cs @@ -0,0 +1,40 @@ +using Newtonsoft.Json; +using System; + +namespace EventWebhook.Converters +{ + public class UriConverter : JsonConverter + { + public override bool CanConvert(Type objectType) => objectType == typeof(string); + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) + { + return null; + } + + if (reader.TokenType == JsonToken.String) + { + return new Uri((string)reader.Value); + } + + throw new InvalidOperationException("Invalid Url"); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (null == value) + { + writer.WriteNull(); + return; + } + + if (value is Uri) + { + writer.WriteValue(((Uri)value).OriginalString); + return; + } + } + } +} diff --git a/examples/eventwebhook/consumer/Src/EventWebhook/EventWebhook.csproj b/examples/eventwebhook/consumer/Src/EventWebhook/EventWebhook.csproj new file mode 100644 index 000000000..1318472b7 --- /dev/null +++ b/examples/eventwebhook/consumer/Src/EventWebhook/EventWebhook.csproj @@ -0,0 +1,18 @@ + + + + netcoreapp2.1 + + + + + + + + + + + + + + diff --git a/examples/eventwebhook/consumer/Src/EventWebhook/Models/BounceEvent.cs b/examples/eventwebhook/consumer/Src/EventWebhook/Models/BounceEvent.cs new file mode 100644 index 000000000..484c8911b --- /dev/null +++ b/examples/eventwebhook/consumer/Src/EventWebhook/Models/BounceEvent.cs @@ -0,0 +1,11 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace EventWebhook.Models +{ + public class BounceEvent : DroppedEvent + { + [JsonConverter(typeof(StringEnumConverter))] + public BounceEventType BounceType { get; set; } + } +} diff --git a/examples/eventwebhook/consumer/Src/EventWebhook/Models/BounceEventType.cs b/examples/eventwebhook/consumer/Src/EventWebhook/Models/BounceEventType.cs new file mode 100644 index 000000000..0948a650c --- /dev/null +++ b/examples/eventwebhook/consumer/Src/EventWebhook/Models/BounceEventType.cs @@ -0,0 +1,9 @@ +namespace EventWebhook.Models +{ + public enum BounceEventType + { + Bounce, + Blocked, + Expired + } +} diff --git a/examples/eventwebhook/consumer/Src/EventWebhook/Models/Category.cs b/examples/eventwebhook/consumer/Src/EventWebhook/Models/Category.cs new file mode 100644 index 000000000..b6762e74d --- /dev/null +++ b/examples/eventwebhook/consumer/Src/EventWebhook/Models/Category.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace EventWebhook.Models +{ + public class Category + { + public string[] Value { get; } + private JsonToken _jsonToken; + + public Category(string[] value, JsonToken jsonToken) + { + Value = value; + _jsonToken = jsonToken; + } + + [JsonIgnore] + public bool IsArray => _jsonToken == JsonToken.StartArray; + } +} diff --git a/examples/eventwebhook/consumer/Src/EventWebhook/Models/ClickEvent.cs b/examples/eventwebhook/consumer/Src/EventWebhook/Models/ClickEvent.cs new file mode 100644 index 000000000..94a8be67c --- /dev/null +++ b/examples/eventwebhook/consumer/Src/EventWebhook/Models/ClickEvent.cs @@ -0,0 +1,12 @@ +using EventWebhook.Converters; +using Newtonsoft.Json; +using System; + +namespace EventWebhook.Models +{ + public class ClickEvent : OpenEvent + { + [JsonConverter(typeof(UriConverter))] + public Uri Url { get; set; } + } +} diff --git a/examples/eventwebhook/consumer/Src/EventWebhook/Models/DeferredEvent.cs b/examples/eventwebhook/consumer/Src/EventWebhook/Models/DeferredEvent.cs new file mode 100644 index 000000000..2704527a7 --- /dev/null +++ b/examples/eventwebhook/consumer/Src/EventWebhook/Models/DeferredEvent.cs @@ -0,0 +1,8 @@ +namespace EventWebhook.Models +{ + public class DeferredEvent : DeliveredEvent + { + public int Attempt { get; set; } + + } +} diff --git a/examples/eventwebhook/consumer/Src/EventWebhook/Models/DeliveredEvent.cs b/examples/eventwebhook/consumer/Src/EventWebhook/Models/DeliveredEvent.cs new file mode 100644 index 000000000..0a0b7df05 --- /dev/null +++ b/examples/eventwebhook/consumer/Src/EventWebhook/Models/DeliveredEvent.cs @@ -0,0 +1,7 @@ +namespace EventWebhook.Models +{ + public class DeliveredEvent : Event + { + public string Response { get; set; } + } +} diff --git a/examples/eventwebhook/consumer/Src/EventWebhook/Models/DroppedEvent.cs b/examples/eventwebhook/consumer/Src/EventWebhook/Models/DroppedEvent.cs new file mode 100644 index 000000000..d3f38649e --- /dev/null +++ b/examples/eventwebhook/consumer/Src/EventWebhook/Models/DroppedEvent.cs @@ -0,0 +1,8 @@ +namespace EventWebhook.Models +{ + public class DroppedEvent : Event + { + public string Reason { get; set; } + public string Status { get; set; } + } +} diff --git a/examples/eventwebhook/consumer/Src/EventWebhook/Models/Event.cs b/examples/eventwebhook/consumer/Src/EventWebhook/Models/Event.cs new file mode 100644 index 000000000..ef23f9a00 --- /dev/null +++ b/examples/eventwebhook/consumer/Src/EventWebhook/Models/Event.cs @@ -0,0 +1,45 @@ +using EventWebhook.Converters; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; + +namespace EventWebhook.Models +{ + public class Event + { + public string Email { get; set; } + + [JsonConverter(typeof(UnixDateTimeConverter))] + public DateTime Timestamp { get; set; } + + [JsonProperty("smtp-id")] + public string SmtpId { get; set; } + + [JsonProperty("event")] + [JsonConverter(typeof(StringEnumConverter))] + public EventType EventType { get; set; } + + [JsonConverter(typeof(CategoryConverter))] + public Category Category { get; set; } + + [JsonProperty("sg_event_id")] + public string SendGridEventId { get; set; } + + [JsonProperty("sg_message_id")] + public string SendGridMessageId { get; set; } + + public string TLS { get; set; } + + [JsonExtensionData] + public IDictionary UniqueArgs { get; set; } + + [JsonProperty("marketing_campaign_id")] + public string MarketingCampainId { get; set; } + + [JsonProperty("marketing_campaign_name")] + public string MarketingCampainName { get; set; } + + } +} diff --git a/examples/eventwebhook/consumer/Src/EventWebhook/Models/EventType.cs b/examples/eventwebhook/consumer/Src/EventWebhook/Models/EventType.cs new file mode 100644 index 000000000..0bb8b79de --- /dev/null +++ b/examples/eventwebhook/consumer/Src/EventWebhook/Models/EventType.cs @@ -0,0 +1,21 @@ +using System.Runtime.Serialization; + +namespace EventWebhook.Models +{ + public enum EventType + { + Processed, + Deferred, + Delivered, + Open, + Click, + Bounce, + Dropped, + SpamReport, + Unsubscribe, + [EnumMember(Value = "group_unsubscribe")] + GroupUnsubscribe, + [EnumMember(Value = "group_resubscribe")] + GroupResubscribe + } +} diff --git a/examples/eventwebhook/consumer/Src/EventWebhook/Models/GroupResubscribeEvent.cs b/examples/eventwebhook/consumer/Src/EventWebhook/Models/GroupResubscribeEvent.cs new file mode 100644 index 000000000..4034c5384 --- /dev/null +++ b/examples/eventwebhook/consumer/Src/EventWebhook/Models/GroupResubscribeEvent.cs @@ -0,0 +1,4 @@ +namespace EventWebhook.Models +{ + public class GroupResubscribeEvent : GroupUnsubscribeEvent { } +} diff --git a/examples/eventwebhook/consumer/Src/EventWebhook/Models/GroupUnsubscribeEvent.cs b/examples/eventwebhook/consumer/Src/EventWebhook/Models/GroupUnsubscribeEvent.cs new file mode 100644 index 000000000..4763182f2 --- /dev/null +++ b/examples/eventwebhook/consumer/Src/EventWebhook/Models/GroupUnsubscribeEvent.cs @@ -0,0 +1,10 @@ +using Newtonsoft.Json; + +namespace EventWebhook.Models +{ + public class GroupUnsubscribeEvent : ClickEvent + { + [JsonProperty("asm_group_id")] + public int AsmGroupId { get; set; } + } +} diff --git a/examples/eventwebhook/consumer/Src/EventWebhook/Models/OpenEvent.cs b/examples/eventwebhook/consumer/Src/EventWebhook/Models/OpenEvent.cs new file mode 100644 index 000000000..81439ba38 --- /dev/null +++ b/examples/eventwebhook/consumer/Src/EventWebhook/Models/OpenEvent.cs @@ -0,0 +1,9 @@ +namespace EventWebhook.Models +{ + public class OpenEvent : Event + { + public string UserAgent { get; set; } + + public string IP { get; set; } + } +} diff --git a/examples/eventwebhook/consumer/Src/EventWebhook/Models/Pool.cs b/examples/eventwebhook/consumer/Src/EventWebhook/Models/Pool.cs new file mode 100644 index 000000000..0d534d6ce --- /dev/null +++ b/examples/eventwebhook/consumer/Src/EventWebhook/Models/Pool.cs @@ -0,0 +1,8 @@ +namespace EventWebhook.Models +{ + public class Pool + { + public string Name { get; set; } + public int Id { get; set; } + } +} diff --git a/examples/eventwebhook/consumer/Src/EventWebhook/Models/ProcessedEvent.cs b/examples/eventwebhook/consumer/Src/EventWebhook/Models/ProcessedEvent.cs new file mode 100644 index 000000000..648429470 --- /dev/null +++ b/examples/eventwebhook/consumer/Src/EventWebhook/Models/ProcessedEvent.cs @@ -0,0 +1,7 @@ +namespace EventWebhook.Models +{ + public class ProcessedEvent : Event + { + public Pool Pool { get; set; } + } +} diff --git a/examples/eventwebhook/consumer/Src/EventWebhook/Models/SpamReportEvent.cs b/examples/eventwebhook/consumer/Src/EventWebhook/Models/SpamReportEvent.cs new file mode 100644 index 000000000..0f1b6c738 --- /dev/null +++ b/examples/eventwebhook/consumer/Src/EventWebhook/Models/SpamReportEvent.cs @@ -0,0 +1,4 @@ +namespace EventWebhook.Models +{ + public class SpamReportEvent : Event { } +} diff --git a/examples/eventwebhook/consumer/Src/EventWebhook/Models/UnsubscribeEvent.cs b/examples/eventwebhook/consumer/Src/EventWebhook/Models/UnsubscribeEvent.cs new file mode 100644 index 000000000..350dbf09e --- /dev/null +++ b/examples/eventwebhook/consumer/Src/EventWebhook/Models/UnsubscribeEvent.cs @@ -0,0 +1,4 @@ +namespace EventWebhook.Models +{ + public class UnsubscribeEvent : Event { } +} diff --git a/examples/eventwebhook/consumer/Src/EventWebhook/Parser/EventConverter.cs b/examples/eventwebhook/consumer/Src/EventWebhook/Parser/EventConverter.cs new file mode 100644 index 000000000..7a5602525 --- /dev/null +++ b/examples/eventwebhook/consumer/Src/EventWebhook/Parser/EventConverter.cs @@ -0,0 +1,57 @@ +using EventWebhook.Models; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; + +namespace EventWebhook.Parser +{ + public class EventConverter : JsonConverter + { + private static readonly Dictionary> eventConverters = + new Dictionary>() + { + { EventType.Bounce, (json) => JsonConvert.DeserializeObject(json) }, + { EventType.Click, (json) => JsonConvert.DeserializeObject(json) }, + { EventType.Deferred, (json) => JsonConvert.DeserializeObject(json) }, + { EventType.Delivered, (json) => JsonConvert.DeserializeObject(json) }, + { EventType.Dropped, (json) => JsonConvert.DeserializeObject(json) }, + { EventType.GroupResubscribe, (json) => JsonConvert.DeserializeObject(json) }, + { EventType.GroupUnsubscribe, (json) => JsonConvert.DeserializeObject(json) }, + { EventType.Open, (json) => JsonConvert.DeserializeObject(json) }, + { EventType.Processed, (json) => JsonConvert.DeserializeObject(json) }, + { EventType.SpamReport, (json) => JsonConvert.DeserializeObject(json) }, + { EventType.Unsubscribe, (json) => JsonConvert.DeserializeObject(json) }, + }; + + private static Event DeserializeEvent(EventType type, string json) + { + if (!eventConverters.ContainsKey(type)) + { + throw new ArgumentOutOfRangeException($"Unknown event type: {type.ToString()}"); + } + + return eventConverters.GetValueOrDefault(type)(json); + } + + public override bool CanConvert(Type objectType) => typeof(Event) == objectType; + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var jsonObject = JObject.Load(reader); + + jsonObject.TryGetValue("event", StringComparison.OrdinalIgnoreCase, out JToken eventTypeJsonProperty); + + var eventType = (EventType)eventTypeJsonProperty.ToObject(typeof(EventType)); + + var webhookEvent = DeserializeEvent(eventType, jsonObject.ToString()); + + return webhookEvent; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + } +} diff --git a/examples/eventwebhook/consumer/Src/EventWebhook/Parser/EventParser.cs b/examples/eventwebhook/consumer/Src/EventWebhook/Parser/EventParser.cs new file mode 100644 index 000000000..31b09b5e7 --- /dev/null +++ b/examples/eventwebhook/consumer/Src/EventWebhook/Parser/EventParser.cs @@ -0,0 +1,46 @@ +using EventWebhook.Models; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; + +namespace EventWebhook.Parser +{ + public class EventParser + { + public static async Task> ParseAsync(string json) + { + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(json))) + { + return await ParseAsync(stream); + } + } + + public static async Task> ParseAsync(Stream stream) + { + var reader = new StreamReader(stream); + + var json = await reader.ReadToEndAsync(); + + return JsonConvert.DeserializeObject>(json, new EventConverter()); + } + + public static IEnumerable Parse(string json) + { + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(json))) + { + return Parse(stream); + } + } + + public static IEnumerable Parse(Stream stream) + { + var reader = new StreamReader(stream); + + var json = reader.ReadToEnd(); + + return JsonConvert.DeserializeObject>(json, new EventConverter()); + } + } +} diff --git a/examples/eventwebhook/consumer/Src/EventWebhook/Program.cs b/examples/eventwebhook/consumer/Src/EventWebhook/Program.cs new file mode 100644 index 000000000..3e5ac6ad8 --- /dev/null +++ b/examples/eventwebhook/consumer/Src/EventWebhook/Program.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace EventWebhook +{ + public class Program + { + public static void Main(string[] args) + { + CreateWebHostBuilder(args).Build().Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup(); + } +} diff --git a/examples/eventwebhook/consumer/Src/EventWebhook/Startup.cs b/examples/eventwebhook/consumer/Src/EventWebhook/Startup.cs new file mode 100644 index 000000000..b3e6f8b9a --- /dev/null +++ b/examples/eventwebhook/consumer/Src/EventWebhook/Startup.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.HttpsPolicy; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace EventWebhook +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + app.UseHsts(); + } + + app.UseHttpsRedirection(); + app.UseMvc(); + } + } +} diff --git a/examples/eventwebhook/consumer/Src/EventWebhook/Views/EventWebhook/Index.cshtml b/examples/eventwebhook/consumer/Src/EventWebhook/Views/EventWebhook/Index.cshtml new file mode 100644 index 000000000..af0b233c0 --- /dev/null +++ b/examples/eventwebhook/consumer/Src/EventWebhook/Views/EventWebhook/Index.cshtml @@ -0,0 +1,9 @@ + +@{ + ViewData["Title"] = "Index"; +} + +

You have successfully launched the server!

+

+ Check out the documentation on how to use this webhook to utilize the SendGrid Event webhook. +

diff --git a/examples/eventwebhook/consumer/Src/EventWebhook/appsettings.Development.json b/examples/eventwebhook/consumer/Src/EventWebhook/appsettings.Development.json new file mode 100644 index 000000000..e203e9407 --- /dev/null +++ b/examples/eventwebhook/consumer/Src/EventWebhook/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/examples/eventwebhook/consumer/Src/EventWebhook/appsettings.json b/examples/eventwebhook/consumer/Src/EventWebhook/appsettings.json new file mode 100644 index 000000000..def9159a7 --- /dev/null +++ b/examples/eventwebhook/consumer/Src/EventWebhook/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/examples/eventwebhook/consumer/Tests/EventWebhook.Tests/EventTests.cs b/examples/eventwebhook/consumer/Tests/EventWebhook.Tests/EventTests.cs new file mode 100644 index 000000000..94741a3e0 --- /dev/null +++ b/examples/eventwebhook/consumer/Tests/EventWebhook.Tests/EventTests.cs @@ -0,0 +1,376 @@ +using EventWebhook.Models; +using EventWebhook.Parser; +using Shouldly; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace EventWebhook.Tests +{ + public class EventTests + { + [Fact] + public async Task AllEvents() + { + var jsonStream = new MemoryStream(File.ReadAllBytes("TestData/events.json")); + + IEnumerable events = await EventParser.ParseAsync(jsonStream); + + events.Count().ShouldBeGreaterThanOrEqualTo(1); + } + + [Fact] + public async Task ProcessedEventTest() + { + var json = @" + [ + { + 'email': 'example@test.com', + 'timestamp': 1513299569, + 'smtp-id': '<14c5d75ce93.dfd.64b469@ismtpd-555>', + 'event': 'processed', + 'category': 'cat facts', + 'sg_event_id': 'sg_event_id', + 'sg_message_id': 'sg_message_id' + } + ] + "; + IEnumerable events = await EventParser.ParseAsync(json); + events.Count().ShouldBe(1); + var processedEvent = events.Single(); + processedEvent.Email.ShouldBe("example@test.com"); + processedEvent.Timestamp.ShouldBe(DateTime.UnixEpoch.AddSeconds(1513299569)); + processedEvent.SmtpId.ShouldBe("<14c5d75ce93.dfd.64b469@ismtpd-555>"); + processedEvent.EventType.ShouldBe(EventType.Processed); + processedEvent.Category.Value[0].ShouldBe("cat facts"); + processedEvent.SendGridEventId.ShouldBe("sg_event_id"); + processedEvent.SendGridMessageId.ShouldBe("sg_message_id"); + } + + [Fact] + public async Task DefferedEventTest() + { + var json = @" + [ + { + 'email': 'example@test.com', + 'timestamp': 1513299569, + 'smtp-id': '<14c5d75ce93.dfd.64b469@ismtpd-555>', + 'event': 'deferred', + 'category': 'cat facts', + 'sg_event_id': 'sg_event_id', + 'sg_message_id': 'sg_message_id', + 'response': '400 try again later', + 'attempt': '5' + } + ] + "; + IEnumerable events = await EventParser.ParseAsync(json); + events.Count().ShouldBe(1); + var defferedEvent = (DeferredEvent)events.Single(); + defferedEvent.Email.ShouldBe("example@test.com"); + defferedEvent.Timestamp.ShouldBe(DateTime.UnixEpoch.AddSeconds(1513299569)); + defferedEvent.SmtpId.ShouldBe("<14c5d75ce93.dfd.64b469@ismtpd-555>"); + defferedEvent.EventType.ShouldBe(EventType.Deferred); + defferedEvent.Category.Value[0].ShouldBe("cat facts"); + defferedEvent.SendGridEventId.ShouldBe("sg_event_id"); + defferedEvent.SendGridMessageId.ShouldBe("sg_message_id"); + defferedEvent.Response.ShouldBe("400 try again later"); + defferedEvent.Attempt.ShouldBe(5); + } + + [Fact] + public async Task DeleveredEventTest() + { + var json = @" + [ + { + 'email': 'example@test.com', + 'timestamp': 1513299569, + 'smtp-id': '<14c5d75ce93.dfd.64b469@ismtpd-555>', + 'event': 'delivered', + 'category': 'cat facts', + 'sg_event_id': 'sg_event_id', + 'sg_message_id': 'sg_message_id', + 'response': '200 OK' + } + ] + "; + IEnumerable events = await EventParser.ParseAsync(json); + events.Count().ShouldBe(1); + var defferedEvent = (DeliveredEvent)events.Single(); + defferedEvent.Email.ShouldBe("example@test.com"); + defferedEvent.Timestamp.ShouldBe(DateTime.UnixEpoch.AddSeconds(1513299569)); + defferedEvent.SmtpId.ShouldBe("<14c5d75ce93.dfd.64b469@ismtpd-555>"); + defferedEvent.EventType.ShouldBe(EventType.Delivered); + defferedEvent.Category.Value[0].ShouldBe("cat facts"); + defferedEvent.SendGridEventId.ShouldBe("sg_event_id"); + defferedEvent.SendGridMessageId.ShouldBe("sg_message_id"); + defferedEvent.Response.ShouldBe("200 OK"); + } + + [Fact] + public async Task OpenEventTest() + { + var json = @" + [ + { + 'email': 'example@test.com', + 'timestamp': 1513299569, + 'smtp-id': '<14c5d75ce93.dfd.64b469@ismtpd-555>', + 'event': 'open', + 'category': 'cat facts', + 'sg_event_id': 'sg_event_id', + 'sg_message_id': 'sg_message_id', + 'useragent': 'Mozilla/4.0 (compatible; MSIE 6.1; Windows XP; .NET CLR 1.1.4322; .NET CLR 2.0.50727)', + 'ip': '255.255.255.255' + } + ] + "; + IEnumerable events = await EventParser.ParseAsync(json); + events.Count().ShouldBe(1); + var openEvent = (OpenEvent)events.Single(); + openEvent.Email.ShouldBe("example@test.com"); + openEvent.Timestamp.ShouldBe(DateTime.UnixEpoch.AddSeconds(1513299569)); + openEvent.SmtpId.ShouldBe("<14c5d75ce93.dfd.64b469@ismtpd-555>"); + openEvent.EventType.ShouldBe(EventType.Open); + openEvent.Category.Value[0].ShouldBe("cat facts"); + openEvent.SendGridEventId.ShouldBe("sg_event_id"); + openEvent.SendGridMessageId.ShouldBe("sg_message_id"); + openEvent.UserAgent.ShouldBe("Mozilla/4.0 (compatible; MSIE 6.1; Windows XP; .NET CLR 1.1.4322; .NET CLR 2.0.50727)"); + openEvent.IP.ShouldBe("255.255.255.255"); + + } + + [Fact] + public async Task ClickEventTest() + { + var json = @" + [ + { + 'email': 'example@test.com', + 'timestamp': 1513299569, + 'smtp-id': '<14c5d75ce93.dfd.64b469@ismtpd-555>', + 'event': 'click', + 'category': 'cat facts', + 'sg_event_id': 'sg_event_id', + 'sg_message_id': 'sg_message_id', + 'useragent': 'Mozilla/4.0 (compatible; MSIE 6.1; Windows XP; .NET CLR 1.1.4322; .NET CLR 2.0.50727)', + 'ip': '255.255.255.255', + 'url': 'http://www.sendgrid.com/' + } + ] + "; + IEnumerable events = await EventParser.ParseAsync(json); + events.Count().ShouldBe(1); + var clickEvent = (ClickEvent)events.Single(); + clickEvent.Email.ShouldBe("example@test.com"); + clickEvent.Timestamp.ShouldBe(DateTime.UnixEpoch.AddSeconds(1513299569)); + clickEvent.SmtpId.ShouldBe("<14c5d75ce93.dfd.64b469@ismtpd-555>"); + clickEvent.EventType.ShouldBe(EventType.Click); + clickEvent.Category.Value[0].ShouldBe("cat facts"); + clickEvent.SendGridEventId.ShouldBe("sg_event_id"); + clickEvent.SendGridMessageId.ShouldBe("sg_message_id"); + clickEvent.UserAgent.ShouldBe("Mozilla/4.0 (compatible; MSIE 6.1; Windows XP; .NET CLR 1.1.4322; .NET CLR 2.0.50727)"); + clickEvent.IP.ShouldBe("255.255.255.255"); + clickEvent.Url.ToString().ShouldBe("http://www.sendgrid.com/"); + + + } + + [Fact] + public async Task BounceEventTest() + { + var json = @" + [ + { + 'email': 'example@test.com', + 'timestamp': 1513299569, + 'smtp-id': '<14c5d75ce93.dfd.64b469@ismtpd-555>', + 'event': 'bounce', + 'category': 'cat facts', + 'sg_event_id': 'sg_event_id', + 'sg_message_id': 'sg_message_id', + 'reason': '500 unknown recipient', + 'status': '5.0.0' + } + ] + "; + IEnumerable events = await EventParser.ParseAsync(json); + events.Count().ShouldBe(1); + var bounceEvent = (BounceEvent)events.Single(); + bounceEvent.Email.ShouldBe("example@test.com"); + bounceEvent.Timestamp.ShouldBe(DateTime.UnixEpoch.AddSeconds(1513299569)); + bounceEvent.SmtpId.ShouldBe("<14c5d75ce93.dfd.64b469@ismtpd-555>"); + bounceEvent.EventType.ShouldBe(EventType.Bounce); + bounceEvent.Category.Value[0].ShouldBe("cat facts"); + bounceEvent.SendGridEventId.ShouldBe("sg_event_id"); + bounceEvent.SendGridMessageId.ShouldBe("sg_message_id"); + bounceEvent.Reason.ShouldBe("500 unknown recipient"); + bounceEvent.Status.ShouldBe("5.0.0"); + } + + [Fact] + public async Task DroppedEventTest() + { + var json = @" + [ + { + 'email': 'example@test.com', + 'timestamp': 1513299569, + 'smtp-id': '<14c5d75ce93.dfd.64b469@ismtpd-555>', + 'event': 'dropped', + 'category': 'cat facts', + 'sg_event_id': 'sg_event_id', + 'sg_message_id': 'sg_message_id', + 'reason': 'Bounced Address', + 'status': '5.0.0' + } + ] + "; + IEnumerable events = await EventParser.ParseAsync(json); + events.Count().ShouldBe(1); + var droppedEvent = (DroppedEvent)events.Single(); + droppedEvent.Email.ShouldBe("example@test.com"); + droppedEvent.Timestamp.ShouldBe(DateTime.UnixEpoch.AddSeconds(1513299569)); + droppedEvent.SmtpId.ShouldBe("<14c5d75ce93.dfd.64b469@ismtpd-555>"); + droppedEvent.EventType.ShouldBe(EventType.Dropped); + droppedEvent.Category.Value[0].ShouldBe("cat facts"); + droppedEvent.SendGridEventId.ShouldBe("sg_event_id"); + droppedEvent.SendGridMessageId.ShouldBe("sg_message_id"); + droppedEvent.Reason.ShouldBe("Bounced Address"); + droppedEvent.Status.ShouldBe("5.0.0"); + } + + [Fact] + public async Task SpamReportEventTest() + { + var json = @" + [ + { + 'email': 'example@test.com', + 'timestamp': 1513299569, + 'smtp-id': '<14c5d75ce93.dfd.64b469@ismtpd-555>', + 'event': 'spamreport', + 'category': 'cat facts', + 'sg_event_id': 'sg_event_id', + 'sg_message_id': 'sg_message_id' + } + ] + "; + IEnumerable events = await EventParser.ParseAsync(json); + events.Count().ShouldBe(1); + var spamReportEvent = (SpamReportEvent)events.Single(); + spamReportEvent.Email.ShouldBe("example@test.com"); + spamReportEvent.Timestamp.ShouldBe(DateTime.UnixEpoch.AddSeconds(1513299569)); + spamReportEvent.SmtpId.ShouldBe("<14c5d75ce93.dfd.64b469@ismtpd-555>"); + spamReportEvent.EventType.ShouldBe(EventType.SpamReport); + spamReportEvent.Category.Value[0].ShouldBe("cat facts"); + spamReportEvent.SendGridEventId.ShouldBe("sg_event_id"); + spamReportEvent.SendGridMessageId.ShouldBe("sg_message_id"); + } + + [Fact] + public async Task UnsubscribeEventTest() + { + var json = @" + [ + { + 'email': 'example@test.com', + 'timestamp': 1513299569, + 'smtp-id': '<14c5d75ce93.dfd.64b469@ismtpd-555>', + 'event': 'unsubscribe', + 'category': 'cat facts', + 'sg_event_id': 'sg_event_id', + 'sg_message_id': 'sg_message_id' + } + ] + "; + IEnumerable events = await EventParser.ParseAsync(json); + events.Count().ShouldBe(1); + var spamReportEvent = (UnsubscribeEvent)events.Single(); + spamReportEvent.Email.ShouldBe("example@test.com"); + spamReportEvent.Timestamp.ShouldBe(DateTime.UnixEpoch.AddSeconds(1513299569)); + spamReportEvent.SmtpId.ShouldBe("<14c5d75ce93.dfd.64b469@ismtpd-555>"); + spamReportEvent.EventType.ShouldBe(EventType.Unsubscribe); + spamReportEvent.Category.Value[0].ShouldBe("cat facts"); + spamReportEvent.SendGridEventId.ShouldBe("sg_event_id"); + spamReportEvent.SendGridMessageId.ShouldBe("sg_message_id"); + } + + [Fact] + public async Task GroupUnsubscribeEventTest() + { + var json = @" + [ + { + 'email': 'example@test.com', + 'timestamp': 1513299569, + 'smtp-id': '<14c5d75ce93.dfd.64b469@ismtpd-555>', + 'event': 'group_unsubscribe', + 'category': 'cat facts', + 'sg_event_id': 'sg_event_id', + 'sg_message_id': 'sg_message_id', + 'useragent': 'Mozilla/4.0 (compatible; MSIE 6.1; Windows XP; .NET CLR 1.1.4322; .NET CLR 2.0.50727)', + 'ip': '255.255.255.255', + 'url': 'http://www.sendgrid.com/', + 'asm_group_id': 10 + } + ] + "; + IEnumerable events = await EventParser.ParseAsync(json); + events.Count().ShouldBe(1); + var groupUnSubscribeEvent = (GroupUnsubscribeEvent)events.Single(); + groupUnSubscribeEvent.Email.ShouldBe("example@test.com"); + groupUnSubscribeEvent.Timestamp.ShouldBe(DateTime.UnixEpoch.AddSeconds(1513299569)); + groupUnSubscribeEvent.SmtpId.ShouldBe("<14c5d75ce93.dfd.64b469@ismtpd-555>"); + groupUnSubscribeEvent.EventType.ShouldBe(EventType.GroupUnsubscribe); + groupUnSubscribeEvent.Category.Value[0].ShouldBe("cat facts"); + groupUnSubscribeEvent.SendGridEventId.ShouldBe("sg_event_id"); + groupUnSubscribeEvent.SendGridMessageId.ShouldBe("sg_message_id"); + groupUnSubscribeEvent.UserAgent.ShouldBe("Mozilla/4.0 (compatible; MSIE 6.1; Windows XP; .NET CLR 1.1.4322; .NET CLR 2.0.50727)"); + groupUnSubscribeEvent.IP.ShouldBe("255.255.255.255"); + groupUnSubscribeEvent.Url.ToString().ShouldBe("http://www.sendgrid.com/"); + groupUnSubscribeEvent.AsmGroupId.ShouldBe(10); + } + + [Fact] + public async Task GroupResubscribeEventTest() + { + var json = @" + [ + { + 'email': 'example@test.com', + 'timestamp': 1513299569, + 'smtp-id': '<14c5d75ce93.dfd.64b469@ismtpd-555>', + 'event': 'group_resubscribe', + 'category': 'cat facts', + 'sg_event_id': 'sg_event_id', + 'sg_message_id': 'sg_message_id', + 'useragent': 'Mozilla/4.0 (compatible; MSIE 6.1; Windows XP; .NET CLR 1.1.4322; .NET CLR 2.0.50727)', + 'ip': '255.255.255.255', + 'url': 'http://www.sendgrid.com/', + 'asm_group_id': 10 + } + ] + "; + IEnumerable events = await EventParser.ParseAsync(json); + events.Count().ShouldBe(1); + var groupUnSubscribeEvent = (GroupResubscribeEvent)events.Single(); + groupUnSubscribeEvent.Email.ShouldBe("example@test.com"); + groupUnSubscribeEvent.Timestamp.ShouldBe(DateTime.UnixEpoch.AddSeconds(1513299569)); + groupUnSubscribeEvent.SmtpId.ShouldBe("<14c5d75ce93.dfd.64b469@ismtpd-555>"); + groupUnSubscribeEvent.EventType.ShouldBe(EventType.GroupResubscribe); + groupUnSubscribeEvent.Category.Value[0].ShouldBe("cat facts"); + groupUnSubscribeEvent.SendGridEventId.ShouldBe("sg_event_id"); + groupUnSubscribeEvent.SendGridMessageId.ShouldBe("sg_message_id"); + groupUnSubscribeEvent.UserAgent.ShouldBe("Mozilla/4.0 (compatible; MSIE 6.1; Windows XP; .NET CLR 1.1.4322; .NET CLR 2.0.50727)"); + groupUnSubscribeEvent.IP.ShouldBe("255.255.255.255"); + groupUnSubscribeEvent.Url.ToString().ShouldBe("http://www.sendgrid.com/"); + groupUnSubscribeEvent.AsmGroupId.ShouldBe(10); + } + } +} diff --git a/examples/eventwebhook/consumer/Tests/EventWebhook.Tests/EventWebhook.Tests.csproj b/examples/eventwebhook/consumer/Tests/EventWebhook.Tests/EventWebhook.Tests.csproj new file mode 100644 index 000000000..2f6ed144e --- /dev/null +++ b/examples/eventwebhook/consumer/Tests/EventWebhook.Tests/EventWebhook.Tests.csproj @@ -0,0 +1,26 @@ + + + + netcoreapp2.1 + + false + + + + + + + + + + + + + + + + Always + + + + diff --git a/examples/eventwebhook/consumer/Tests/EventWebhook.Tests/TestData/events.json b/examples/eventwebhook/consumer/Tests/EventWebhook.Tests/TestData/events.json new file mode 100644 index 000000000..20f7de25e --- /dev/null +++ b/examples/eventwebhook/consumer/Tests/EventWebhook.Tests/TestData/events.json @@ -0,0 +1,119 @@ +[ + { + "email": "example@test.com", + "timestamp": 1513299569, + "smtp-id": "<14c5d75ce93.dfd.64b469@ismtpd-555>", + "event": "processed", + "category": "cat facts", + "sg_event_id": "sg_event_id", + "sg_message_id": "sg_message_id" + }, + { + "email": "example@test.com", + "timestamp": 1513299569, + "smtp-id": "<14c5d75ce93.dfd.64b469@ismtpd-555>", + "event": "deferred", + "category": "cat facts", + "sg_event_id": "sg_event_id", + "sg_message_id": "sg_message_id", + "response": "400 try again later", + "attempt": "5" + }, + { + "email": "example@test.com", + "timestamp": 1513299569, + "smtp-id": "<14c5d75ce93.dfd.64b469@ismtpd-555>", + "event": "delivered", + "category": "cat facts", + "sg_event_id": "sg_event_id", + "sg_message_id": "sg_message_id", + "response": "250 OK" + }, + { + "email": "example@test.com", + "timestamp": 1513299569, + "event": "open", + "category": "cat facts", + "sg_event_id": "sg_event_id", + "sg_message_id": "sg_message_id", + "useragent": "Mozilla/4.0 (compatible; MSIE 6.1; Windows XP; .NET CLR 1.1.4322; .NET CLR 2.0.50727)", + "ip": "255.255.255.255" + }, + { + "email": "example@test.com", + "timestamp": 1513299569, + "event": "click", + "category": "cat facts", + "sg_event_id": "sg_event_id", + "sg_message_id": "sg_message_id", + "useragent": "Mozilla/4.0 (compatible; MSIE 6.1; Windows XP; .NET CLR 1.1.4322; .NET CLR 2.0.50727)", + "ip": "255.255.255.255", + "url": "http://www.sendgrid.com/" + }, + { + "email": "example@test.com", + "timestamp": 1513299569, + "smtp-id": "<14c5d75ce93.dfd.64b469@ismtpd-555>", + "event": "bounce", + "category": "cat facts", + "sg_event_id": "sg_event_id", + "sg_message_id": "sg_message_id", + "reason": "500 unknown recipient", + "status": "5.0.0" + }, + { + "email": "example@test.com", + "timestamp": 1513299569, + "smtp-id": "<14c5d75ce93.dfd.64b469@ismtpd-555>", + "event": "dropped", + "category": "cat facts", + "sg_event_id": "sg_event_id", + "sg_message_id": "sg_message_id", + "reason": "Bounced Address", + "status": "5.0.0" + }, + { + "email": "example@test.com", + "timestamp": 1513299569, + "smtp-id": "<14c5d75ce93.dfd.64b469@ismtpd-555>", + "event": "spamreport", + "category": "cat facts", + "sg_event_id": "sg_event_id", + "sg_message_id": "sg_message_id" + }, + { + "email": "example@test.com", + "timestamp": 1513299569, + "smtp-id": "<14c5d75ce93.dfd.64b469@ismtpd-555>", + "event": "unsubscribe", + "category": "cat facts", + "sg_event_id": "sg_event_id", + "sg_message_id": "sg_message_id" + }, + { + "email": "example@test.com", + "timestamp": 1513299569, + "smtp-id": "<14c5d75ce93.dfd.64b469@ismtpd-555>", + "event": "group_unsubscribe", + "category": "cat facts", + "sg_event_id": "sg_event_id", + "sg_message_id": "sg_message_id", + "useragent": "Mozilla/4.0 (compatible; MSIE 6.1; Windows XP; .NET CLR 1.1.4322; .NET CLR 2.0.50727)", + "ip": "255.255.255.255", + "url": "http://www.sendgrid.com/", + "asm_group_id": 10 + }, + { + "email": "example@test.com", + "timestamp": 1513299569, + "smtp-id": "<14c5d75ce93.dfd.64b469@ismtpd-555>", + "event": "group_resubscribe", + "category": "cat facts", + "sg_event_id": "sg_event_id", + "sg_message_id": "sg_message_id", + "useragent": "Mozilla/4.0 (compatible; MSIE 6.1; Windows XP; .NET CLR 1.1.4322; .NET CLR 2.0.50727)", + "ip": "000.000.000.000", + "url": "http://www.sendgrid.com/", + "asm_group_id": 10 + } +] \ No newline at end of file