├── .config └── dotnet-tools.json ├── .csharpierrc ├── .editorconfig ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ └── on-update.yml ├── .gitignore ├── AUTHORS.md ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── build ├── Program.cs └── build.csproj ├── docker ├── Dockerfile ├── build_and_deploy.sh └── readme.md ├── examples ├── BCC │ ├── BCC.csproj │ └── Program.cs ├── CC │ ├── CC.csproj │ └── Program.cs ├── SendInline │ ├── Program.cs │ └── SendInline.csproj └── SendTemplate │ ├── Order.cs │ ├── Program.cs │ └── SendTemplate.csproj └── src ├── SparkPost.Acceptance ├── ClientSteps.cs ├── MessageEvents.feature ├── MessageEventsSteps.cs ├── Metrics.feature ├── MetricsSteps.cs ├── RecipientListSteps.cs ├── RecipientLists.feature ├── ResponseSteps.cs ├── SparkPost.Acceptance.csproj ├── Suppressions.feature ├── SuppressionsSteps.cs ├── TransmissionSteps.cs ├── Transmissions.feature └── testtextfile.txt ├── SparkPost.Tests ├── CastAsExtensions.cs ├── ClientTests.cs ├── DataMapperTests.cs ├── FileTests.cs ├── ListMessageEventsResponseTests.cs ├── MessageEventsQueryTests.cs ├── MetricsQueryTests.cs ├── RelayWebhookTests.cs ├── RequestMethodFinderTests.cs ├── RequestMethods │ ├── DeleteTests.cs │ ├── GetTests.cs │ ├── PostTests.cs │ └── PutTests.cs ├── RequestSenders │ └── RequestSenderTests.cs ├── SparkPost.Tests.csproj ├── SubaccountTest.cs ├── SuppressionTests.cs ├── TransmissionTests.cs ├── UserAgentTests.cs ├── Utilities │ └── SnakeCaseTests.cs └── WebhookTests.cs ├── SparkPost.sln └── SparkPost ├── Address.cs ├── Attachment.cs ├── Attributes.cs ├── BounceCategory.cs ├── BounceClass.cs ├── BounceClassDetails.cs ├── BounceClassesDetails.cs ├── CcHandling.cs ├── Client.cs ├── Content.cs ├── CreateSendingDomainResponse.cs ├── CreateSubaccountResponse.cs ├── CreateTemplateResponse.cs ├── DataMapper.cs ├── Dkim.cs ├── Dns.cs ├── EmailValidationResponse.cs ├── Events.cs ├── File.cs ├── GetMetricsResourceResponse.cs ├── GetMetricsResponse.cs ├── GetSendingDomainResponse.cs ├── IClient.cs ├── IMessageEvents.cs ├── IMetrics.cs ├── IRecipientLists.cs ├── IRecipientValidation.cs ├── IRequestMethod.cs ├── ISendingDomains.cs ├── ISubaccounts.cs ├── ISuppressions.cs ├── ITemplates.cs ├── ITransmissions.cs ├── IValueMapper.cs ├── InboundDomain.cs ├── InboundDomainResponse.cs ├── InboundDomains.cs ├── InlineImage.cs ├── LeftRight.cs ├── ListInboundDomainResponse.cs ├── ListMessageEventsResponse.cs ├── ListRelayWebhookResponse.cs ├── ListSendingDomainResponse.cs ├── ListSubaccountResponse.cs ├── ListSuppressionResponse.cs ├── ListTransmissionResponse.cs ├── ListWebhookResponse.cs ├── MessageEvent.cs ├── MessageEventSampleResponse.cs ├── MessageEventType.cs ├── MessageEvents.cs ├── MessageEventsQuery.cs ├── Metrics.cs ├── MetricsField.cs ├── MetricsQuery.cs ├── MetricsResourceQuery.cs ├── Options.cs ├── PageLink.cs ├── Recipient.cs ├── RecipientList.cs ├── RecipientLists.cs ├── RecipientType.cs ├── RecipientValidation.cs ├── RelayWebhook.cs ├── RelayWebhooks.cs ├── Request.cs ├── RequestMethodFinder.cs ├── RequestMethods ├── Delete.cs ├── Get.cs ├── Post.cs ├── Put.cs ├── PutAndPostAreTheSame.cs └── RequestMethod.cs ├── RequestSenders └── RequestSender.cs ├── Response.cs ├── ResponseException.cs ├── RetrieveEmailValidationResponse.cs ├── RetrieveRecipientListsResponse.cs ├── RetrieveRelayWebhookResponse.cs ├── RetrieveTemplateResponse.cs ├── RetrieveTemplatesResponse.cs ├── RetrieveTransmissionResponse.cs ├── RetrieveWebhookResponse.cs ├── SendRecipientListsResponse.cs ├── SendTransmissionResponse.cs ├── SendingDomain.cs ├── SendingDomainStatus.cs ├── SendingDomains.cs ├── SparkPost.csproj ├── Subaccount.cs ├── SubaccountCreate.cs ├── SubaccountStatus.cs ├── SubaccountUpdate.cs ├── Subaccounts.cs ├── Suppression.cs ├── Suppressions.cs ├── SuppressionsQuery.cs ├── Template.cs ├── TemplateContent.cs ├── TemplateOptions.cs ├── Templates.cs ├── Transmission.cs ├── Transmissions.cs ├── UpdateRecipientListResponse.cs ├── UpdateSendingDomainResponse.cs ├── UpdateSubaccountResponse.cs ├── UpdateSuppressionResponse.cs ├── Utilities ├── Jsonification.cs ├── MailMessageMapping.cs ├── SnakeCase.cs └── StringHelper.cs ├── ValueMappers ├── AnonymousValueMapper.cs ├── BooleanValueMapper.cs ├── DateTimeOffsetValueMapper.cs ├── DateTimeValueMapper.cs ├── EnumValueMapper.cs ├── EnumerableValueMapper.cs ├── MapASetOfItemsUsingToDictionary.cs ├── MapASingleItemUsingToDictionary.cs ├── StringObjectDictionaryValueMapper.cs └── StringStringDictionaryValueMapper.cs ├── VerifySendingDomain.cs ├── VerifySendingDomainResponse.cs ├── VerifySendingDomainStatus.cs ├── Webhook.cs └── Webhooks.cs /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "csharpier": { 6 | "version": "0.18.0", 7 | "commands": [ 8 | "dotnet-csharpier" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /.csharpierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 150, 3 | "preprocessorSymbolSets": ["", "DEBUG", "DEBUG,CODE_STYLE"] 4 | } 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Declare files that will always have CRLF line endings on checkout. 2 | * text eol=lf 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" # search for actions - there are other options available 4 | directory: "/" # search in .github/workflows under root `/` 5 | schedule: 6 | interval: "weekly" # check for action update every week 7 | -------------------------------------------------------------------------------- /.github/workflows/on-update.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Build and Test 3 | 4 | on: [push] 5 | 6 | jobs: 7 | build-and-run-tests: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | - uses: actions/setup-dotnet@v4 12 | with: 13 | dotnet-version: 6.0.301 14 | - run: dotnet run -p build/build.csproj 15 | - uses: actions/upload-artifact@v4 16 | with: 17 | name: SparkPost.nupkg 18 | path: artifacts/* 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | 24 | # Visual Studio 2015 cache/options directory 25 | .vs/ 26 | # Uncomment if you have tasks that create the project's static files in wwwroot 27 | #wwwroot/ 28 | 29 | # MSTest test Results 30 | [Tt]est[Rr]esult*/ 31 | [Bb]uild[Ll]og.* 32 | 33 | # NUNIT 34 | *.VisualState.xml 35 | TestResult.xml 36 | 37 | # Build Results of an ATL Project 38 | [Dd]ebugPS/ 39 | [Rr]eleasePS/ 40 | dlldata.c 41 | 42 | # DNX 43 | project.lock.json 44 | artifacts/ 45 | 46 | *_i.c 47 | *_p.c 48 | *_i.h 49 | *.ilk 50 | *.meta 51 | *.obj 52 | *.pch 53 | *.pdb 54 | *.pgc 55 | *.pgd 56 | *.rsp 57 | *.sbr 58 | *.tlb 59 | *.tli 60 | *.tlh 61 | *.tmp 62 | *.tmp_proj 63 | *.log 64 | *.vspscc 65 | *.vssscc 66 | .builds 67 | *.pidb 68 | *.svclog 69 | *.scc 70 | 71 | # Chutzpah Test files 72 | _Chutzpah* 73 | 74 | # Visual C++ cache files 75 | ipch/ 76 | *.aps 77 | *.ncb 78 | *.opendb 79 | *.opensdf 80 | *.sdf 81 | *.cachefile 82 | 83 | # Visual Studio profiler 84 | *.psess 85 | *.vsp 86 | *.vspx 87 | *.sap 88 | 89 | # TFS 2012 Local Workspace 90 | $tf/ 91 | 92 | # Guidance Automation Toolkit 93 | *.gpState 94 | 95 | # ReSharper is a .NET coding add-in 96 | _ReSharper*/ 97 | *.[Rr]e[Ss]harper 98 | *.DotSettings.user 99 | 100 | # JustCode is a .NET coding add-in 101 | .JustCode 102 | 103 | # TeamCity is a build add-in 104 | _TeamCity* 105 | 106 | # DotCover is a Code Coverage Tool 107 | *.dotCover 108 | 109 | # NCrunch 110 | _NCrunch_* 111 | .*crunch*.local.xml 112 | nCrunchTemp_* 113 | 114 | # MightyMoose 115 | *.mm.* 116 | AutoTest.Net/ 117 | 118 | # Web workbench (sass) 119 | .sass-cache/ 120 | 121 | # Installshield output folder 122 | [Ee]xpress/ 123 | 124 | # DocProject is a documentation generator add-in 125 | DocProject/buildhelp/ 126 | DocProject/Help/*.HxT 127 | DocProject/Help/*.HxC 128 | DocProject/Help/*.hhc 129 | DocProject/Help/*.hhk 130 | DocProject/Help/*.hhp 131 | DocProject/Help/Html2 132 | DocProject/Help/html 133 | 134 | # Click-Once directory 135 | publish/ 136 | 137 | # Publish Web Output 138 | *.[Pp]ublish.xml 139 | *.azurePubxml 140 | # TODO: Comment the next line if you want to checkin your web deploy settings 141 | # but database connection strings (with potential passwords) will be unencrypted 142 | *.pubxml 143 | *.publishproj 144 | 145 | # NuGet Packages 146 | *.nupkg 147 | # The packages folder can be ignored because of Package Restore 148 | **/packages/* 149 | # except build/, which is used as an MSBuild target. 150 | !**/packages/build/ 151 | # Uncomment if necessary however generally it will be regenerated when needed 152 | #!**/packages/repositories.config 153 | # NuGet v3's project.json files produces more ignoreable files 154 | *.nuget.props 155 | *.nuget.targets 156 | 157 | # Microsoft Azure Build Output 158 | csx/ 159 | *.build.csdef 160 | 161 | # Microsoft Azure Emulator 162 | ecf/ 163 | rcf/ 164 | 165 | # Microsoft Azure ApplicationInsights config file 166 | ApplicationInsights.config 167 | 168 | # Windows Store app package directory 169 | AppPackages/ 170 | BundleArtifacts/ 171 | 172 | # Visual Studio cache files 173 | # files ending in .cache can be ignored 174 | *.[Cc]ache 175 | # but keep track of directories ending in .cache 176 | !*.[Cc]ache/ 177 | 178 | # Others 179 | ClientBin/ 180 | ~$* 181 | *~ 182 | *.dbmdl 183 | *.dbproj.schemaview 184 | *.pfx 185 | *.publishsettings 186 | node_modules/ 187 | orleans.codegen.cs 188 | 189 | # RIA/Silverlight projects 190 | Generated_Code/ 191 | 192 | # Backup & report files from converting an old project file 193 | # to a newer Visual Studio version. Backup files are not needed, 194 | # because we have git ;-) 195 | _UpgradeReport_Files/ 196 | Backup*/ 197 | UpgradeLog*.XML 198 | UpgradeLog*.htm 199 | 200 | # SQL Server files 201 | *.mdf 202 | *.ldf 203 | 204 | # Business Intelligence projects 205 | *.rdl.data 206 | *.bim.layout 207 | *.bim_*.settings 208 | 209 | # Microsoft Fakes 210 | FakesAssemblies/ 211 | 212 | # GhostDoc plugin setting file 213 | *.GhostDoc.xml 214 | 215 | # Node.js Tools for Visual Studio 216 | .ntvs_analysis.dat 217 | 218 | # Visual Studio 6 build log 219 | *.plg 220 | 221 | # Visual Studio 6 workspace options file 222 | *.opt 223 | 224 | # Visual Studio LightSwitch build output 225 | **/*.HTMLClient/GeneratedArtifacts 226 | **/*.DesktopClient/GeneratedArtifacts 227 | **/*.DesktopClient/ModelManifest.xml 228 | **/*.Server/GeneratedArtifacts 229 | **/*.Server/ModelManifest.xml 230 | _Pvt_Extensions 231 | 232 | # Paket dependency manager 233 | .paket/paket.exe 234 | 235 | # FAKE - F# Make 236 | .fake/ 237 | .DS_Store 238 | **/.idea/ 239 | *.feature.cs 240 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | ## Core contributors 2 | 3 | * Darren Cauthon (http://github.com/darrencauthon) 4 | * Stefan de Vogelaere (http://githun.com/stefandevo) - Xamarin Support 5 | * Geoffrey Ballenden (http://githun.com/ZA1) 6 | * Kiril Sidun (https://github.com/kirilsi) 7 | * Adam Hathcock (https://github.com/adamhathcock) 8 | * Combeenation (https://github.com/Combeenation) 9 | * ADD YOURSELF HERE (and link to your github page) 10 | 11 | ## Patches and suggestions 12 | 13 | * Chris Charabaruk (https://github.com/coldacid) - SPEQit 14 | * ADD YOURSELF HERE (and link to your github page) 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | This project adheres to [Semantic Versioning](http://semver.org/). 3 | 4 | All releases with documentation are posted to the Github Releases page: 5 | 6 | https://github.com/SparkPost/csharp-sparkpost/releases 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to csharp-sparkpost 2 | 3 | The following is a set of guidelines for contributing to csharp-sparkpost, 4 | which is hosted by [Darren Cauthon](https://github.com/darrencauthon) on GitHub. 5 | These are just guidelines, not rules, use your best judgment and feel free to 6 | propose changes to this document in a pull request. 7 | 8 | ## Submitting Issues 9 | 10 | * Before logging an issue, please [search existing issues](https://github.com/darrencauthon/csharp-sparkpost/issues?q=is%3Aissue+is%3Aopen) first. 11 | 12 | * You can create an issue [here](https://github.com/darrencauthon/csharp-sparkpost/issues/new). Please include the library version number and as much detail as possible in your report. 13 | 14 | ## Local Development 15 | 16 | 1. Fork this repo 17 | 1. Clone your fork 18 | 1. Write some code! 19 | 1. Please follow the pull request submission steps in the next section 20 | 21 | ## Contribution Steps 22 | 23 | To contribute to csharp-sparkpost: 24 | 25 | 1. Create a new branch named after the issue you’ll be fixing (include the issue number as the branch name, example: Issue in GH is #8 then the branch name should be ISSUE-8)) 26 | 1. Write corresponding tests and code (only what is needed to satisfy the issue and tests please) 27 | * Include your tests in the 'test' directory in an appropriate test file 28 | * Write code to satisfy the tests 29 | 1. Ensure automated tests pass 30 | 1. Submit a new Pull Request applying your feature/fix branch to the `master` branch 31 | 32 | ### Releasing 33 | 34 | TODO: add instructions for publishing to https://www.nuget.org/ 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # C# Library for [SparkPost](https://www.sparkpost.com) 2 | 3 | A C# package for the [SparkPost API](https://developers.sparkpost.com/api). Xamarin.iOS and Xamarin.Android support provided in the Portable Package (PCL Profile7). 4 | 5 | ## Installation 6 | 7 | To install via NuGet, run the following command in the [Package Manager Console](http://docs.nuget.org/consume/package-manager-console): 8 | 9 | ``` 10 | PM> Install-Package SparkPost 11 | ``` 12 | 13 | Alternatively, you can get the latest dll from the releases tab. You can also download this code and compile it yourself. 14 | 15 | ## Usage 16 | 17 | #### Special Note about ```Async``` 18 | 19 | By default, this library uses .Net 4.5's ```Async``` functionality for better performance ([read more here](https://msdn.microsoft.com/en-us/library/hh191443.aspx)). This requires knowledge and execution 20 | of the async/await behavior in C#. If you're noticing what seems to be weird behavior, or MVC action hangs, 21 | or anything of that nature, just switch your client to ```Sync``` and you'll get the expected (but blocking) behavior. 22 | 23 | ```c# 24 | client.CustomSettings.SendingMode = SendingModes.Sync; 25 | client.Transmissions.Send(transmission); // now this call will be made synchronously 26 | 27 | client.CustomSettings.SendingMode = SendingModes.Async; 28 | client.Transmissions.Send(transmission); // now this call will be made asynchronously 29 | ``` 30 | 31 | 32 | 33 | ### Transmissions 34 | 35 | To send an email: 36 | 37 | ```c# 38 | var transmission = new Transmission(); 39 | transmission.Content.From.Email = "testing@sparkpostbox.com"; 40 | transmission.Content.Subject = "Oh hey!"; 41 | transmission.Content.Text = "Testing SparkPost - the world\'s most awesomest email service!"; 42 | transmission.Content.Html = "

Testing SparkPost - the world\'s most awesomest email service!

"; 43 | 44 | var recipient = new Recipient 45 | { 46 | Address = new Address { Email = "my@email.com" } 47 | }; 48 | transmission.Recipients.Add(recipient); 49 | 50 | var client = new Client(""); 51 | client.Transmissions.Send(transmission); 52 | // or client.Transmissions.Send(transmission).Wait(); 53 | 54 | ``` 55 | 56 | To send a template email: 57 | 58 | ```c# 59 | var transmission = new Transmission(); 60 | transmission.Content.TemplateId = "my-template-id"; 61 | transmission.Content.From.Email = "testing@sparkpostbox.com"; 62 | 63 | transmission.SubstitutionData["first_name"] = "John"; 64 | transmission.SubstitutionData["last_name"] = "Doe"; 65 | 66 | var orders = new List 67 | { 68 | new Order { OrderId = "1", Total = 101 }, 69 | new Order { OrderId = "2", Total = 304 } 70 | }; 71 | 72 | // you can pass more complicated data, so long as it 73 | // can be parsed easily to JSON 74 | transmission.SubstitutionData["orders"] = orders; 75 | 76 | var recipient = new Recipient 77 | { 78 | Address = new Address { Email = "my@email.com" } 79 | }; 80 | transmission.Recipients.Add(recipient); 81 | 82 | var client = new Client("MY_API_KEY"); 83 | client.Transmissions.Send(transmission); 84 | // or client.Transmissions.Send(transmission).Wait(); 85 | 86 | ``` 87 | 88 | ### Sub Accounts 89 | 90 | You can use the client to send emails through a sub account by passing the ```subAccountId``` to your client. 91 | 92 | ```c# 93 | client = new Client(YOUR_API_KEY, YOUR_SUB_ACCOUNT_ID); 94 | // now the emails will be processed through your sub account 95 | ``` 96 | 97 | ### Suppression List 98 | 99 | The suppression list are users who have opted-out of your emails. To retrieve this list: 100 | 101 | ```c# 102 | var client = new Client("MY_API_KEY"); 103 | 104 | client.Suppressions.List(); // returns a list of 105 | 106 | client.Suppressions.List(new { limit = 3 }); // it accepts an anonymous type for filters 107 | 108 | client.Suppressions.List(new SuppressionQuery()); // a SuppressionQuery is also allowed for typed help 109 | ``` 110 | 111 | To add email addresses to the list: 112 | 113 | ```c# 114 | var client = new Client("MY_API_KEY"); 115 | 116 | var item1 = new Suppression { Email = "testing@testing.com", NonTransactional = true }; 117 | var item2 = new Suppression { Email = "testing2@testing.com", Description = "testing" }; 118 | 119 | client.Suppressions.CreateOrUpdate(new []{ item1, item2 }); 120 | ``` 121 | 122 | To delete email addresses from the list: 123 | 124 | ```c# 125 | var client = new Client("MY_API_KEY"); 126 | 127 | client.Suppressions.Delete("testing@testing.com"); 128 | ``` 129 | 130 | To retrieve details about an email address on (or not on) the list: 131 | 132 | ```c# 133 | var client = new Client("MY_API_KEY"); 134 | 135 | client.Suppressions.Retrieve("testing@testing.com"); 136 | ``` 137 | 138 | ### Setting the API hostname 139 | ```c# 140 | var client = new Client("MY_API_KEY", "https://api.eu.sparkpost.com"); 141 | ``` 142 | 143 | ### Contribute 144 | 145 | We welcome your contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to help out. 146 | 147 | ### Change Log 148 | 149 | [See ChangeLog here](CHANGELOG.md) 150 | -------------------------------------------------------------------------------- /build/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Runtime.InteropServices; 5 | using GlobExpressions; 6 | using static Bullseye.Targets; 7 | using static SimpleExec.Command; 8 | 9 | const string Clean = "clean"; 10 | const string Format = "format"; 11 | const string Build = "build"; 12 | const string Test = "test"; 13 | const string Publish = "publish"; 14 | 15 | Target( 16 | Clean, 17 | ForEach("**/bin", "**/obj"), 18 | dir => 19 | { 20 | IEnumerable GetDirectories(string d) 21 | { 22 | return Glob.Directories(".", d); 23 | } 24 | 25 | void RemoveDirectory(string d) 26 | { 27 | if (Directory.Exists(d)) 28 | { 29 | Console.WriteLine(d); 30 | Directory.Delete(d, true); 31 | } 32 | } 33 | 34 | foreach (var d in GetDirectories(dir)) 35 | { 36 | RemoveDirectory(d); 37 | } 38 | } 39 | ); 40 | 41 | Target( 42 | Format, 43 | () => 44 | { 45 | Run("dotnet", "tool restore", "."); 46 | Run("dotnet", "csharpier --check .", "."); 47 | } 48 | ); 49 | 50 | Target( 51 | Build, 52 | DependsOn(Format), 53 | () => 54 | { 55 | Run("dotnet", "build src/SparkPost.sln -c Release"); 56 | } 57 | ); 58 | 59 | Target( 60 | Test, 61 | DependsOn(Build), 62 | () => 63 | { 64 | Run("dotnet", "test src/SparkPost.sln"); 65 | } 66 | ); 67 | 68 | Target( 69 | Publish, 70 | DependsOn(Test), 71 | () => 72 | { 73 | Run("dotnet", "pack src/SparkPost/SparkPost.csproj -c Release -o artifacts/"); 74 | } 75 | ); 76 | 77 | Target("default", DependsOn(Publish), () => Console.WriteLine("Done!")); 78 | 79 | await RunTargetsAndExitAsync(args); 80 | -------------------------------------------------------------------------------- /build/build.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mono 2 | MAINTAINER Darren Cauthon 3 | 4 | RUN apt-get update 5 | RUN apt-get install -y wget git dos2unix vim zip 6 | 7 | RUN nuget update -self 8 | 9 | ENV MONO_THREADS_PER_CPU 2000 10 | 11 | WORKDIR / 12 | 13 | RUN git clone https://github.com/SparkPost/csharp-sparkpost.git 14 | 15 | WORKDIR /csharp-sparkpost/src 16 | 17 | ADD build_and_deploy.sh /csharp-sparkpost/src 18 | RUN chmod 777 build_and_deploy.sh 19 | RUN dos2unix build_and_deploy.sh 20 | 21 | CMD /csharp-sparkpost/src/build_and_deploy.sh 22 | -------------------------------------------------------------------------------- /docker/build_and_deploy.sh: -------------------------------------------------------------------------------- 1 | git pull origin master 2 | 3 | nuget restore 4 | xbuild /p:Configuration=Release 5 | 6 | nuget pack SparkPost/SparkPost.nuspec -Prop Configuration=Release 7 | 8 | PACKAGE=$(ls *.nupkg) 9 | 10 | # This will unzip the nupkg, then zip it back up. 11 | # This is done to some issue with the nuget command 12 | # line app creating nupkg files that cannot be 13 | # used by Visual Studio. 14 | # https://github.com/NuGet/Home/issues/2833 15 | mv $PACKAGE file.zip 16 | mkdir stuff 17 | cd stuff 18 | unzip ../file.zip 19 | rm ../file.zip 20 | zip -r ../file.zip * 21 | cd .. 22 | mv file.zip $PACKAGE 23 | 24 | nuget push $PACKAGE $APIKEY -s nuget.org 25 | -------------------------------------------------------------------------------- /docker/readme.md: -------------------------------------------------------------------------------- 1 | ## Build & Deploy via Docker & Nuget 2 | 3 | This docker container will pull the latest version of this library, create a nuget package, and deploy it to Nuget. 4 | 5 | The only thing is needs is a valid Nuget API key, from someone that has rights to publish the package to Nuget. 6 | 7 | ``` 8 | cd docker 9 | docker build . # this will produce a hash key, say 12345678 10 | docker run -e "APIKEY=your_nuget_key_here" 12345678 11 | ``` 12 | 13 | This container has been registered on Docker Hub, and can be run like so: 14 | 15 | ``` 16 | docker run -e "APIKEY=your_nuget_key_here" darrencauthon/csharp-sparkpost 17 | ``` 18 | -------------------------------------------------------------------------------- /examples/BCC/BCC.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net6.0 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/BCC/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using SparkPost; 3 | 4 | var apikey = "YOUR_API_KEY"; 5 | var fromAddr = "from-csharp@yourdomain.com"; 6 | var toAddr = "to@you.com"; 7 | var ccAddr = "cc@them.com"; 8 | var bccAddr = "bcc@sneaky.com"; 9 | 10 | var trans = new Transmission(); 11 | 12 | var to = new Recipient { Address = new Address { Email = toAddr } }; 13 | trans.Recipients.Add(to); 14 | 15 | var cc = new Recipient 16 | { 17 | Address = new Address { Email = ccAddr, HeaderTo = toAddr } 18 | }; 19 | trans.Recipients.Add(cc); 20 | 21 | var bcc = new Recipient 22 | { 23 | Address = new Address { Email = bccAddr, HeaderTo = toAddr } 24 | }; 25 | trans.Recipients.Add(bcc); 26 | 27 | trans.Content.From.Email = fromAddr; 28 | trans.Content.Subject = "SparkPost BCC / CC example"; 29 | trans.Content.Text = "This message was sent To 1 recipient, 1 recipient was CC'd and 1 sneaky recipient was BCC'd."; 30 | trans.Content.Headers.Add("CC", ccAddr); 31 | 32 | Console.Write("Sending BCC / CC sample mail..."); 33 | 34 | var client = new Client(apikey); 35 | 36 | var response = await client.Transmissions.Send(trans); 37 | 38 | Console.WriteLine("done"); 39 | -------------------------------------------------------------------------------- /examples/CC/CC.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net6.0 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/CC/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using SparkPost; 3 | 4 | var apikey = "YOUR_API_KEY"; 5 | var fromAddr = "from-csharp@yourdomain.com"; 6 | var toAddr = "to@you.com"; 7 | var ccAddr = "cc@them.com"; 8 | 9 | var trans = new Transmission(); 10 | 11 | var to = new Recipient { Address = new Address { Email = toAddr } }; 12 | trans.Recipients.Add(to); 13 | 14 | var cc = new Recipient 15 | { 16 | Address = new Address { Email = ccAddr, HeaderTo = toAddr } 17 | }; 18 | trans.Recipients.Add(cc); 19 | 20 | trans.Content.From.Email = fromAddr; 21 | trans.Content.Subject = "SparkPost CC example"; 22 | trans.Content.Text = "This message was sent To 1 recipient and 1 recipient was CC'd."; 23 | trans.Content.Headers.Add("CC", ccAddr); 24 | 25 | Console.Write("Sending CC sample mail..."); 26 | 27 | var client = new Client(apikey); 28 | 29 | var response = await client.Transmissions.Send(trans); 30 | 31 | Console.WriteLine("done"); 32 | -------------------------------------------------------------------------------- /examples/SendInline/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using SparkPost; 4 | 5 | var apikey = "YOUR_API_KEY"; 6 | var fromAddr = "from-csharp@yourdomain.com"; 7 | var toAddr = "to@you.com"; 8 | 9 | var trans = new Transmission(); 10 | 11 | var to = new Recipient 12 | { 13 | Address = new Address { Email = toAddr }, 14 | SubstitutionData = new Dictionary { { "firstName", "Jane" } } 15 | }; 16 | 17 | trans.Recipients.Add(to); 18 | 19 | trans.SubstitutionData["firstName"] = "Oh Ye Of Little Name"; 20 | 21 | trans.Content.From.Email = fromAddr; 22 | trans.Content.Subject = "SparkPost online content example"; 23 | trans.Content.Text = "Greetings {{firstName or 'recipient'}}\nHello from C# land."; 24 | trans.Content.Html = "

Greetings {{firstName or 'recipient'}}

Hello from C# land.

"; 25 | 26 | Console.Write("Sending mail..."); 27 | 28 | var client = new Client(apikey); 29 | 30 | var response = await client.Transmissions.Send(trans); 31 | 32 | Console.WriteLine("done"); 33 | -------------------------------------------------------------------------------- /examples/SendInline/SendInline.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net6.0 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/SendTemplate/Order.cs: -------------------------------------------------------------------------------- 1 | namespace SendTemplate; 2 | 3 | public record Order(int OrderId, string Desc, int Total); 4 | -------------------------------------------------------------------------------- /examples/SendTemplate/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using SendTemplate; 4 | using SparkPost; 5 | 6 | var fromAddr = "from-csharp@yourdomain.com"; 7 | var toAddr = "to@you.com"; 8 | var apikey = "YOUR_API_KEY"; 9 | 10 | var trans = new Transmission(); 11 | 12 | var to = new Recipient 13 | { 14 | Address = new Address { Email = toAddr }, 15 | SubstitutionData = new Dictionary { { "firstName", "Jane" } } 16 | }; 17 | 18 | trans.Recipients.Add(to); 19 | trans.SubstitutionData["title"] = "Dr"; 20 | trans.SubstitutionData["firstName"] = "Rick"; 21 | trans.SubstitutionData["lastName"] = "Sanchez"; 22 | trans.SubstitutionData["orders"] = new List { new(101, "Tomatoes", 5), new(271, "Entropy", 314) }; 23 | 24 | trans.Content.From.Email = fromAddr; 25 | trans.Content.TemplateId = "orderSummary"; 26 | 27 | Console.Write("Sending mail..."); 28 | 29 | var client = new Client(apikey); 30 | 31 | var response = await client.Transmissions.Send(trans); 32 | 33 | Console.WriteLine("done"); 34 | -------------------------------------------------------------------------------- /examples/SendTemplate/SendTemplate.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net6.0 4 | Exe 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/SparkPost.Acceptance/ClientSteps.cs: -------------------------------------------------------------------------------- 1 | using TechTalk.SpecFlow; 2 | 3 | namespace SparkPost.Acceptance 4 | { 5 | [Binding] 6 | public class ClientSteps 7 | { 8 | private readonly ScenarioContext _scenarioContext; 9 | 10 | public ClientSteps(ScenarioContext scenarioContext) 11 | { 12 | _scenarioContext = scenarioContext; 13 | } 14 | 15 | [Given(@"my api key is '(.*)'")] 16 | public void GivenMyApiKeyIs(string apiKey) 17 | { 18 | var client = new Client(apiKey); 19 | _scenarioContext.Set(client); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/SparkPost.Acceptance/MessageEvents.feature: -------------------------------------------------------------------------------- 1 | Feature: Message Events 2 | 3 | Background: 4 | Given my api key is 'yyy' 5 | 6 | @ignore 7 | Scenario: Samples 8 | When I ask for samples of 'bounce' 9 | Then it should return a 200 -------------------------------------------------------------------------------- /src/SparkPost.Acceptance/MessageEventsSteps.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using TechTalk.SpecFlow; 3 | 4 | namespace SparkPost.Acceptance 5 | { 6 | [Binding] 7 | public class MessageEventsSteps 8 | { 9 | private readonly ScenarioContext _scenarioContext; 10 | 11 | public MessageEventsSteps(ScenarioContext scenarioContext) 12 | { 13 | _scenarioContext = scenarioContext; 14 | } 15 | 16 | [When(@"I ask for samples of '(.*)'")] 17 | public async Task WhenIAskForSamplesOf(string events) 18 | { 19 | var client = _scenarioContext.Get(); 20 | 21 | MessageEventSampleResponse response = await client.MessageEvents.SamplesOf(events); 22 | 23 | _scenarioContext.Set(response); 24 | _scenarioContext.Set(response); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/SparkPost.Acceptance/Metrics.feature: -------------------------------------------------------------------------------- 1 | Feature: Metrics 2 | 3 | Background: 4 | Given my api key is 'yyy' 5 | 6 | @ignore 7 | Scenario: Checking for count accepted 8 | When I query my deliverability for count_accepted 9 | Then it should return a 200 10 | And it should return some metrics count 11 | @ignore 12 | Scenario: Bounce reasons 13 | When I query my bounce reasons 14 | Then it should return a 200 15 | And it should return some metrics count -------------------------------------------------------------------------------- /src/SparkPost.Acceptance/MetricsSteps.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using TechTalk.SpecFlow; 4 | using Xunit; 5 | 6 | namespace SparkPost.Acceptance 7 | { 8 | [Binding] 9 | public class MetricsSteps 10 | { 11 | private readonly ScenarioContext _scenarioContext; 12 | 13 | public MetricsSteps(ScenarioContext scenarioContext) 14 | { 15 | _scenarioContext = scenarioContext; 16 | } 17 | 18 | [When(@"I query my deliverability for (.*)")] 19 | public async Task WhenIQueryMyDeliverability(string metric) 20 | { 21 | var client = _scenarioContext.Get(); 22 | Response response = await client.Metrics.GetDeliverability(new { from = DateTime.MinValue, metrics = metric }); 23 | 24 | _scenarioContext.Set(response); 25 | } 26 | 27 | [When(@"I query my bounce reasons")] 28 | public async Task y() 29 | { 30 | var client = _scenarioContext.Get(); 31 | Response response = await client.Metrics.GetBounceReasons(new { from = DateTime.MinValue }); 32 | 33 | _scenarioContext.Set(response); 34 | } 35 | 36 | [Then("it should return some metrics count")] 37 | public void x() 38 | { 39 | var response = _scenarioContext.Get(); 40 | Assert.IsType(response); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/SparkPost.Acceptance/RecipientListSteps.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using TechTalk.SpecFlow; 5 | using TechTalk.SpecFlow.Assist; 6 | 7 | namespace SparkPost.Acceptance 8 | { 9 | [Binding] 10 | public class RecipientListSteps 11 | { 12 | private readonly ScenarioContext scenarioContext; 13 | 14 | public RecipientListSteps(ScenarioContext scenarioContext) 15 | { 16 | this.scenarioContext = scenarioContext; 17 | } 18 | 19 | [Given(@"I have a new recipient list as")] 20 | public void GivenIHaveANewRecipientListAs(Table table) 21 | { 22 | var recipientList = table.CreateInstance(); 23 | scenarioContext.Set(recipientList); 24 | } 25 | 26 | [Given(@"I add '(.*)' to the recipient list")] 27 | public void GivenIAddToTheRecipientList(string email) 28 | { 29 | var recipientList = scenarioContext.Get(); 30 | recipientList.Recipients.Add(new Recipient { Address = new Address { Email = email } }); 31 | } 32 | 33 | [Given(@"I clear the recipients on the recipient list")] 34 | public void x() 35 | { 36 | var recipientList = scenarioContext.Get(); 37 | recipientList.Recipients.Clear(); 38 | } 39 | 40 | [Given(@"I do not have a recipient list of id '(.*)'")] 41 | public void GivenIDoNotHaveARecipientListOfId(string id) 42 | { 43 | var client = scenarioContext.Get(); 44 | client.RecipientLists.Delete(id); 45 | } 46 | 47 | [When(@"I create the recipient list")] 48 | public async Task WhenICreateTheRecipientList() 49 | { 50 | var recipientList = scenarioContext.Get(); 51 | 52 | var client = scenarioContext.Get(); 53 | 54 | SendRecipientListsResponse response = await client.RecipientLists.Create(recipientList); 55 | 56 | scenarioContext.Set(response); 57 | scenarioContext.Set(response); 58 | } 59 | 60 | [When(@"I retrieve the ""(.*)"" recipient list")] 61 | public async Task WhenIRetrieveTheRecipientList(string key) 62 | { 63 | var client = scenarioContext.Get(); 64 | 65 | RetrieveRecipientListsResponse response = await client.RecipientLists.Retrieve(key); 66 | 67 | scenarioContext.Set(response.RecipientList); 68 | } 69 | 70 | [When(@"I update the recipient list")] 71 | public async Task WhenIUpdateTheRecipientList() 72 | { 73 | var recipientList = scenarioContext.Get(); 74 | 75 | var client = scenarioContext.Get(); 76 | 77 | UpdateRecipientListResponse response = await client.RecipientLists.Update(recipientList); 78 | 79 | scenarioContext.Set(response); 80 | } 81 | 82 | [Then(@"it should have the following recipient list values")] 83 | public void ThenItShouldHaveTheFollowingRecipientListValues(Table table) 84 | { 85 | var recipientList = scenarioContext.Get(); 86 | table.CompareToInstance(recipientList); 87 | } 88 | 89 | [Then(@"it should have the following recipients")] 90 | public void ThenItShouldHaveTheFollowingRecipients(Table table) 91 | { 92 | var recipientLists = scenarioContext.Get().Recipients.Select(x => new { x.Address.Email }); 93 | table.CompareToSet(recipientLists); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/SparkPost.Acceptance/RecipientLists.feature: -------------------------------------------------------------------------------- 1 | Feature: Recipient Lists 2 | 3 | Background: 4 | Given my api key is 'yyy' 5 | 6 | @ignore 7 | Scenario: Retrieving a recipient list 8 | Given I do not have a recipient list of id 'test-name' 9 | And I have a new recipient list as 10 | | Id | Name | Description | 11 | | test-name | Test Name | Test Description | 12 | And I add 'test@test.com' to the recipient list 13 | And I add 'test2@test.com' to the recipient list 14 | When I create the recipient list 15 | When I retrieve the "test-name" recipient list 16 | Then it should return a 200 17 | And it should have the following recipients 18 | | Email | 19 | | test@test.com | 20 | | test2@test.com | 21 | And it should have the following recipient list values 22 | | Id | Name | Description | 23 | | test-name | Test Name | Test Description | 24 | 25 | @ignore 26 | Scenario: Creating a recipient list 27 | Given I do not have a recipient list of id 'test-name' 28 | And I have a new recipient list as 29 | | Id | Name | Description | 30 | | test-name | Test Name | Test Description | 31 | And I add 'test@test.com' to the recipient list 32 | When I create the recipient list 33 | Then it should return a 200 34 | When I retrieve the "test-name" recipient list 35 | Then it should have the following recipients 36 | | Email | 37 | | test@test.com | 38 | 39 | @ignore 40 | Scenario: Updating a recipient list 41 | Given I do not have a recipient list of id 'test-name' 42 | And I have a new recipient list as 43 | | Id | Name | Description | 44 | | test-name | Test Name | Test Description | 45 | And I add 'test@test.com' to the recipient list 46 | And I add 'test2@test.com' to the recipient list 47 | When I create the recipient list 48 | Given I have a new recipient list as 49 | | Id | Name | Description | 50 | | test-name | Test Name Again | Test Description Second | 51 | And I clear the recipients on the recipient list 52 | And I add 'test1@test.com' to the recipient list 53 | And I add 'test3@test.com' to the recipient list 54 | When I update the recipient list 55 | Then it should return a 200 56 | When I retrieve the "test-name" recipient list 57 | Then it should return a 200 58 | And it should have the following recipient list values 59 | | Id | Name | Description | 60 | | test-name | Test Name Again | Test Description Second | 61 | And it should have the following recipients 62 | | Email | 63 | | test1@test.com | 64 | | test3@test.com | -------------------------------------------------------------------------------- /src/SparkPost.Acceptance/ResponseSteps.cs: -------------------------------------------------------------------------------- 1 | using TechTalk.SpecFlow; 2 | using Xunit; 3 | 4 | namespace SparkPost.Acceptance 5 | { 6 | [Binding] 7 | public class ResponseSteps 8 | { 9 | private readonly ScenarioContext _scenarioContext; 10 | 11 | public ResponseSteps(ScenarioContext scenarioContext) 12 | { 13 | _scenarioContext = scenarioContext; 14 | } 15 | 16 | [Then(@"it should return a (.*)")] 17 | public void ThenItShouldReturnA(int statusCode) 18 | { 19 | var response = _scenarioContext.Get(); 20 | Assert.Equal(statusCode, response.StatusCode.GetHashCode()); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/SparkPost.Acceptance/SparkPost.Acceptance.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net6.0 4 | SparkPost.Acceptance 5 | Library 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | Always 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/SparkPost.Acceptance/Suppressions.feature: -------------------------------------------------------------------------------- 1 | Feature: Suppressions 2 | 3 | Background: 4 | Given my api key is 'yyy' 5 | 6 | @ignore 7 | Scenario: Adding an email to the suppressions list 8 | Given I have a random email address ending in '@cauthon.com' 9 | When I add my random email address a to my suppressions list 10 | Then it should return a 200 11 | And my random email address should be on my suppressions list -------------------------------------------------------------------------------- /src/SparkPost.Acceptance/SuppressionsSteps.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using TechTalk.SpecFlow; 5 | using Xunit; 6 | 7 | namespace SparkPost.Acceptance 8 | { 9 | [Binding] 10 | public class SuppressionsSteps 11 | { 12 | private readonly ScenarioContext scenarioContext; 13 | 14 | public SuppressionsSteps(ScenarioContext scenarioContext) 15 | { 16 | this.scenarioContext = scenarioContext; 17 | } 18 | 19 | [Given(@"I have a random email address ending in '(.*)'")] 20 | public void y(string email) 21 | { 22 | scenarioContext["randomemail"] = $"{Guid.NewGuid().ToString().Split('-')[0]}{email}"; 23 | } 24 | 25 | [When(@"I add my random email address a to my suppressions list")] 26 | public async Task WhenIAddToMySuppressionsList() 27 | { 28 | var email = scenarioContext["randomemail"] as string; 29 | 30 | var client = scenarioContext.Get(); 31 | 32 | UpdateSuppressionResponse response = await client.Suppressions.CreateOrUpdate(new[] { email }); 33 | 34 | scenarioContext.Set(response); 35 | scenarioContext.Set(response); 36 | } 37 | 38 | [Then(@"my random email address should be on my suppressions list")] 39 | public async Task ThenShouldBeOnMySuppressionsList() 40 | { 41 | var email = scenarioContext["randomemail"] as string; 42 | 43 | var client = scenarioContext.Get(); 44 | 45 | ListSuppressionResponse response = null; 46 | 47 | response = await client.Suppressions.Retrieve(email); 48 | Assert.True(response.Suppressions.Any()); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/SparkPost.Acceptance/TransmissionSteps.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using TechTalk.SpecFlow; 4 | using TechTalk.SpecFlow.Assist; 5 | 6 | namespace SparkPost.Acceptance 7 | { 8 | [Binding] 9 | public class TransmissionSteps 10 | { 11 | private readonly ScenarioContext scenarioContext; 12 | 13 | public TransmissionSteps(ScenarioContext scenarioContext) 14 | { 15 | this.scenarioContext = scenarioContext; 16 | } 17 | 18 | [Given(@"I have a new transmission")] 19 | public void GivenIHaveANewTransmissionWith() 20 | { 21 | var transmission = new Transmission(); 22 | scenarioContext.Set(transmission); 23 | } 24 | 25 | [Given(@"the transmission is meant to be sent from '(.*)'")] 26 | public void GivenTheTransmissionIsMeantToBeSentfrom(string email) 27 | { 28 | var transmission = scenarioContext.Get(); 29 | transmission.Content.From = new Address { Email = email }; 30 | scenarioContext.Set(transmission); 31 | } 32 | 33 | [Given(@"the transmission is meant to be sent to '(.*)'")] 34 | public void GivenTheTransmissionIsMeantToBeSentTo(string email) 35 | { 36 | var transmission = scenarioContext.Get(); 37 | transmission.Recipients.Add(new Recipient { Address = new Address { Email = email } }); 38 | scenarioContext.Set(transmission); 39 | } 40 | 41 | [Given(@"the transmission content is")] 42 | public void GivenTheTransmissionContentIs(Table table) 43 | { 44 | var transmission = scenarioContext.Get(); 45 | table.FillInstance(transmission.Content); 46 | scenarioContext.Set(transmission); 47 | } 48 | 49 | [Given(@"the transmission template id is set to '(.*)'")] 50 | public void x(string templateId) 51 | { 52 | var transmission = scenarioContext.Get(); 53 | 54 | transmission.Content.TemplateId = templateId; 55 | 56 | scenarioContext.Set(transmission); 57 | } 58 | 59 | [Given(@"the transmission has a text file attachment")] 60 | public void GivenTheTransmissionHasATextFileAttachment() 61 | { 62 | var transmission = scenarioContext.Get(); 63 | 64 | var attachment = File.Create("testtextfile.txt"); 65 | 66 | transmission.Content.Attachments.Add(attachment); 67 | 68 | scenarioContext.Set(transmission); 69 | } 70 | 71 | [Given(@"the transmission is meant to be CCd to '(.*)'")] 72 | public void GivenTheTransmissionIsMeantToBeCCdTo(string email) 73 | { 74 | var transmission = scenarioContext.Get(); 75 | 76 | transmission.Recipients.Add( 77 | new Recipient 78 | { 79 | Type = RecipientType.CC, 80 | Address = new Address { Email = email } 81 | } 82 | ); 83 | 84 | scenarioContext.Set(transmission); 85 | } 86 | 87 | [Given(@"the transmission is meant to be BCCd to '(.*)'")] 88 | public void GivenTheTransmissionIsMeantToBeBCCdTo(string email) 89 | { 90 | var transmission = scenarioContext.Get(); 91 | 92 | transmission.Recipients.Add( 93 | new Recipient 94 | { 95 | Type = RecipientType.BCC, 96 | Address = new Address { Email = email } 97 | } 98 | ); 99 | 100 | scenarioContext.Set(transmission); 101 | } 102 | 103 | [When(@"I send the transmission")] 104 | public async Task WhenISendTheTransmission() 105 | { 106 | var client = scenarioContext.Get(); 107 | var transmission = scenarioContext.Get(); 108 | 109 | SendTransmissionResponse response = await client.Transmissions.Send(transmission); 110 | 111 | scenarioContext.Set(response); 112 | scenarioContext.Set(response); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/SparkPost.Acceptance/Transmissions.feature: -------------------------------------------------------------------------------- 1 | Feature: Transmissions 2 | 3 | Background: 4 | Given my api key is 'yyy' 5 | 6 | @ignore 7 | Scenario: Sending a regular email 8 | Given I have a new transmission 9 | And the transmission is meant to be sent from 'darren@cauthon.com' 10 | And the transmission is meant to be sent to 'darren@cauthon.com' 11 | And the transmission content is 12 | | Subject | Html | 13 | | Test Email | this is a test email | 14 | When I send the transmission 15 | Then it should return a 200 16 | 17 | @ignore 18 | Scenario: Sending a regular email with an attachment 19 | Given I have a new transmission 20 | And the transmission is meant to be sent from 'darren@cauthon.com' 21 | And the transmission is meant to be sent to 'darren@cauthon.com' 22 | And the transmission has a text file attachment 23 | And the transmission content is 24 | | Subject | Html | 25 | | Test Email with an attachment | this is a test email | 26 | When I send the transmission 27 | Then it should return a 200 28 | 29 | @ignore 30 | Scenario: Sending a template email with an attachment, which will be ignored and no attachment will be included 31 | Given I have a new transmission 32 | And the transmission is meant to be sent from 'darren@cauthon.com' 33 | And the transmission is meant to be sent to 'darren@cauthon.com' 34 | And the transmission has a text file attachment 35 | And the transmission template id is set to 'my-first-email' 36 | When I send the transmission 37 | Then it should return a 200 38 | 39 | @ignore 40 | Scenario: Using CC/BCC with one direct recipient 41 | Given I have a new transmission 42 | And the transmission is meant to be sent from 'darren@cauthon.com' 43 | And the transmission is meant to be sent to 'darren@cauthon.com' 44 | And the transmission is meant to be CCd to 'darrencauthon@gmail.com' 45 | And the transmission is meant to be BCCd to 'darrencauthon@yahoo.com' 46 | And the transmission content is 47 | | Subject | Html | 48 | | Test Email With CC and BCC (1 recipient) | this is a test email | 49 | When I send the transmission 50 | Then it should return a 200 51 | 52 | @ignore 53 | Scenario: Using CC/BCC with two direct recipients 54 | Given I have a new transmission 55 | And the transmission is meant to be sent from 'darren@cauthon.com' 56 | And the transmission is meant to be sent to 'darrencauthon@hotmail.com' 57 | And the transmission is meant to be sent to 'darren@cauthon.com' 58 | And the transmission is meant to be CCd to 'darrencauthon@gmail.com' 59 | And the transmission is meant to be BCCd to 'darrencauthon@yahoo.com' 60 | And the transmission content is 61 | | Subject | Html | 62 | | Test Email With CC and BCC (2 recipients) | this is a test email | 63 | When I send the transmission 64 | Then it should return a 200 -------------------------------------------------------------------------------- /src/SparkPost.Acceptance/testtextfile.txt: -------------------------------------------------------------------------------- 1 | This is a text file. -------------------------------------------------------------------------------- /src/SparkPost.Tests/CastAsExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace SparkPost.Tests 6 | { 7 | internal static class CastAsExtensions 8 | { 9 | internal static T CastAs(this object dict) 10 | { 11 | return (T)dict; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/SparkPost.Tests/ClientTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using Xunit; 4 | 5 | namespace SparkPost.Tests 6 | { 7 | public partial class ClientTests 8 | { 9 | public class HttpClientOverridingTests 10 | { 11 | private readonly Client client; 12 | 13 | public HttpClientOverridingTests() 14 | { 15 | client = new Client(null); 16 | } 17 | 18 | [Fact] 19 | public void By_default_it_should_return_new_http_clients_each_time() 20 | { 21 | var first = client.CustomSettings.CreateANewHttpClient(); 22 | var second = client.CustomSettings.CreateANewHttpClient(); 23 | 24 | Assert.NotNull(first); 25 | Assert.NotNull(second); 26 | Assert.NotEqual(first, second); 27 | } 28 | 29 | [Fact] 30 | public void It_should_allow_the_overriding_of_the_http_client_building() 31 | { 32 | var httpClient = new HttpClient(); 33 | 34 | client.CustomSettings.BuildHttpClientsUsing(() => httpClient); 35 | 36 | Assert.Equal(httpClient, client.CustomSettings.CreateANewHttpClient()); 37 | Assert.Equal(httpClient, client.CustomSettings.CreateANewHttpClient()); 38 | Assert.Equal(httpClient, client.CustomSettings.CreateANewHttpClient()); 39 | Assert.Equal(httpClient, client.CustomSettings.CreateANewHttpClient()); 40 | } 41 | 42 | [Fact] 43 | public void it_should_have_inbound_domains() 44 | { 45 | Assert.NotNull(client.InboundDomains); 46 | } 47 | 48 | [Fact] 49 | public void It_should_set_any_subaccount_id_passed_to_it() 50 | { 51 | Assert.Equal(1234, new Client(Guid.NewGuid().ToString(), 1234).SubaccountId); 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/SparkPost.Tests/FileTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using Xunit; 4 | 5 | namespace SparkPost.Tests 6 | { 7 | public class FileTests 8 | { 9 | [Fact] 10 | public void It_should_create_correct_type() 11 | { 12 | byte[] content = null; 13 | Assert.IsType(File.Create(content)); 14 | Assert.IsType(File.Create(content)); 15 | } 16 | 17 | [Theory] 18 | [InlineData("This is some test data.")] 19 | [InlineData("This is some other data.")] 20 | public void It_should_encode_data_correctly(string s) 21 | { 22 | var b = GetBytes(s); 23 | var attach = File.Create(b); 24 | Assert.Equal(EncodeString(s), attach.Data); 25 | } 26 | 27 | [Theory] 28 | [InlineData("foo.png", "image/png")] 29 | [InlineData("foo.txt", "text/plain")] 30 | [InlineData("sf", "application/octet-stream")] 31 | [InlineData("", "application/octet-stream")] 32 | public void It_should_set_name_and_type_correctly(string filename, string mimeType) 33 | { 34 | var b = GetBytes("Some Test Data"); 35 | var attach = File.Create(b, filename); 36 | Assert.Equal(filename, attach.Name); 37 | Assert.Equal(mimeType, attach.Type); 38 | } 39 | 40 | private byte[] GetBytes(string input) 41 | { 42 | return Encoding.ASCII.GetBytes(input); 43 | } 44 | 45 | private string EncodeString(string input) 46 | { 47 | return Convert.ToBase64String(GetBytes(input)); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/SparkPost.Tests/ListMessageEventsResponseTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace SparkPost.Tests 4 | { 5 | public class ListMessageEventsResponseTests 6 | { 7 | public class DefaultTests 8 | { 9 | [Fact] 10 | public void It_should_not_have_nil_links() 11 | { 12 | var response = new ListMessageEventsResponse(); 13 | Assert.NotNull(response.Links); 14 | } 15 | 16 | [Fact] 17 | public void It_should_not_have_nil_events() 18 | { 19 | var response = new ListMessageEventsResponse(); 20 | Assert.NotNull(response.MessageEvents); 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/SparkPost.Tests/MessageEventsQueryTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace SparkPost.Tests 4 | { 5 | public class MessageEventsQueryTests 6 | { 7 | public class Defaults 8 | { 9 | [Fact] 10 | public void It_should_have_a_default_build_list() 11 | { 12 | Assert.NotNull(new MessageEventsQuery().BounceClasses); 13 | } 14 | 15 | [Fact] 16 | public void It_should_have_a_default_campaign_ids_list() 17 | { 18 | Assert.NotNull(new MessageEventsQuery().CampaignIds); 19 | } 20 | 21 | [Fact] 22 | public void It_should_have_a_default_friendly_froms_list() 23 | { 24 | Assert.NotNull(new MessageEventsQuery().FriendlyFroms); 25 | } 26 | 27 | [Fact] 28 | public void It_should_have_a_default_message_ids_list() 29 | { 30 | Assert.NotNull(new MessageEventsQuery().MessageIds); 31 | } 32 | 33 | [Fact] 34 | public void It_should_have_a_recipients_list() 35 | { 36 | Assert.NotNull(new MessageEventsQuery().Recipients); 37 | } 38 | 39 | [Fact] 40 | public void It_should_have_a_Subaccounts_list() 41 | { 42 | Assert.NotNull(new MessageEventsQuery().Subaccounts); 43 | } 44 | 45 | [Fact] 46 | public void It_should_have_TemplateIds_list() 47 | { 48 | Assert.NotNull(new MessageEventsQuery().TemplateIds); 49 | } 50 | 51 | [Fact] 52 | public void It_should_have_Transmissions_list() 53 | { 54 | Assert.NotNull(new MessageEventsQuery().TransmissionIds); 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/SparkPost.Tests/MetricsQueryTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Xunit; 3 | 4 | namespace SparkPost.Tests 5 | { 6 | public class MetricsQueryTests 7 | { 8 | private MetricsQuery _query; 9 | 10 | public MetricsQueryTests() 11 | { 12 | _query = new MetricsQuery(); 13 | } 14 | 15 | private void Check(IList list) 16 | { 17 | Assert.NotNull(list); 18 | Assert.Empty(list); 19 | } 20 | 21 | [Fact] 22 | public void It_should_have_a_default_campaigns_list() => Check(_query.Campaigns); 23 | 24 | [Fact] 25 | public void It_should_have_a_default_domains_list() => Check(_query.Domains); 26 | 27 | [Fact] 28 | public void It_should_have_a_default_metrics_list() => Check(_query.Metrics); 29 | 30 | [Fact] 31 | public void It_should_have_a_default_templates_list() => Check(_query.Templates); 32 | 33 | [Fact] 34 | public void It_should_have_a_default_sending_ips_list() => Check(_query.SendingIps); 35 | 36 | [Fact] 37 | public void It_should_have_a_default_ip_pools_list() => Check(_query.IpPools); 38 | 39 | [Fact] 40 | public void It_should_have_a_default_sending_domains_list() => Check(_query.SendingDomains); 41 | 42 | [Fact] 43 | public void It_should_have_a_default_subaccounts_list() => Check(_query.Subaccounts); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/SparkPost.Tests/RelayWebhookTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace SparkPost.Tests 4 | { 5 | public class RelayWebhookTests 6 | { 7 | public class DefaultTests 8 | { 9 | [Fact] 10 | public void It_should_initialize_match() 11 | { 12 | Assert.NotNull(new RelayWebhook().Match); 13 | } 14 | 15 | [Fact] 16 | public void It_should_initialize_match_protocol() 17 | { 18 | Assert.Equal("SMTP", new RelayWebhook().Match.Protocol); 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/SparkPost.Tests/RequestMethodFinderTests.cs: -------------------------------------------------------------------------------- 1 | using SparkPost.RequestMethods; 2 | using Xunit; 3 | 4 | namespace SparkPost.Tests 5 | { 6 | public class RequestMethodFinderTests 7 | { 8 | public class FindForTests 9 | { 10 | private readonly RequestMethodFinder finder; 11 | 12 | public FindForTests() 13 | { 14 | finder = new RequestMethodFinder(null, null); 15 | } 16 | 17 | [Fact] 18 | public void It_should_return_put_for_put() 19 | { 20 | Assert.IsType(finder.FindFor(new Request { Method = "PUT" })); 21 | } 22 | 23 | [Fact] 24 | public void It_should_return_post_for_post() 25 | { 26 | Assert.IsType(finder.FindFor(new Request { Method = "POST" })); 27 | } 28 | 29 | [Fact] 30 | public void It_should_return_delete_for_delete() 31 | { 32 | Assert.IsType(finder.FindFor(new Request { Method = "DELETE" })); 33 | } 34 | 35 | [Fact] 36 | public void It_should_return_get_for_get() 37 | { 38 | Assert.IsType(finder.FindFor(new Request { Method = "GET" })); 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/SparkPost.Tests/RequestMethods/DeleteTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using SparkPost.RequestMethods; 4 | using Xunit; 5 | 6 | namespace SparkPost.Tests.RequestMethods 7 | { 8 | public class DeleteTests 9 | { 10 | public class CanExecuteTests 11 | { 12 | private readonly Delete delete; 13 | 14 | public CanExecuteTests() 15 | { 16 | var httpClient = new HttpClient(); 17 | delete = new Delete(httpClient); 18 | } 19 | 20 | [Fact] 21 | public void It_should_return_true_for_delete() 22 | { 23 | var request = new Request { Method = "DELETE" }; 24 | Assert.True(delete.CanExecute(request)); 25 | } 26 | 27 | [Fact] 28 | public void It_should_return_true_for_delete_lower() 29 | { 30 | var request = new Request { Method = "delete" }; 31 | Assert.True(delete.CanExecute(request)); 32 | } 33 | 34 | [Fact] 35 | public void It_should_return_true_for_delete_spaces() 36 | { 37 | var request = new Request { Method = "delete " }; 38 | Assert.True(delete.CanExecute(request)); 39 | } 40 | 41 | [Fact] 42 | public void It_should_return_false_for_others() 43 | { 44 | var request = new Request { Method = Guid.NewGuid().ToString() }; 45 | Assert.False(delete.CanExecute(request)); 46 | } 47 | 48 | [Fact] 49 | public void It_should_return_false_for_null() 50 | { 51 | var request = new Request { Method = null }; 52 | Assert.False(delete.CanExecute(request)); 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/SparkPost.Tests/RequestMethods/GetTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using Moq; 4 | using SparkPost.RequestMethods; 5 | using Xunit; 6 | 7 | namespace SparkPost.Tests.RequestMethods 8 | { 9 | public class GetTests 10 | { 11 | public class CanExecuteTests 12 | { 13 | private readonly Get get; 14 | 15 | public CanExecuteTests() 16 | { 17 | var httpClient = new HttpClient(); 18 | var dataMapper = new Mock(); 19 | get = new Get(httpClient, dataMapper.Object); 20 | } 21 | 22 | [Fact] 23 | public void It_should_return_true_for_get() 24 | { 25 | var request = new Request { Method = "GET" }; 26 | Assert.True(get.CanExecute(request)); 27 | } 28 | 29 | [Fact] 30 | public void It_should_return_true_for_get_lower() 31 | { 32 | var request = new Request { Method = "get" }; 33 | Assert.True(get.CanExecute(request)); 34 | } 35 | 36 | [Fact] 37 | public void It_should_return_true_for_get_spacing() 38 | { 39 | var request = new Request { Method = "get " }; 40 | Assert.True(get.CanExecute(request)); 41 | } 42 | 43 | [Fact] 44 | public void It_should_return_false_for_others() 45 | { 46 | var request = new Request { Method = Guid.NewGuid().ToString() }; 47 | Assert.False(get.CanExecute(request)); 48 | } 49 | 50 | [Fact] 51 | public void It_should_return_false_for_null() 52 | { 53 | var request = new Request { Method = null }; 54 | Assert.False(get.CanExecute(request)); 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/SparkPost.Tests/RequestMethods/PostTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using SparkPost.RequestMethods; 4 | using Xunit; 5 | 6 | namespace SparkPost.Tests.RequestMethods 7 | { 8 | public class PostTests 9 | { 10 | public class CanExecuteTests 11 | { 12 | private readonly Post post; 13 | 14 | public CanExecuteTests() 15 | { 16 | var httpClient = new HttpClient(); 17 | post = new Post(httpClient); 18 | } 19 | 20 | [Fact] 21 | public void It_should_return_true_for_post() 22 | { 23 | var request = new Request { Method = "POST" }; 24 | Assert.True(post.CanExecute(request)); 25 | } 26 | 27 | [Fact] 28 | public void It_should_return_true_for_post_lower() 29 | { 30 | var request = new Request { Method = "post" }; 31 | Assert.True(post.CanExecute(request)); 32 | } 33 | 34 | [Fact] 35 | public void It_should_return_true_for_post_spaces() 36 | { 37 | var request = new Request { Method = "post " }; 38 | Assert.True(post.CanExecute(request)); 39 | } 40 | 41 | [Fact] 42 | public void It_should_return_false_for_others() 43 | { 44 | var request = new Request { Method = Guid.NewGuid().ToString() }; 45 | Assert.False(post.CanExecute(request)); 46 | } 47 | 48 | [Fact] 49 | public void It_should_return_false_for_nil() 50 | { 51 | var request = new Request { Method = null }; 52 | Assert.False(post.CanExecute(request)); 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/SparkPost.Tests/RequestMethods/PutTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using SparkPost.RequestMethods; 4 | using Xunit; 5 | 6 | namespace SparkPost.Tests.RequestMethods 7 | { 8 | public class PutTests 9 | { 10 | public class CanExecuteTests 11 | { 12 | private readonly Put put; 13 | 14 | public CanExecuteTests() 15 | { 16 | var httpClient = new HttpClient(); 17 | put = new Put(httpClient); 18 | } 19 | 20 | [Fact] 21 | public void It_should_return_true_for_put() 22 | { 23 | var request = new Request { Method = "PUT" }; 24 | Assert.True(put.CanExecute(request)); 25 | } 26 | 27 | [Fact] 28 | public void It_should_return_true_for_put_lower() 29 | { 30 | var request = new Request { Method = "put" }; 31 | Assert.True(put.CanExecute(request)); 32 | } 33 | 34 | [Fact] 35 | public void It_should_return_true_for_put_json() 36 | { 37 | var request = new Request { Method = "PUT JSON" }; 38 | Assert.True(put.CanExecute(request)); 39 | } 40 | 41 | [Fact] 42 | public void It_should_return_false_for_others() 43 | { 44 | var request = new Request { Method = Guid.NewGuid().ToString() }; 45 | Assert.False(put.CanExecute(request)); 46 | } 47 | 48 | [Fact] 49 | public void It_should_return_false_for_nil() 50 | { 51 | var request = new Request { Method = null }; 52 | Assert.False(put.CanExecute(request)); 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/SparkPost.Tests/SparkPost.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net6.0 4 | SparkPost.Tests 5 | Library 6 | 7 | 8 | 9 | 10 | 11 | 12 | all 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/SparkPost.Tests/SubaccountTest.cs: -------------------------------------------------------------------------------- 1 | namespace SparkPost.Tests 2 | { 3 | public class SubaccountTests { } 4 | } 5 | -------------------------------------------------------------------------------- /src/SparkPost.Tests/UserAgentTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace SparkPost.Tests 5 | { 6 | public partial class ClientTests 7 | { 8 | public partial class UserAgentTests 9 | { 10 | private readonly Client.Settings settings; 11 | 12 | public UserAgentTests() 13 | { 14 | settings = new Client.Settings(); 15 | } 16 | 17 | [Fact] 18 | public void It_should_default_to_the_library_version() 19 | { 20 | Assert.StartsWith($"csharp-sparkpost/2.", settings.UserAgent); 21 | } 22 | 23 | [Fact] 24 | public void It_should_allow_the_user_agent_to_be_changed() 25 | { 26 | var userAgent = Guid.NewGuid().ToString(); 27 | settings.UserAgent = userAgent; 28 | Assert.Equal(userAgent, settings.UserAgent); 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/SparkPost.Tests/Utilities/SnakeCaseTests.cs: -------------------------------------------------------------------------------- 1 | using SparkPost.Utilities; 2 | using Xunit; 3 | 4 | namespace SparkPost.Tests.Utilities 5 | { 6 | public class SnakeCaseTests 7 | { 8 | [Fact] 9 | public void It_should_convert_things_to_snake_case() 10 | { 11 | Assert.Equal("t", SnakeCase.Convert("T")); 12 | Assert.Equal("test", SnakeCase.Convert("Test")); 13 | Assert.Equal("t_e_s_t", SnakeCase.Convert("TEST")); 14 | Assert.Equal("john_galt", SnakeCase.Convert("JohnGalt")); 15 | } 16 | 17 | [Fact] 18 | public void It_should_handle_harder_strings() 19 | { 20 | Assert.Equal("test_testing", SnakeCase.Convert("TestTesting")); 21 | Assert.Equal("testing_test", SnakeCase.Convert("TestingTest")); 22 | Assert.Equal("appppp_appppppp", SnakeCase.Convert("ApppppAppppppp")); 23 | Assert.Equal("appppppp_appppp", SnakeCase.Convert("ApppppppAppppp")); 24 | } 25 | 26 | [Fact] 27 | public void It_should_convert_null_to_null() 28 | { 29 | Assert.Null(SnakeCase.Convert(null)); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/SparkPost.Tests/WebhookTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace SparkPost.Tests 4 | { 5 | public class WebhookTests 6 | { 7 | public class DefaultTests 8 | { 9 | [Fact] 10 | public void It_should_initialize_events() 11 | { 12 | Assert.NotNull(new Webhook().Events); 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/SparkPost.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SparkPost", "SparkPost\SparkPost.csproj", "{A5DDA3E3-7B3D-46C3-B4BB-C627FBA37812}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SparkPost.Tests", "SparkPost.Tests\SparkPost.Tests.csproj", "{52F2F4F4-3DE2-49C6-87D8-BD6F825B9372}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SparkPost.Acceptance", "SparkPost.Acceptance\SparkPost.Acceptance.csproj", "{8F62193B-1C44-4E92-96C7-8EBD610F7669}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SendTemplate", "..\examples\SendTemplate\SendTemplate.csproj", "{A85DDE10-EBD6-4B58-93D6-185809C73FE8}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{1DC64ED8-82FD-4640-90F3-BD487C809039}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SendInline", "..\examples\SendInline\SendInline.csproj", "{8D45D223-E1FE-4652-A37E-09507AC4E107}" 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CC", "..\examples\CC\CC.csproj", "{2B086611-A56B-4857-9F7F-A8603D7AA3FD}" 19 | EndProject 20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BCC", "..\examples\BCC\BCC.csproj", "{16978032-B34C-44B6-8DA1-475D751338F1}" 21 | EndProject 22 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "build", "..\build\build.csproj", "{C68B2B1C-B5A9-4E97-8971-2ACDFF94A6B2}" 23 | EndProject 24 | Global 25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 26 | Debug|Any CPU = Debug|Any CPU 27 | Release|Any CPU = Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {A5DDA3E3-7B3D-46C3-B4BB-C627FBA37812}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {A5DDA3E3-7B3D-46C3-B4BB-C627FBA37812}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {A5DDA3E3-7B3D-46C3-B4BB-C627FBA37812}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {A5DDA3E3-7B3D-46C3-B4BB-C627FBA37812}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {52F2F4F4-3DE2-49C6-87D8-BD6F825B9372}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {52F2F4F4-3DE2-49C6-87D8-BD6F825B9372}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {52F2F4F4-3DE2-49C6-87D8-BD6F825B9372}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {52F2F4F4-3DE2-49C6-87D8-BD6F825B9372}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {8F62193B-1C44-4E92-96C7-8EBD610F7669}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {8F62193B-1C44-4E92-96C7-8EBD610F7669}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {8F62193B-1C44-4E92-96C7-8EBD610F7669}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {8F62193B-1C44-4E92-96C7-8EBD610F7669}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {A85DDE10-EBD6-4B58-93D6-185809C73FE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {A85DDE10-EBD6-4B58-93D6-185809C73FE8}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {A85DDE10-EBD6-4B58-93D6-185809C73FE8}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {A85DDE10-EBD6-4B58-93D6-185809C73FE8}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {8D45D223-E1FE-4652-A37E-09507AC4E107}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {8D45D223-E1FE-4652-A37E-09507AC4E107}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {8D45D223-E1FE-4652-A37E-09507AC4E107}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {8D45D223-E1FE-4652-A37E-09507AC4E107}.Release|Any CPU.Build.0 = Release|Any CPU 50 | {2B086611-A56B-4857-9F7F-A8603D7AA3FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {2B086611-A56B-4857-9F7F-A8603D7AA3FD}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {2B086611-A56B-4857-9F7F-A8603D7AA3FD}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {2B086611-A56B-4857-9F7F-A8603D7AA3FD}.Release|Any CPU.Build.0 = Release|Any CPU 54 | {16978032-B34C-44B6-8DA1-475D751338F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {16978032-B34C-44B6-8DA1-475D751338F1}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {16978032-B34C-44B6-8DA1-475D751338F1}.Release|Any CPU.ActiveCfg = Release|Any CPU 57 | {16978032-B34C-44B6-8DA1-475D751338F1}.Release|Any CPU.Build.0 = Release|Any CPU 58 | {C68B2B1C-B5A9-4E97-8971-2ACDFF94A6B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 59 | {C68B2B1C-B5A9-4E97-8971-2ACDFF94A6B2}.Debug|Any CPU.Build.0 = Debug|Any CPU 60 | {C68B2B1C-B5A9-4E97-8971-2ACDFF94A6B2}.Release|Any CPU.ActiveCfg = Release|Any CPU 61 | {C68B2B1C-B5A9-4E97-8971-2ACDFF94A6B2}.Release|Any CPU.Build.0 = Release|Any CPU 62 | EndGlobalSection 63 | GlobalSection(SolutionProperties) = preSolution 64 | HideSolutionNode = FALSE 65 | EndGlobalSection 66 | GlobalSection(NestedProjects) = preSolution 67 | {A85DDE10-EBD6-4B58-93D6-185809C73FE8} = {1DC64ED8-82FD-4640-90F3-BD487C809039} 68 | {8D45D223-E1FE-4652-A37E-09507AC4E107} = {1DC64ED8-82FD-4640-90F3-BD487C809039} 69 | {2B086611-A56B-4857-9F7F-A8603D7AA3FD} = {1DC64ED8-82FD-4640-90F3-BD487C809039} 70 | {16978032-B34C-44B6-8DA1-475D751338F1} = {1DC64ED8-82FD-4640-90F3-BD487C809039} 71 | EndGlobalSection 72 | EndGlobal 73 | -------------------------------------------------------------------------------- /src/SparkPost/Address.cs: -------------------------------------------------------------------------------- 1 | namespace SparkPost 2 | { 3 | public class Address 4 | { 5 | public string Name { get; set; } 6 | public string Email { get; set; } 7 | public string HeaderTo { get; set; } 8 | 9 | public Address() { } 10 | 11 | public Address(string email) : this(email, null, null) { } 12 | 13 | public Address(string email, string name) : this(email, name, null) { } 14 | 15 | public Address(string email, string name, string headerTo) 16 | { 17 | this.Email = email; 18 | this.Name = name; 19 | this.HeaderTo = headerTo; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/SparkPost/Attachment.cs: -------------------------------------------------------------------------------- 1 | namespace SparkPost 2 | { 3 | public class Attachment : File { } 4 | } 5 | -------------------------------------------------------------------------------- /src/SparkPost/Attributes.cs: -------------------------------------------------------------------------------- 1 | namespace SparkPost 2 | { 3 | public class Attributes 4 | { 5 | public Attributes() { } 6 | 7 | public int InternalId { get; set; } 8 | 9 | public int ListGroupId { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/SparkPost/BounceCategory.cs: -------------------------------------------------------------------------------- 1 | namespace SparkPost 2 | { 3 | // The list was taken from: 4 | // https://support.sparkpost.com/customer/portal/articles/1929896 5 | 6 | public enum BounceCategory 7 | { 8 | /// 9 | /// None or unable to parse. 10 | /// 11 | Undefined, 12 | 13 | /// 14 | /// The response text could not be identified. 15 | /// 16 | Undetermined, 17 | 18 | /// 19 | /// Hard bounce: Invalid recipient, no recipient. 20 | /// 21 | Hard, 22 | 23 | /// 24 | /// Soft bounce: soft bounced, DNS failure, MX record not found, mailbox is full, message too large, timeout, delayed, auto-reply, unspecified reason. 25 | /// 26 | Soft, 27 | 28 | /// 29 | /// The message was failed by Momentum's (SparkPost) configured policies or the message is a subscribe request. 30 | /// 31 | Admin, 32 | 33 | /// 34 | /// The message was blocked by the receiver (spam). 35 | /// 36 | Block 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/SparkPost/BounceClass.cs: -------------------------------------------------------------------------------- 1 | namespace SparkPost 2 | { 3 | // The list was taken from: 4 | // https://support.sparkpost.com/customer/portal/articles/1929896 5 | 6 | public enum BounceClass 7 | { 8 | /// 9 | /// None or unable to parse. 10 | /// 11 | Undefined = 0, 12 | 13 | /// 14 | /// Undetermined 15 | /// The response text could not be identified. 16 | /// Category: Undetermined. 17 | /// 18 | Undetermined = 1, 19 | 20 | /// 21 | /// Invalid Recipient 22 | /// The recipient is invalid. 23 | /// Category: Hard. 24 | /// 25 | InvalidRecipient = 10, 26 | 27 | /// 28 | /// Soft Bounce 29 | /// The message soft bounced. 30 | /// Category: Soft. 31 | /// 32 | SoftBounce = 20, 33 | 34 | /// 35 | /// DNS Failure 36 | /// The message bounced due to a DNS failure. 37 | /// Category: Soft. 38 | /// 39 | DnsFailure = 21, 40 | 41 | /// 42 | /// Mailbox Full 43 | /// The message bounced due to the remote mailbox being over quota. 44 | /// Category: Soft. 45 | /// 46 | MailboxFull = 22, 47 | 48 | /// 49 | /// Too Large 50 | /// The message bounced because it was too large for the recipient. 51 | /// Category: Soft. 52 | /// 53 | TooLarge = 23, 54 | 55 | /// 56 | /// Timeout 57 | /// The message timed out. 58 | /// Category: Soft. 59 | /// 60 | Timeout = 24, 61 | 62 | /// 63 | /// Admin Failure 64 | /// The message was failed by Momentum's configured policies. 65 | /// Category: Admin. 66 | /// 67 | AdminFailure = 25, 68 | 69 | /// 70 | /// Generic Bounce: No RCPT 71 | /// No recipient could be determined for the message. 72 | /// Category: Hard. 73 | /// 74 | GenericBounceNoRecipient = 30, 75 | 76 | /// 77 | /// Generic Bounce 78 | /// The message failed for unspecified reasons. 79 | /// Category: Soft. 80 | /// 81 | GenericBounce = 40, 82 | 83 | /// 84 | /// Mail Block 85 | /// The message was blocked by the receiver. 86 | /// Category: Block. 87 | /// 88 | MailBlock = 50, 89 | 90 | /// 91 | /// Spam Block 92 | /// The message was blocked by the receiver as coming from a known spam source. 93 | /// Category: Block. 94 | /// 95 | SpamBlock = 51, 96 | 97 | /// 98 | /// Spam Content 99 | /// The message was blocked by the receiver as spam. 100 | /// Category: Block. 101 | /// 102 | SpamContent = 52, 103 | 104 | /// 105 | /// Prohibited Attachment 106 | /// The message was blocked by the receiver because it contained an attachment. 107 | /// Category: Block. 108 | /// 109 | ProhibitedAttachment = 53, 110 | 111 | /// 112 | /// Relaying Denied 113 | /// The message was blocked by the receiver because relaying is not allowed. 114 | /// Category: Block. 115 | /// 116 | RelayingDenied = 54, 117 | 118 | /// 119 | /// Auto-Reply 120 | /// The message is an auto-reply/vacation mail. 121 | /// Category: Soft. 122 | /// 123 | AutoReply = 60, 124 | 125 | /// 126 | /// Transient Failure 127 | /// Message transmission has been temporarily delayed. 128 | /// Category: Soft. 129 | /// 130 | TransientFailure = 70, 131 | 132 | /// 133 | /// Subscribe 134 | /// The message is a subscribe request. 135 | /// Category: Admin. 136 | /// 137 | Subscribe = 80, 138 | 139 | /// 140 | /// Unsubscribe 141 | /// The message is an unsubscribe request. 142 | /// Category: Hard. 143 | /// 144 | Unsubscribe = 90, 145 | 146 | /// 147 | /// Challenge-Response 148 | /// The message is a challenge-response probe. 149 | /// Category: Soft. 150 | /// 151 | ChallengeResponse = 100 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/SparkPost/BounceClassDetails.cs: -------------------------------------------------------------------------------- 1 | namespace SparkPost 2 | { 3 | public class BounceClassDetails 4 | { 5 | public BounceClass BounceClass { get; set; } 6 | public string Name { get; set; } 7 | public string Description { get; set; } 8 | public BounceCategory Category { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/SparkPost/CcHandling.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.RegularExpressions; 5 | 6 | namespace SparkPost 7 | { 8 | internal static class CcHandling 9 | { 10 | internal static void Process(Transmission transmission, IDictionary result) 11 | { 12 | var recipients = transmission.Recipients; 13 | if (recipients.All(RecipientTypeIsTo)) 14 | return; 15 | 16 | var toRecipient = recipients.FirstOrDefault(RecipientTypeIsTo); 17 | if (recipients.Count(RecipientTypeIsTo) == 1 && toRecipient.Address != null) 18 | DoStandardCcRewriting(recipients, result); 19 | else 20 | SetAnyCCsInTheHeader(recipients, result); 21 | } 22 | 23 | private static void DoStandardCcRewriting(IEnumerable recipients, IDictionary result) 24 | { 25 | var toRecipient = recipients.Single(RecipientTypeIsTo); 26 | var toName = toRecipient.Address.Name; 27 | var toEmail = toRecipient.Address.Email; 28 | 29 | var ccRecipients = recipients.Where(RecipientTypeIsCC); 30 | if (ccRecipients.Any()) 31 | { 32 | var ccHeader = GetCcHeader(ccRecipients); 33 | if (!String.IsNullOrWhiteSpace(ccHeader)) 34 | SetTheCcHeader(result, ccHeader); 35 | } 36 | 37 | var resultRecipients = (result["recipients"] as IEnumerable>).ToList(); 38 | SetFieldsOnRecipients(resultRecipients, toName, toEmail); 39 | result["recipients"] = resultRecipients; 40 | } 41 | 42 | private static void SetAnyCCsInTheHeader(IEnumerable recipients, IDictionary result) 43 | { 44 | var ccs = GetTheCcEmails(recipients); 45 | 46 | if (ccs.Any() == false) 47 | return; 48 | 49 | string ccHeader = FormatTheCCs(ccs); 50 | SetTheCcHeader(result, ccHeader); 51 | } 52 | 53 | private static IEnumerable GetTheCcEmails(IEnumerable recipients) 54 | { 55 | return recipients 56 | .Where(x => x.Type == RecipientType.CC) 57 | .Where(x => x.Address != null) 58 | .Where(x => string.IsNullOrWhiteSpace(x.Address.Email) == false) 59 | .Select(x => x.Address.Email); 60 | } 61 | 62 | private static string FormatTheCCs(IEnumerable ccs) 63 | { 64 | return string.Join(",", ccs.Select(x => "<" + x + ">")); 65 | } 66 | 67 | private static void SetTheCcHeader(IDictionary result, string header) 68 | { 69 | MakeSureThereIsAHeaderDefinedInTheRequest(result); 70 | SetThisHeaderValue(result, "CC", header); 71 | } 72 | 73 | private static bool RecipientTypeIsTo(Recipient recipient) 74 | { 75 | return recipient.Type == RecipientType.To; 76 | } 77 | 78 | private static bool RecipientTypeIsCC(Recipient recipient) 79 | { 80 | return recipient.Type == RecipientType.CC; 81 | } 82 | 83 | private static void SetFieldsOnRecipients(IEnumerable> recipients, string name, string email) 84 | { 85 | var addresses = recipients.Where(r => r.ContainsKey("address")).Select(r => r["address"]).Cast>(); 86 | 87 | foreach (var address in addresses) 88 | { 89 | if (!String.IsNullOrWhiteSpace(name)) 90 | address["name"] = name; 91 | if (!String.IsNullOrWhiteSpace(email)) 92 | address["header_to"] = email; 93 | } 94 | } 95 | 96 | private static string GetCcHeader(IEnumerable recipients) 97 | { 98 | var listOfFormattedAddresses = recipients.Select(FormatedAddress).Where(fa => !String.IsNullOrWhiteSpace(fa)); 99 | return listOfFormattedAddresses.Any() ? String.Join(", ", listOfFormattedAddresses) : null; 100 | } 101 | 102 | private static string FormatedAddress(Recipient recipient) 103 | { 104 | var address = recipient.Address; 105 | 106 | if (string.IsNullOrWhiteSpace(address?.Email)) 107 | return null; 108 | 109 | var email = address.Email.Trim(); 110 | 111 | if (string.IsNullOrWhiteSpace(address.Name)) 112 | return email; 113 | 114 | var name = Regex.IsMatch(address.Name, @"[^\w ]") ? $"\"{address.Name}\"" : address.Name; 115 | return $"{name} <{email}>"; 116 | } 117 | 118 | private static void MakeSureThereIsAHeaderDefinedInTheRequest(IDictionary result) 119 | { 120 | if (result.ContainsKey("content") == false) 121 | result["content"] = new Dictionary(); 122 | 123 | var content = result["content"] as IDictionary; 124 | if (content.ContainsKey("headers") == false) 125 | content["headers"] = new Dictionary(); 126 | } 127 | 128 | private static void SetThisHeaderValue(IDictionary result, string key, string value) 129 | { 130 | ((IDictionary)((IDictionary)result["content"])["headers"])[key] = value; 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/SparkPost/Client.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Net.Http; 4 | using SparkPost.RequestSenders; 5 | using SparkPost.Utilities; 6 | 7 | namespace SparkPost 8 | { 9 | public class Client : IClient 10 | { 11 | private const string defaultApiHost = "https://api.sparkpost.com"; 12 | 13 | public Client(string apiKey) : this(apiKey, defaultApiHost, 0) { } 14 | 15 | public Client(string apiKey, string apiHost) : this(apiKey, apiHost, 0) { } 16 | 17 | public Client(string apiKey, long subAccountId) : this(apiKey, defaultApiHost, subAccountId) { } 18 | 19 | public Client(string apiKey, string apiHost, long subAccountId) 20 | { 21 | ApiKey = apiKey; 22 | ApiHost = apiHost; 23 | SubaccountId = subAccountId; 24 | 25 | var dataMapper = new DataMapper(Version); 26 | var requestSender = new RequestSender(this, dataMapper); 27 | 28 | SendingDomains = new SendingDomains(this, requestSender, dataMapper); 29 | Transmissions = new Transmissions(this, requestSender, dataMapper); 30 | Suppressions = new Suppressions(this, requestSender, dataMapper); 31 | Webhooks = new Webhooks(this, requestSender, dataMapper); 32 | Subaccounts = new Subaccounts(this, requestSender, dataMapper); 33 | MessageEvents = new MessageEvents(this, requestSender); 34 | InboundDomains = new InboundDomains(this, requestSender, dataMapper); 35 | RelayWebhooks = new RelayWebhooks(this, requestSender, dataMapper); 36 | RecipientValidations = new RecipientValidation(this, requestSender); 37 | RecipientLists = new RecipientLists(this, requestSender, dataMapper); 38 | Templates = new Templates(this, requestSender, dataMapper); 39 | Metrics = new Metrics(this, requestSender); 40 | Events = new Events(this, requestSender); 41 | CustomSettings = new Settings(); 42 | } 43 | 44 | public string ApiKey { get; set; } 45 | public string ApiHost { get; set; } 46 | public long SubaccountId { get; set; } 47 | 48 | public ISendingDomains SendingDomains { get; } 49 | public ITransmissions Transmissions { get; } 50 | public ISuppressions Suppressions { get; } 51 | public IWebhooks Webhooks { get; } 52 | public ISubaccounts Subaccounts { get; } 53 | public IMessageEvents MessageEvents { get; } 54 | public IInboundDomains InboundDomains { get; } 55 | public IRelayWebhooks RelayWebhooks { get; } 56 | public IRecipientLists RecipientLists { get; } 57 | public ITemplates Templates { get; } 58 | public IMetrics Metrics { get; } 59 | 60 | public IRecipientValidation RecipientValidations { get; } 61 | 62 | public Events Events { get; } 63 | public string Version => "v1"; 64 | 65 | public Settings CustomSettings { get; } 66 | 67 | public class Settings 68 | { 69 | private Func httpClientBuilder; 70 | 71 | public Settings() 72 | { 73 | httpClientBuilder = () => 74 | { 75 | var httpClient = new HttpClient(); 76 | httpClient.DefaultRequestHeaders.Accept.Clear(); 77 | return httpClient; 78 | }; 79 | 80 | var currentVersion = GetTheCurrentVersion(); 81 | if (string.IsNullOrEmpty(currentVersion) == false) 82 | UserAgent = $"csharp-sparkpost/{currentVersion}"; 83 | } 84 | 85 | public string UserAgent { get; set; } 86 | 87 | public HttpClient CreateANewHttpClient() 88 | { 89 | return httpClientBuilder(); 90 | } 91 | 92 | public void BuildHttpClientsUsing(Func httpClient) 93 | { 94 | httpClientBuilder = httpClient; 95 | } 96 | 97 | private static string GetTheCurrentVersion() 98 | { 99 | try 100 | { 101 | return AttemptToPullTheVersionNumberOutOf(typeof(Client).AssemblyQualifiedName); 102 | } 103 | catch 104 | { 105 | return null; 106 | } 107 | } 108 | 109 | private static string AttemptToPullTheVersionNumberOutOf(string value) 110 | { 111 | return value.SplitOn("Version=")[1].SplitOn(",")[0].SplitOn(".").Take(3).JoinWith("."); 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/SparkPost/Content.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace SparkPost 4 | { 5 | public class Content 6 | { 7 | public Content() 8 | { 9 | From = new Address(); 10 | Headers = new Dictionary(); 11 | Attachments = new List(); 12 | InlineImages = new List(); 13 | } 14 | 15 | public string Html { get; set; } 16 | public string Text { get; set; } 17 | public string Subject { get; set; } 18 | public Address From { get; set; } 19 | public string ReplyTo { get; set; } 20 | public IDictionary Headers { get; set; } 21 | public IList Attachments { get; set; } 22 | public IList InlineImages { get; set; } 23 | public string TemplateId { get; set; } 24 | public bool? UseDraftTemplate { get; set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/SparkPost/CreateSendingDomainResponse.cs: -------------------------------------------------------------------------------- 1 | namespace SparkPost 2 | { 3 | public class CreateSendingDomainResponse : Response 4 | { 5 | public string Domain { get; set; } 6 | 7 | public Dkim Dkim { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/SparkPost/CreateSubaccountResponse.cs: -------------------------------------------------------------------------------- 1 | namespace SparkPost 2 | { 3 | public class CreateSubaccountResponse : Response 4 | { 5 | public int SubaccountId { get; set; } 6 | public string Key { get; set; } 7 | public string Label { get; set; } 8 | public string ShortKey { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/SparkPost/CreateTemplateResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SparkPost 8 | { 9 | public class CreateTemplateResponse : Response 10 | { 11 | public string Id { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/SparkPost/Dkim.cs: -------------------------------------------------------------------------------- 1 | namespace SparkPost 2 | { 3 | public class Dkim 4 | { 5 | public string SigningDomain { get; set; } 6 | 7 | public string PrivateKey { get; set; } 8 | 9 | public string PublicKey { get; set; } 10 | 11 | public string Selector { get; set; } 12 | 13 | public string Headers { get; set; } 14 | 15 | /// 16 | /// Convert json result form Sparkpost API to Dkim. 17 | /// 18 | /// Json result form Sparkpost API. 19 | /// 20 | public static Dkim ConvertToDkim(dynamic result) 21 | { 22 | return result != null 23 | ? new Dkim 24 | { 25 | SigningDomain = result["public"], 26 | PublicKey = result["private"], 27 | Selector = result.selector, 28 | Headers = result.headers 29 | } 30 | : null; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/SparkPost/Dns.cs: -------------------------------------------------------------------------------- 1 | namespace SparkPost 2 | { 3 | public class Dns 4 | { 5 | public string DkimRecord { get; set; } 6 | 7 | public string SpfRecord { get; set; } 8 | 9 | public string DkimError { get; set; } 10 | 11 | public string SpfError { get; set; } 12 | 13 | /// 14 | /// Convert json result form Sparkpost API to Dns. 15 | /// 16 | /// Json result form Sparkpost API. 17 | /// 18 | public static Dns ConvertToDns(dynamic result) 19 | { 20 | return result != null 21 | ? new Dns 22 | { 23 | DkimError = result.dkim_error, 24 | DkimRecord = result.dkim_record, 25 | SpfError = result.spf_error, 26 | SpfRecord = result.spf_record 27 | } 28 | : null; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/SparkPost/EmailValidationResponse.cs: -------------------------------------------------------------------------------- 1 | namespace SparkPost 2 | { 3 | public class EmailValidationResponse 4 | { 5 | public string Result { get; set; } 6 | public bool Valid { get; set; } 7 | public string Reason { get; set; } 8 | public bool IsRole { get; set; } 9 | public bool IsDisposable { get; set; } 10 | public int DeliveryConfidence { get; set; } 11 | public bool IsFree { get; set; } 12 | 13 | public static EmailValidationResponse ConvertToResponse(dynamic r) => 14 | new EmailValidationResponse 15 | { 16 | Result = r.result, 17 | Valid = r.valid, 18 | Reason = r.reason ?? string.Empty, 19 | IsRole = r.is_role, 20 | IsDisposable = r.is_disposable, 21 | DeliveryConfidence = r.delivery_confidence, 22 | IsFree = r.is_free 23 | }; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/SparkPost/Events.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Threading.Tasks; 4 | using Newtonsoft.Json; 5 | using Newtonsoft.Json.Linq; 6 | using SparkPost.RequestSenders; 7 | using SparkPost.Utilities; 8 | 9 | namespace SparkPost 10 | { 11 | public class Events 12 | { 13 | private readonly Client client; 14 | private readonly IRequestSender requestSender; 15 | 16 | public Events(Client client, IRequestSender requestSender) 17 | { 18 | this.client = client; 19 | this.requestSender = requestSender; 20 | } 21 | 22 | public async Task Get(string cursor = "initial", int perPage = 1000) 23 | { 24 | var request = new Request { Url = $"api/{client.Version}/events/message?perPage={perPage}&cursor={cursor}", Method = "GET" }; 25 | 26 | var response = await requestSender.Send(request); 27 | if (response.StatusCode != HttpStatusCode.OK) 28 | { 29 | throw new ResponseException(response); 30 | } 31 | var results = Jsonification.DeserializeObject(response.Content); 32 | return results; 33 | } 34 | 35 | public async Task GetEventsSince(DateTime fromTime, int perPage = 1000) 36 | { 37 | var request = new Request 38 | { 39 | Url = $"api/{client.Version}/events/message?perPage={perPage}&from={fromTime:yyyy-MM-ddTHH:mm:ssZ}", 40 | Method = "GET" 41 | }; 42 | 43 | var response = await requestSender.Send(request); 44 | if (response.StatusCode != HttpStatusCode.OK) 45 | { 46 | throw new ResponseException(response); 47 | } 48 | var results = Jsonification.DeserializeObject(response.Content); 49 | return results; 50 | } 51 | 52 | public async Task GetEventsNext(string nextUri) 53 | { 54 | var request = new Request { Url = nextUri, Method = "GET" }; 55 | 56 | var response = await requestSender.Send(request); 57 | if (response.StatusCode != HttpStatusCode.OK) 58 | { 59 | throw new ResponseException(response); 60 | } 61 | var results = Jsonification.DeserializeObject(response.Content); 62 | return results; 63 | } 64 | } 65 | 66 | public class JsonEventResponse 67 | { 68 | [JsonProperty("results")] 69 | public JObject[] Results { get; set; } 70 | 71 | [JsonProperty("total_count")] 72 | public int TotalCount { get; set; } 73 | public Links Links { get; set; } 74 | } 75 | 76 | public class Links 77 | { 78 | public string Next { get; set; } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/SparkPost/File.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Web; 4 | 5 | namespace SparkPost 6 | { 7 | public abstract class File 8 | { 9 | public string Type { get; set; } 10 | public string Name { get; set; } 11 | public string Data { get; set; } 12 | 13 | public static T Create(string filename) where T : File, new() 14 | { 15 | var content = System.IO.File.ReadAllBytes(filename); 16 | return Create(content, Path.GetFileName(filename)); 17 | } 18 | 19 | public static T Create(string filename, string name) where T : File, new() 20 | { 21 | var result = Create(filename); 22 | result.Name = name; 23 | return result; 24 | } 25 | 26 | public static T Create(byte[] content) where T : File, new() 27 | { 28 | return Create(content, String.Empty); 29 | } 30 | 31 | public static T Create(byte[] content, string name) where T : File, new() 32 | { 33 | var result = new T(); 34 | if (content != null) 35 | { 36 | result.Data = Convert.ToBase64String(content); 37 | result.Type = MimeMapping.MimeUtility.GetMimeMapping(name); 38 | result.Name = name; 39 | } 40 | ; 41 | return result; 42 | } 43 | 44 | public static T Create(Stream content) where T : File, new() 45 | { 46 | return Create(content, String.Empty); 47 | } 48 | 49 | public static T Create(Stream content, string name) where T : File, new() 50 | { 51 | using (var ms = new MemoryStream()) 52 | { 53 | content.CopyTo(ms); 54 | return Create(ms.ToArray(), name); 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/SparkPost/GetMetricsResourceResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SparkPost 8 | { 9 | public class GetMetricsResourceResponse : Response 10 | { 11 | public IList Results { get; set; } 12 | 13 | public GetMetricsResourceResponse() 14 | { 15 | Results = new List(); 16 | } 17 | 18 | public GetMetricsResourceResponse(Response source) 19 | { 20 | this.SetFrom(source); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/SparkPost/GetMetricsResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SparkPost 8 | { 9 | public class GetMetricsResponse : Response 10 | { 11 | public IList> Results { get; set; } 12 | 13 | public GetMetricsResponse() 14 | { 15 | Results = new List>(); 16 | } 17 | 18 | public GetMetricsResponse(Response source) 19 | { 20 | this.SetFrom(source); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/SparkPost/GetSendingDomainResponse.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace SparkPost 4 | { 5 | public class GetSendingDomainResponse : Response 6 | { 7 | public SendingDomain SendingDomain { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/SparkPost/IClient.cs: -------------------------------------------------------------------------------- 1 | namespace SparkPost 2 | { 3 | /// 4 | /// Provides access to the SparkPost API. 5 | /// 6 | public interface IClient 7 | { 8 | /// 9 | /// Gets or sets the key used for requests to the SparkPost API. 10 | /// 11 | string ApiKey { get; set; } 12 | 13 | /// 14 | /// Gets or sets the base URL of the SparkPost API. 15 | /// 16 | string ApiHost { get; set; } 17 | 18 | /// 19 | /// Gets access to the transmissions resource of the SparkPost API. 20 | /// 21 | ITransmissions Transmissions { get; } 22 | 23 | /// 24 | /// Gets access to the suppressions resource of the SparkPost API. 25 | /// 26 | ISuppressions Suppressions { get; } 27 | 28 | /// 29 | /// Gets access to the subaccounts resource of the SparkPost API. 30 | /// 31 | ISubaccounts Subaccounts { get; } 32 | 33 | /// 34 | /// Gets access to the webhooks resource of the SparkPost API. 35 | /// 36 | IWebhooks Webhooks { get; } 37 | 38 | /// 39 | /// Gets access to the message events resource of the SparkPost API. 40 | /// 41 | IMessageEvents MessageEvents { get; } 42 | 43 | /// 44 | /// Gets access to the inbound domains resource of the SparkPost API. 45 | /// 46 | IInboundDomains InboundDomains { get; } 47 | 48 | /// 49 | /// Gets access to the sending domains resource of the SparkPost API. 50 | /// 51 | ISendingDomains SendingDomains { get; } 52 | 53 | /// 54 | /// Gets access to the relay webhooks resource of the SparkPost API. 55 | /// 56 | IRelayWebhooks RelayWebhooks { get; } 57 | 58 | IRecipientLists RecipientLists { get; } 59 | 60 | /// 61 | /// Gets access to the Templates resource of the SparkPost API. 62 | /// 63 | ITemplates Templates { get; } 64 | 65 | /// 66 | /// Gets access to the metrics resource of the SparkPost API. 67 | /// 68 | IMetrics Metrics { get; } 69 | 70 | /// 71 | /// Gets access to the Email Recipient Validation resource of the SparkPost API. 72 | /// 73 | IRecipientValidation RecipientValidations { get; } 74 | 75 | /// 76 | /// Gets the API version supported by this client. 77 | /// 78 | string Version { get; } 79 | 80 | /// 81 | /// Get the custom settings for this client. 82 | /// 83 | Client.Settings CustomSettings { get; } 84 | 85 | /// 86 | /// Gets the sub account. 87 | /// 88 | long SubaccountId { get; } 89 | 90 | Events Events { get; } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/SparkPost/IMessageEvents.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace SparkPost 4 | { 5 | public interface IMessageEvents 6 | { 7 | Task List(); 8 | Task List(object query); 9 | Task SamplesOf(string events); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/SparkPost/IRecipientLists.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | namespace SparkPost 5 | { 6 | public interface IRecipientLists 7 | { 8 | /// 9 | /// Creates a recipient list. 10 | /// 11 | /// The properties of the recipientList to create. 12 | /// The response from the API. 13 | Task Create(RecipientList recipientList); 14 | 15 | /// 16 | /// Retrieves a recipient list. 17 | /// 18 | /// The id of the recipient list to retrieve. 19 | /// The response from the API. 20 | Task Retrieve(string recipientListsId); 21 | 22 | /// 23 | /// Deletes a recipient list. 24 | /// 25 | /// 26 | /// A success or failure. 27 | Task Delete(string id); 28 | 29 | /// 30 | /// Updates a recipient list. 31 | /// 32 | /// 33 | /// 34 | Task Update(RecipientList recipientList); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/SparkPost/IRecipientValidation.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace SparkPost 4 | { 5 | public interface IRecipientValidation 6 | { 7 | Task Create(string emailAddress); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/SparkPost/IRequestMethod.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using System.Threading.Tasks; 3 | 4 | namespace SparkPost 5 | { 6 | public interface IRequestMethod 7 | { 8 | bool CanExecute(Request request); 9 | Task Execute(Request request); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/SparkPost/ISendingDomains.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace SparkPost 4 | { 5 | public interface ISendingDomains 6 | { 7 | Task List(); 8 | Task Create(SendingDomain sendingDomain); 9 | Task Update(SendingDomain sendingDomain); 10 | Task Retrieve(string domain); 11 | Task Delete(string domain); 12 | Task Verify(VerifySendingDomain verifySendingDomain); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/SparkPost/ISubaccounts.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace SparkPost 4 | { 5 | public interface ISubaccounts 6 | { 7 | Task List(); 8 | 9 | Task Create(SubaccountCreate subaccount); 10 | 11 | Task Update(SubaccountUpdate subaccount); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/SparkPost/ISuppressions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | namespace SparkPost 5 | { 6 | public interface ISuppressions 7 | { 8 | Task List(SuppressionsQuery supppressionsQuery); 9 | Task List(object query = null); 10 | Task Retrieve(string email); 11 | Task CreateOrUpdate(IEnumerable emails); 12 | Task CreateOrUpdate(IEnumerable suppressions); 13 | Task Delete(string email); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/SparkPost/ITemplates.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace SparkPost 4 | { 5 | /// 6 | /// Provides access to the templates resource of the SparkPost API. 7 | /// 8 | public interface ITemplates 9 | { 10 | /// 11 | /// Creates an email template. 12 | /// 13 | /// The properties of the template to create. 14 | /// The response from the API. 15 | Task Create(Template template); 16 | 17 | /// 18 | /// Retrieves an email template. 19 | /// 20 | /// The id of the template to retrieve. 21 | /// If true, returns the most recent draft template. If false, returns the most recent published template. If not provided, returns the most recent template version regardless of draft or published. 22 | /// The response from the API. 23 | Task Retrieve(string templateId, bool? draft = null); 24 | 25 | Task List(); 26 | 27 | Task Delete(string templateId); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/SparkPost/ITransmissions.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace SparkPost 4 | { 5 | /// 6 | /// Provides access to the transmissions resource of the SparkPost API. 7 | /// 8 | public interface ITransmissions 9 | { 10 | /// 11 | /// Sends an email transmission. 12 | /// 13 | /// The properties of the transmission to send. 14 | /// The response from the API. 15 | Task Send(Transmission transmission); 16 | 17 | /// 18 | /// Retrieves an email transmission. 19 | /// 20 | /// The id of the transmission to retrieve. 21 | /// The response from the API. 22 | Task Retrieve(string transmissionId); 23 | 24 | /// 25 | /// Lists recent email transmissions. 26 | /// 27 | /// The response from the API. 28 | Task List(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/SparkPost/IValueMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SparkPost 4 | { 5 | public interface IValueMapper 6 | { 7 | bool CanMap(Type propertyType, object value); 8 | object Map(Type propertyType, object value); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/SparkPost/InboundDomain.cs: -------------------------------------------------------------------------------- 1 | namespace SparkPost 2 | { 3 | public class InboundDomain 4 | { 5 | public string Domain { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/SparkPost/InboundDomainResponse.cs: -------------------------------------------------------------------------------- 1 | using SparkPost.Utilities; 2 | 3 | namespace SparkPost 4 | { 5 | public class InboundDomainResponse : Response 6 | { 7 | public InboundDomain InboundDomain { get; set; } 8 | 9 | public static InboundDomainResponse CreateFromResponse(Response response) 10 | { 11 | var result = new InboundDomainResponse(); 12 | LeftRight.SetValuesToMatch(result, response); 13 | 14 | var results = Jsonification.DeserializeObject(response.Content).results; 15 | 16 | result.InboundDomain = ListInboundDomainResponse.ConvertToAInboundDomain(results); 17 | 18 | return result; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/SparkPost/InboundDomains.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Threading.Tasks; 3 | using SparkPost.RequestSenders; 4 | 5 | namespace SparkPost 6 | { 7 | public interface IInboundDomains 8 | { 9 | Task List(object query = null); 10 | Task Create(InboundDomain inboundDomain); 11 | Task Retrieve(string domain); 12 | Task Delete(string domain); 13 | } 14 | 15 | public class InboundDomains : IInboundDomains 16 | { 17 | private readonly IClient client; 18 | private readonly IDataMapper dataMapper; 19 | private readonly IRequestSender requestSender; 20 | 21 | public InboundDomains(IClient client, IRequestSender requestSender, IDataMapper dataMapper) 22 | { 23 | this.client = client; 24 | this.requestSender = requestSender; 25 | this.dataMapper = dataMapper; 26 | } 27 | 28 | public async Task List(object query = null) 29 | { 30 | if (query == null) 31 | query = new { }; 32 | var request = new Request 33 | { 34 | Url = $"/api/{client.Version}/inbound-domains", 35 | Method = "GET", 36 | Data = query 37 | }; 38 | 39 | var response = await requestSender.Send(request); 40 | if (response.StatusCode != HttpStatusCode.OK) 41 | throw new ResponseException(response); 42 | 43 | return ListInboundDomainResponse.CreateFromResponse(response); 44 | } 45 | 46 | public async Task Retrieve(string domain) 47 | { 48 | var request = new Request { Url = $"/api/{client.Version}/inbound-domains/{domain}", Method = "GET" }; 49 | 50 | var response = await requestSender.Send(request); 51 | if (response.StatusCode != HttpStatusCode.OK) 52 | throw new ResponseException(response); 53 | 54 | return InboundDomainResponse.CreateFromResponse(response); 55 | } 56 | 57 | public async Task Create(InboundDomain inboundDomain) 58 | { 59 | var request = new Request 60 | { 61 | Url = $"api/{client.Version}/inbound-domains", 62 | Method = "POST", 63 | Data = dataMapper.ToDictionary(inboundDomain) 64 | }; 65 | 66 | var response = await requestSender.Send(request); 67 | if (response.StatusCode != HttpStatusCode.OK) 68 | throw new ResponseException(response); 69 | 70 | var createInboundDomainResponse = new Response(); 71 | LeftRight.SetValuesToMatch(createInboundDomainResponse, response); 72 | return createInboundDomainResponse; 73 | } 74 | 75 | public async Task Delete(string domain) 76 | { 77 | var request = new Request { Url = $"/api/{client.Version}/inbound-domains/{domain}", Method = "DELETE" }; 78 | 79 | var response = await requestSender.Send(request); 80 | return response.StatusCode == HttpStatusCode.OK; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/SparkPost/InlineImage.cs: -------------------------------------------------------------------------------- 1 | namespace SparkPost 2 | { 3 | public class InlineImage : File { } 4 | } 5 | -------------------------------------------------------------------------------- /src/SparkPost/LeftRight.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | 3 | namespace SparkPost 4 | { 5 | public static class LeftRight 6 | { 7 | public static void SetValuesToMatch(object left, object right) 8 | { 9 | var leftProperties = left.GetType().GetProperties(); 10 | var rightProperties = left.GetType().GetProperties(); 11 | foreach (var rightProperty in rightProperties) 12 | { 13 | try 14 | { 15 | var leftProperty = leftProperties.FirstOrDefault(x => x.Name == rightProperty.Name); 16 | leftProperty?.SetValue(left, rightProperty.GetValue(right)); 17 | } 18 | catch 19 | { 20 | // ignore 21 | } 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/SparkPost/ListInboundDomainResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using SparkPost.Utilities; 3 | 4 | namespace SparkPost 5 | { 6 | public class ListInboundDomainResponse : Response 7 | { 8 | public IEnumerable InboundDomains { get; set; } 9 | 10 | public static ListInboundDomainResponse CreateFromResponse(Response response) 11 | { 12 | var thisResponse = new ListInboundDomainResponse(); 13 | 14 | LeftRight.SetValuesToMatch(thisResponse, response); 15 | 16 | thisResponse.InboundDomains = BuildTheInboundDomainsFrom(response); 17 | 18 | return thisResponse; 19 | } 20 | 21 | private static IEnumerable BuildTheInboundDomainsFrom(Response response) 22 | { 23 | dynamic results = Jsonification.DeserializeObject(response.Content).results; 24 | 25 | var inboundDomains = new List(); 26 | foreach (var r in results) 27 | inboundDomains.Add(ConvertToAInboundDomain(r)); 28 | 29 | return inboundDomains; 30 | } 31 | 32 | internal static InboundDomain ConvertToAInboundDomain(dynamic item) 33 | { 34 | return new InboundDomain { Domain = item.domain }; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/SparkPost/ListMessageEventsResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace SparkPost 4 | { 5 | public class ListMessageEventsResponse : Response 6 | { 7 | public ListMessageEventsResponse() 8 | { 9 | MessageEvents = new MessageEvent[] { }; 10 | Links = new PageLink[] { }; 11 | } 12 | 13 | public IEnumerable MessageEvents { get; set; } 14 | 15 | public IList Links { get; set; } 16 | 17 | public int TotalCount { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/SparkPost/ListRelayWebhookResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using SparkPost.Utilities; 3 | 4 | namespace SparkPost 5 | { 6 | public class ListRelayWebhookResponse : Response 7 | { 8 | public IEnumerable RelayWebhooks { get; set; } 9 | 10 | public static ListRelayWebhookResponse CreateFromResponse(Response response) 11 | { 12 | var result = new ListRelayWebhookResponse(); 13 | 14 | LeftRight.SetValuesToMatch(result, response); 15 | 16 | result.RelayWebhooks = BuildTheRelayWebhooksFrom(response); 17 | 18 | return result; 19 | } 20 | 21 | private static IEnumerable BuildTheRelayWebhooksFrom(Response response) 22 | { 23 | var results = Jsonification.DeserializeObject(response.Content).results; 24 | 25 | var relayWebhooks = new List(); 26 | foreach (var r in results) 27 | relayWebhooks.Add(ConvertToARelayWebhook(r)); 28 | 29 | return relayWebhooks; 30 | } 31 | 32 | internal static RelayWebhook ConvertToARelayWebhook(dynamic item) 33 | { 34 | return new RelayWebhook 35 | { 36 | Id = item.id, 37 | Name = item.name, 38 | Target = item.target, 39 | AuthToken = item.auth_token, 40 | Match = new RelayWebhookMatch { Protocol = item.match.protocol, Domain = item.match.domain } 41 | }; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/SparkPost/ListSendingDomainResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using SparkPost.Utilities; 3 | 4 | namespace SparkPost 5 | { 6 | public class ListSendingDomainResponse : Response 7 | { 8 | public IEnumerable SendingDomains { get; set; } 9 | 10 | public static ListSendingDomainResponse CreateFromResponse(Response response) 11 | { 12 | var result = new ListSendingDomainResponse(); 13 | LeftRight.SetValuesToMatch(result, response); 14 | 15 | var results = Jsonification.DeserializeObject(response.Content).results; 16 | result.SendingDomains = BuildTheSendingDomains(results); 17 | return result; 18 | } 19 | 20 | private static IEnumerable BuildTheSendingDomains(dynamic results) 21 | { 22 | var sendingDomains = new List(); 23 | foreach (var r in results) 24 | sendingDomains.Add(SendingDomain.ConvertToSendingDomain(r)); 25 | return sendingDomains; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/SparkPost/ListSubaccountResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using SparkPost.Utilities; 4 | 5 | namespace SparkPost 6 | { 7 | public class ListSubaccountResponse : Response 8 | { 9 | public IEnumerable Subaccounts { get; set; } 10 | 11 | public static ListSubaccountResponse CreateFromResponse(Response response) 12 | { 13 | var result = new ListSubaccountResponse(); 14 | LeftRight.SetValuesToMatch(result, response); 15 | 16 | var results = Jsonification.DeserializeObject(response.Content).results; 17 | var subaccounts = new List(); 18 | foreach (var r in results) 19 | subaccounts.Add(ConvertToSubaccount(r)); 20 | 21 | result.Subaccounts = subaccounts; 22 | return result; 23 | } 24 | 25 | private static Subaccount ConvertToSubaccount(dynamic result) 26 | { 27 | return new Subaccount 28 | { 29 | Id = result.id, 30 | Name = result.name, 31 | Status = Enum.Parse(typeof(SubaccountStatus), result.status.ToString(), true), 32 | ComplianceStatus = result.compliance_status 33 | }; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/SparkPost/ListSuppressionResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace SparkPost 4 | { 5 | public class ListSuppressionResponse : Response 6 | { 7 | public IEnumerable Suppressions { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/SparkPost/ListTransmissionResponse.cs: -------------------------------------------------------------------------------- 1 | namespace SparkPost 2 | { 3 | public class ListTransmissionResponse : Response { } 4 | } 5 | -------------------------------------------------------------------------------- /src/SparkPost/ListWebhookResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using SparkPost.Utilities; 3 | 4 | namespace SparkPost 5 | { 6 | public class ListWebhookResponse : Response 7 | { 8 | public IEnumerable Webhooks { get; set; } 9 | 10 | public static ListWebhookResponse CreateFromResponse(Response response) 11 | { 12 | var result = new ListWebhookResponse(); 13 | LeftRight.SetValuesToMatch(result, response); 14 | 15 | var results = Jsonification.DeserializeObject(result.Content).results; 16 | var webhooks = new List(); 17 | foreach (var r in results) 18 | webhooks.Add(ConvertToAWebhook(r)); 19 | 20 | result.Webhooks = webhooks; 21 | return result; 22 | } 23 | 24 | internal static Webhook ConvertToAWebhook(dynamic r) 25 | { 26 | var events = new List(); 27 | foreach (var i in r.events) 28 | events.Add(i.ToString()); 29 | var webhook = new Webhook 30 | { 31 | Id = r.id, 32 | Name = r.name, 33 | Target = r.target, 34 | Events = events, 35 | AuthType = r.auth_type, 36 | AuthRequestDetails = r.auth_request_details, 37 | AuthCredentials = r.auth_credentials, 38 | AuthToken = r.auth_token, 39 | LastSuccessful = r.last_successful, 40 | LastFailure = r.last_failure 41 | }; 42 | return webhook; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/SparkPost/MessageEventSampleResponse.cs: -------------------------------------------------------------------------------- 1 | namespace SparkPost 2 | { 3 | public class MessageEventSampleResponse : Response { } 4 | } 5 | -------------------------------------------------------------------------------- /src/SparkPost/MessageEventType.cs: -------------------------------------------------------------------------------- 1 | namespace SparkPost 2 | { 3 | // Values taken from: 4 | // https://developers.sparkpost.com/api/#/reference/message-events/message-events 5 | // Additional values and descriptions taken from: 6 | // https://support.sparkpost.com/customer/portal/articles/1976204-webhook-event-reference 7 | 8 | public enum MessageEventType 9 | { 10 | /// 11 | /// Undefined/unknown or unable to parse. 12 | /// 13 | Undefined, 14 | 15 | /// 16 | /// delivery 17 | /// Delivery. 18 | /// Remote MTA acknowledged receipt of a message. 19 | /// 20 | Delivery, 21 | 22 | /// 23 | /// injection 24 | /// Injection. 25 | /// Message is received by or injected into SparkPost. 26 | /// 27 | Injection, 28 | 29 | /// 30 | /// bounce 31 | /// Bounce. 32 | /// Remote MTA has permanently rejected a message. 33 | /// 34 | Bounce, 35 | 36 | /// 37 | /// delay 38 | /// Delay. 39 | /// Remote MTA has temporarily rejected a message. 40 | /// 41 | Delay, 42 | 43 | /// 44 | /// policy_rejection 45 | /// Policy Rejection. 46 | /// Due to policy, SparkPost rejected a message or failed to generate a message. 47 | /// 48 | PolicyRejection, 49 | 50 | /// 51 | /// out_of_band 52 | /// Out of Band. 53 | /// Remote MTA initially reported acceptance of a message, but it has since asynchronously reported that the message was not delivered. 54 | /// 55 | OutOfBand, 56 | 57 | /// 58 | /// open 59 | /// Open. 60 | /// Recipient opened a message in a mail client, thus rendering a tracking pixel. 61 | /// 62 | Open, 63 | 64 | /// 65 | /// click 66 | /// Click. 67 | /// Recipient clicked a tracked link in a message, thus prompting a redirect through the SparkPost click-tracking server to the link's destination. 68 | /// 69 | Click, 70 | 71 | /// 72 | /// generation_failure 73 | /// Generation Failure. 74 | /// Message generation failed for an intended recipient. 75 | /// 76 | GenerationFailure, 77 | 78 | /// 79 | /// generation_rejection 80 | /// Generation Rejection. 81 | /// SparkPost rejected message generation due to policy. 82 | /// 83 | GenerationRejection, 84 | 85 | /// 86 | /// spam_complaint 87 | /// Spam Complaint. 88 | /// Message was classified as spam by the recipient. 89 | /// 90 | SpamComplaint, 91 | 92 | /// 93 | /// list_unsubscribe 94 | /// List Unsubscribe. 95 | /// User clicked the 'unsubscribe' button on an email client. 96 | /// 97 | ListUnsubscribe, 98 | 99 | /// 100 | /// link_unsubscribe 101 | /// Link Unsubscribe. 102 | /// User clicked a hyperlink in a received email. 103 | /// 104 | LinkUnsubscribe, 105 | 106 | /// 107 | /// sms_status 108 | /// SMS Status. 109 | /// SMPP/SMS message produced a status log output. 110 | /// 111 | SmsStatus, 112 | 113 | /// 114 | /// relay_injection 115 | /// Relay Injection. 116 | /// Relayed message is received by or injected into SparkPost. 117 | /// 118 | RelayInjection, 119 | 120 | /// 121 | /// relay_rejection 122 | /// Relay Rejection. 123 | /// SparkPost rejected a relayed message or failed to generate a relayed message. 124 | /// 125 | RelayRejection, 126 | 127 | /// 128 | /// relay_delivery 129 | /// Relay Delivery. 130 | /// Remote HTTP Endpoint acknowledged receipt of a relayed message. 131 | /// 132 | RelayDelivery, 133 | 134 | /// 135 | /// relay_tempfail 136 | /// Relay Temporary Failure. 137 | /// Remote HTTP Endpoint has failed to accept a relayed message. 138 | /// 139 | RelayTempfail, 140 | 141 | /// 142 | /// relay_permfail 143 | /// Relay Permanent Failure. 144 | /// Relayed message has reached the maximum retry threshold and will be removed from the system. 145 | /// 146 | RelayPermfail 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/SparkPost/MessageEventsQuery.cs: -------------------------------------------------------------------------------- 1 | using SparkPost.Utilities; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace SparkPost 7 | { 8 | public class MessageEventsQuery 9 | { 10 | public MessageEventsQuery() 11 | { 12 | this.Events = new List(); 13 | this.BounceClasses = new List(); 14 | this.CampaignIds = new List(); 15 | this.FriendlyFroms = new List(); 16 | this.MessageIds = new List(); 17 | this.Recipients = new List(); 18 | this.Subaccounts = new List(); 19 | this.TemplateIds = new List(); 20 | this.TransmissionIds = new List(); 21 | } 22 | 23 | /// 24 | /// bounce_classes : Number : Comma-delimited list of bounce classification codes to search. 25 | /// See Bounce Classification Codes at https://support.sparkpost.com/customer/portal/articles/1929896. 26 | /// Example: 1,10,20. 27 | /// 28 | public IList BounceClasses { get; set; } 29 | 30 | /// 31 | /// campaign_ids : ? : (optional, string, `Example Campaign Name`) ... Comma-delimited list of campaign ID's to search (i.e. campaign_id used during creation of a transmission). 32 | /// 33 | public IList CampaignIds { get; set; } 34 | 35 | /// 36 | /// events : List : Comma-delimited list of event types to search. Defaults to all event types. 37 | /// Example: delivery, injection, bounce, delay, policy_rejection, out_of_band, open, click, generation_failure, generation_rejection, spam_complaint, list_unsubscribe, link_unsubscribe. 38 | /// 39 | public IList Events { get; set; } 40 | 41 | /// 42 | /// friendly_froms : ? : (optional, list, `sender@mail.example.com`) ... Comma-delimited list of friendly_froms to search. 43 | /// 44 | public IList FriendlyFroms { get; set; } 45 | 46 | /// 47 | /// from : Datetime : Datetime in format of YYYY-MM-DDTHH:MM. 48 | /// Example: 2014-07-20T08:00. 49 | /// Default: One hour ago. 50 | /// 51 | public DateTime? From { get; set; } 52 | 53 | /// 54 | /// message_ids : List : Comma-delimited list of message ID's to search. 55 | /// Example: 0e0d94b7-9085-4e3c-ab30-e3f2cd9c273e. 56 | /// 57 | public IList MessageIds { get; set; } 58 | 59 | /// 60 | /// page : number : The results page number to return. Used with per_page for paging through results. 61 | /// Example: 25. 62 | /// Default: 1. 63 | /// 64 | public int? Page { get; set; } 65 | 66 | /// 67 | /// per_page : Number : Number of results to return per page. Must be between 1 and 10,000 (inclusive). 68 | /// Example: 100. 69 | /// Default: 1000. 70 | /// 71 | public int? PerPage { get; set; } 72 | 73 | /// 74 | /// reason : String :Bounce/failure/rejection reason that will be matched using a wildcard(e.g., %reason%). 75 | /// Example: bounce. 76 | /// 77 | public string Reason { get; set; } 78 | 79 | /// 80 | /// recipients : List : Comma-delimited list of recipients to search. 81 | /// Example: recipient @example.com. 82 | /// 83 | public IList Recipients { get; set; } 84 | 85 | /// 86 | /// subaccounts : List : Comma-delimited list of subaccount ID's to search. 87 | /// Example: 101. 88 | /// 89 | public IList Subaccounts { get; set; } 90 | 91 | /// 92 | /// template_ids : List : Comma-delimited list of template ID's to search. 93 | /// Example: templ-1234. 94 | /// 95 | public IList TemplateIds { get; set; } 96 | 97 | /// 98 | /// timezone : String : Standard timezone identification string. 99 | /// Example: America/New_York. 100 | /// Default: UTC. 101 | /// 102 | public string Timezone { get; set; } 103 | 104 | /// 105 | /// to : Datetime : Datetime in format of YYYY-MM-DDTHH:MM. 106 | /// Example: 2014-07-20T09:00. 107 | /// Default: now. 108 | /// 109 | public DateTime? To { get; set; } 110 | 111 | /// 112 | /// transmission_ids : List : Comma-delimited list of transmission ID's to search (i.e. id generated during creation of a transmission). 113 | /// Example: 65832150921904138. 114 | /// 115 | public IList TransmissionIds { get; set; } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/SparkPost/MetricsQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SparkPost 8 | { 9 | public class MetricsQuery 10 | { 11 | public DateTime? From { get; set; } 12 | public DateTime? To { get; set; } 13 | public IList Domains { get; set; } 14 | public IList Campaigns { get; set; } 15 | public IList Templates { get; set; } 16 | public IList SendingIps { get; set; } 17 | public IList IpPools { get; set; } 18 | public IList SendingDomains { get; set; } 19 | public IList Subaccounts { get; set; } 20 | public IList Metrics { get; set; } 21 | public string Timezone { get; set; } 22 | public string Precision { get; set; } 23 | 24 | public MetricsQuery() 25 | { 26 | Domains = new List(); 27 | Campaigns = new List(); 28 | Templates = new List(); 29 | SendingIps = new List(); 30 | IpPools = new List(); 31 | SendingDomains = new List(); 32 | Subaccounts = new List(); 33 | Metrics = new List(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/SparkPost/MetricsResourceQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SparkPost 8 | { 9 | public class MetricsResourceQuery 10 | { 11 | /// 12 | /// Only return results containing this string 13 | /// 14 | public string Match { get; set; } 15 | 16 | /// 17 | /// Maximum number of results to return 18 | /// 19 | public int? Limit { get; set; } 20 | public DateTime? From { get; set; } 21 | public DateTime? To { get; set; } 22 | 23 | /// 24 | /// Standard timezone identification string, defaults to UTC 25 | /// 26 | public string Timezone { get; set; } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/SparkPost/Options.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SparkPost 4 | { 5 | public class Options 6 | { 7 | public DateTimeOffset? StartTime { get; set; } 8 | public bool? OpenTracking { get; set; } 9 | public bool? ClickTracking { get; set; } 10 | public bool? Transactional { get; set; } 11 | public bool? Sandbox { get; set; } 12 | public bool? SkipSuppression { get; set; } 13 | public bool? InlineCss { get; set; } 14 | public string IpPool { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/SparkPost/PageLink.cs: -------------------------------------------------------------------------------- 1 | namespace SparkPost 2 | { 3 | public class PageLink 4 | { 5 | public string Href { get; set; } 6 | public string Type { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/SparkPost/Recipient.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace SparkPost 4 | { 5 | public class Recipient 6 | { 7 | public Recipient() 8 | { 9 | Address = new Address(); 10 | Tags = new List(); 11 | Metadata = new Dictionary(); 12 | SubstitutionData = new Dictionary(); 13 | } 14 | 15 | public Address Address { get; set; } 16 | public string ReturnPath { get; set; } 17 | public IList Tags { get; set; } 18 | public IDictionary Metadata { get; set; } 19 | public IDictionary SubstitutionData { get; set; } 20 | 21 | public RecipientType Type { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/SparkPost/RecipientList.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace SparkPost 4 | { 5 | public class RecipientList 6 | { 7 | public RecipientList() 8 | { 9 | Recipients = new List(); 10 | Attributes = new Attributes(); 11 | } 12 | 13 | public List Recipients { get; set; } 14 | 15 | public string Id { get; set; } 16 | 17 | public string Name { get; set; } 18 | 19 | public string Description { get; set; } 20 | 21 | public Attributes Attributes { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/SparkPost/RecipientLists.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Threading.Tasks; 6 | using Newtonsoft.Json; 7 | using SparkPost.RequestSenders; 8 | 9 | namespace SparkPost 10 | { 11 | public class RecipientLists : IRecipientLists 12 | { 13 | private readonly Client client; 14 | private readonly IRequestSender requestSender; 15 | private readonly DataMapper dataMapper; 16 | 17 | public RecipientLists(Client client, IRequestSender requestSender, DataMapper dataMapper) 18 | { 19 | this.client = client; 20 | this.requestSender = requestSender; 21 | this.dataMapper = dataMapper; 22 | } 23 | 24 | public async Task Retrieve(string recipientListsId) 25 | { 26 | var request = new Request 27 | { 28 | Url = $"api/{client.Version}/recipient-lists/" + recipientListsId + "?show_recipients=true", 29 | Method = "GET", 30 | }; 31 | 32 | var response = await requestSender.Send(request); 33 | 34 | if (new[] { HttpStatusCode.OK, HttpStatusCode.NotFound }.Contains(response.StatusCode) == false) 35 | throw new ResponseException(response); 36 | 37 | var recipientListsResponse = new RetrieveRecipientListsResponse() 38 | { 39 | ReasonPhrase = response.ReasonPhrase, 40 | StatusCode = response.StatusCode, 41 | Content = response.Content, 42 | }; 43 | 44 | var results = JsonConvert.DeserializeObject(response.Content).results; 45 | if (results.recipients == null) 46 | return recipientListsResponse; 47 | 48 | recipientListsResponse.TotalAcceptedRecipients = results.total_accepted_recipients; 49 | 50 | recipientListsResponse.RecipientList = new RecipientList 51 | { 52 | Id = results.id, 53 | Recipients = RetrieveRecipientListsResponse.CreateFromResponse(response), 54 | Attributes = 55 | results.attributes != null 56 | ? new Attributes { InternalId = results.attributes.internal_id, ListGroupId = results.attributes.list_group_id } 57 | : null, 58 | Description = results.description, 59 | Name = results.name 60 | }; 61 | 62 | return recipientListsResponse; 63 | } 64 | 65 | public async Task Delete(string id) 66 | { 67 | var request = new Request { Url = $"/api/{client.Version}/recipient-lists/{id}", Method = "DELETE" }; 68 | 69 | var response = await requestSender.Send(request); 70 | return response.StatusCode == HttpStatusCode.NoContent; 71 | } 72 | 73 | public async Task Update(RecipientList recipientList) 74 | { 75 | var request = new Request 76 | { 77 | Url = $"api/{client.Version}/recipient-lists/{recipientList.Id}", 78 | Method = "PUT", 79 | Data = dataMapper.ToDictionary(recipientList) 80 | }; 81 | 82 | var response = await requestSender.Send(request); 83 | 84 | if (new[] { HttpStatusCode.OK, HttpStatusCode.NotFound }.Contains(response.StatusCode) == false) 85 | throw new ResponseException(response); 86 | 87 | return new UpdateRecipientListResponse() 88 | { 89 | ReasonPhrase = response.ReasonPhrase, 90 | StatusCode = response.StatusCode, 91 | Content = response.Content, 92 | }; 93 | } 94 | 95 | public async Task Create(RecipientList recipientList) 96 | { 97 | var request = new Request 98 | { 99 | Url = $"api/{client.Version}/recipient-lists?num_rcpt_errors=3", 100 | Method = "POST", 101 | Data = dataMapper.ToDictionary(recipientList) 102 | }; 103 | 104 | var response = await requestSender.Send(request); 105 | 106 | if (response.StatusCode != HttpStatusCode.OK) 107 | throw new ResponseException(response); 108 | 109 | var results = JsonConvert.DeserializeObject(response.Content).results; 110 | return new SendRecipientListsResponse() 111 | { 112 | Id = results.id, 113 | TotalAcceptedRecipients = results.total_accepted_recipients, 114 | TotalRejectedRecipients = results.total_rejected_recipients, 115 | Name = results.name, 116 | Content = response.Content, 117 | StatusCode = response.StatusCode, 118 | ReasonPhrase = response.ReasonPhrase 119 | }; 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/SparkPost/RecipientType.cs: -------------------------------------------------------------------------------- 1 | namespace SparkPost 2 | { 3 | public enum RecipientType 4 | { 5 | To, 6 | CC, 7 | BCC 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/SparkPost/RecipientValidation.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Threading.Tasks; 3 | using SparkPost.RequestSenders; 4 | 5 | namespace SparkPost 6 | { 7 | public class RecipientValidation : IRecipientValidation 8 | { 9 | private readonly IClient client; 10 | private readonly IRequestSender requestSender; 11 | 12 | public RecipientValidation(IClient client, IRequestSender requestSender) 13 | { 14 | this.client = client; 15 | this.requestSender = requestSender; 16 | } 17 | 18 | public async Task Create(string emailAddress) 19 | { 20 | var request = new Request { Url = $"/api/{client.Version}/recipient-validation/single/{emailAddress}", Method = "GET" }; 21 | 22 | var response = await requestSender.Send(request); 23 | if (response.StatusCode != HttpStatusCode.OK) 24 | { 25 | throw new ResponseException(response); 26 | } 27 | 28 | return RetrieveEmailValidationResponse.CreateFromResponse(response); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/SparkPost/RelayWebhook.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace SparkPost 5 | { 6 | public class RelayWebhook 7 | { 8 | public RelayWebhook() 9 | { 10 | Match = new RelayWebhookMatch(); 11 | } 12 | 13 | public string Id { get; set; } 14 | public string Target { get; set; } 15 | public string Name { get; set; } 16 | public string AuthToken { get; set; } 17 | public RelayWebhookMatch Match { get; set; } 18 | } 19 | 20 | public class RelayWebhookMatch 21 | { 22 | public RelayWebhookMatch() 23 | { 24 | Protocol = "SMTP"; 25 | } 26 | 27 | public string Protocol { get; set; } 28 | public string Domain { get; set; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/SparkPost/RelayWebhooks.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Threading.Tasks; 3 | using SparkPost.RequestSenders; 4 | 5 | namespace SparkPost 6 | { 7 | public interface IRelayWebhooks 8 | { 9 | Task List(object query = null); 10 | Task Create(RelayWebhook webhook); 11 | Task Retrieve(string id); 12 | Task Update(RelayWebhook relayWebhook); 13 | Task Delete(string id); 14 | } 15 | 16 | public class RelayWebhooks : IRelayWebhooks 17 | { 18 | private readonly IClient client; 19 | private readonly IDataMapper dataMapper; 20 | private readonly IRequestSender requestSender; 21 | 22 | public RelayWebhooks(IClient client, IRequestSender requestSender, IDataMapper dataMapper) 23 | { 24 | this.client = client; 25 | this.requestSender = requestSender; 26 | this.dataMapper = dataMapper; 27 | } 28 | 29 | public async Task List(object query = null) 30 | { 31 | if (query == null) 32 | query = new { }; 33 | var request = new Request 34 | { 35 | Url = $"/api/{client.Version}/relay-webhooks", 36 | Method = "GET", 37 | Data = query 38 | }; 39 | 40 | var response = await requestSender.Send(request); 41 | if (response.StatusCode != HttpStatusCode.OK) 42 | throw new ResponseException(response); 43 | 44 | return ListRelayWebhookResponse.CreateFromResponse(response); 45 | } 46 | 47 | public async Task Retrieve(string id) 48 | { 49 | var request = new Request { Url = $"/api/{client.Version}/relay-webhooks/{id}", Method = "GET" }; 50 | 51 | var response = await requestSender.Send(request); 52 | if (response.StatusCode != HttpStatusCode.OK) 53 | throw new ResponseException(response); 54 | 55 | return RetrieveRelayWebhookResponse.CreateFromResponse(response); 56 | } 57 | 58 | public async Task Create(RelayWebhook relayWebhook) 59 | { 60 | var request = new Request 61 | { 62 | Url = $"api/{client.Version}/relay-webhooks", 63 | Method = "POST", 64 | Data = dataMapper.ToDictionary(relayWebhook) 65 | }; 66 | 67 | var response = await requestSender.Send(request); 68 | if (response.StatusCode != HttpStatusCode.OK) 69 | throw new ResponseException(response); 70 | 71 | var createWebhookResponse = new Response(); 72 | LeftRight.SetValuesToMatch(createWebhookResponse, response); 73 | return createWebhookResponse; 74 | } 75 | 76 | public async Task Update(RelayWebhook relayWebhook) 77 | { 78 | var request = new Request 79 | { 80 | Url = $"api/{client.Version}/relay-webhooks/{relayWebhook.Id}", 81 | Method = "PUT", 82 | Data = dataMapper.ToDictionary(relayWebhook) 83 | }; 84 | 85 | var response = await requestSender.Send(request); 86 | if (response.StatusCode != HttpStatusCode.OK) 87 | throw new ResponseException(response); 88 | 89 | var createWebhookResponse = new Response(); 90 | LeftRight.SetValuesToMatch(createWebhookResponse, response); 91 | return createWebhookResponse; 92 | } 93 | 94 | public async Task Delete(string id) 95 | { 96 | var request = new Request { Url = $"/api/{client.Version}/relay-webhooks/{id}", Method = "DELETE" }; 97 | 98 | var response = await requestSender.Send(request); 99 | return response.StatusCode == HttpStatusCode.OK; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/SparkPost/Request.cs: -------------------------------------------------------------------------------- 1 | using SparkPost.Utilities; 2 | 3 | namespace SparkPost 4 | { 5 | public class Request 6 | { 7 | public string Url { get; set; } 8 | public object Data { get; set; } 9 | public string Method { get; set; } 10 | 11 | public string ToJson() 12 | { 13 | return Jsonification.SerializeObject(this); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/SparkPost/RequestMethodFinder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Net.Http; 4 | using SparkPost.RequestMethods; 5 | 6 | namespace SparkPost 7 | { 8 | public interface IRequestMethodFinder 9 | { 10 | IRequestMethod FindFor(Request request); 11 | } 12 | 13 | public class RequestMethodFinder : IRequestMethodFinder 14 | { 15 | private readonly HttpClient client; 16 | private readonly IDataMapper dataMapper; 17 | 18 | public RequestMethodFinder(HttpClient client, IDataMapper dataMapper) 19 | { 20 | this.client = client; 21 | this.dataMapper = dataMapper; 22 | } 23 | 24 | public IRequestMethod FindFor(Request request) 25 | { 26 | return new List { new Delete(client), new Post(client), new Put(client), new Get(client, dataMapper) }.First( 27 | x => x.CanExecute(request) 28 | ); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/SparkPost/RequestMethods/Delete.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using System.Threading.Tasks; 3 | 4 | namespace SparkPost.RequestMethods 5 | { 6 | public class Delete : RequestMethod 7 | { 8 | private readonly HttpClient client; 9 | 10 | public Delete(HttpClient client) 11 | { 12 | this.client = client; 13 | } 14 | 15 | public override Task Execute(Request request) 16 | { 17 | return client.DeleteAsync(request.Url); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/SparkPost/RequestMethods/Get.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Net.Http; 7 | using System.Threading.Tasks; 8 | using System.Web; 9 | using SparkPost.Utilities; 10 | 11 | namespace SparkPost.RequestMethods 12 | { 13 | public class Get : RequestMethod 14 | { 15 | private readonly HttpClient client; 16 | private readonly IDataMapper dataMapper; 17 | 18 | public Get(HttpClient client, IDataMapper dataMapper) 19 | { 20 | this.client = client; 21 | this.dataMapper = dataMapper; 22 | } 23 | 24 | public override Task Execute(Request request) 25 | { 26 | return client.GetAsync( 27 | string.Join("?", new[] { request.Url, ConvertToQueryString(request.Data) }.Where(x => string.IsNullOrEmpty(x) == false)) 28 | ); 29 | } 30 | 31 | private string ConvertToQueryString(object data) 32 | { 33 | if (data == null) 34 | return null; 35 | var original = dataMapper.CatchAll(data); 36 | 37 | var dictionary = new Dictionary(); 38 | foreach (var thing in original.Where(x => string.IsNullOrEmpty(x.Value.ToString()) == false)) 39 | dictionary[thing.Key] = thing.Value.ToString(); 40 | 41 | var values = dictionary.Select(x => WebUtility.UrlEncode(SnakeCase.Convert(x.Key)) + "=" + WebUtility.UrlEncode(x.Value)); 42 | 43 | return string.Join("&", values); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/SparkPost/RequestMethods/Post.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using System.Threading.Tasks; 4 | 5 | namespace SparkPost.RequestMethods 6 | { 7 | public class Post : PutAndPostAreTheSame 8 | { 9 | public Post(HttpClient client) : base(client) { } 10 | 11 | public override Task Execute(string url, StringContent stringContent) 12 | { 13 | return Client.PostAsync(url, stringContent); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/SparkPost/RequestMethods/Put.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using System.Threading.Tasks; 3 | 4 | namespace SparkPost.RequestMethods 5 | { 6 | public class Put : PutAndPostAreTheSame 7 | { 8 | public Put(HttpClient client) : base(client) { } 9 | 10 | public override Task Execute(string url, StringContent stringContent) 11 | { 12 | return Client.PutAsync(url, stringContent); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/SparkPost/RequestMethods/PutAndPostAreTheSame.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using System.Text; 3 | using System.Threading.Tasks; 4 | using SparkPost.Utilities; 5 | 6 | namespace SparkPost.RequestMethods 7 | { 8 | public abstract class PutAndPostAreTheSame : RequestMethod 9 | { 10 | protected readonly HttpClient Client; 11 | 12 | protected PutAndPostAreTheSame(HttpClient client) 13 | { 14 | Client = client; 15 | } 16 | 17 | public override Task Execute(Request request) 18 | { 19 | return Execute(request.Url, ContentFrom(request)); 20 | } 21 | 22 | public abstract Task Execute(string url, StringContent stringContent); 23 | 24 | private static StringContent ContentFrom(Request request) 25 | { 26 | return new StringContent(SerializeObject(request.Data), Encoding.UTF8, "application/json"); 27 | } 28 | 29 | private static string SerializeObject(object data) 30 | { 31 | return Jsonification.SerializeObject(data); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/SparkPost/RequestMethods/RequestMethod.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using System.Threading.Tasks; 3 | 4 | namespace SparkPost.RequestMethods 5 | { 6 | public abstract class RequestMethod : IRequestMethod 7 | { 8 | public bool CanExecute(Request request) 9 | { 10 | return (request.Method ?? "").ToLower().StartsWith(GetType().Name.ToLower()); 11 | } 12 | 13 | public abstract Task Execute(Request request); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/SparkPost/RequestSenders/RequestSender.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Net.Http; 4 | using System.Threading.Tasks; 5 | 6 | namespace SparkPost.RequestSenders 7 | { 8 | public interface IRequestSender 9 | { 10 | Task Send(Request request); 11 | } 12 | 13 | public class RequestSender : IRequestSender 14 | { 15 | private readonly IClient client; 16 | private readonly IDataMapper dataMapper; 17 | 18 | public RequestSender(IClient client, IDataMapper dataMapper) 19 | { 20 | this.client = client; 21 | this.dataMapper = dataMapper; 22 | } 23 | 24 | public virtual async Task Send(Request request) 25 | { 26 | using (var httpClient = client.CustomSettings.CreateANewHttpClient()) 27 | { 28 | httpClient.BaseAddress = new Uri(client.ApiHost); 29 | httpClient.DefaultRequestHeaders.Add("Authorization", client.ApiKey); 30 | 31 | SetTheUserAgentIfItIsProvided(httpClient); 32 | 33 | if (client.SubaccountId != 0) 34 | httpClient.DefaultRequestHeaders.Add("X-MSYS-SUBACCOUNT", client.SubaccountId.ToString(CultureInfo.InvariantCulture)); 35 | 36 | var result = await GetTheResponse(request, httpClient); 37 | 38 | return new Response 39 | { 40 | StatusCode = result.StatusCode, 41 | ReasonPhrase = result.ReasonPhrase, 42 | Content = await result.Content.ReadAsStringAsync() 43 | }; 44 | } 45 | } 46 | 47 | private void SetTheUserAgentIfItIsProvided(HttpClient httpClient) 48 | { 49 | if (string.IsNullOrEmpty(client.CustomSettings.UserAgent) == false) 50 | httpClient.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", client.CustomSettings.UserAgent); 51 | } 52 | 53 | protected virtual async Task GetTheResponse(Request request, HttpClient httpClient) 54 | { 55 | return await new RequestMethodFinder(httpClient, dataMapper).FindFor(request).Execute(request); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/SparkPost/Response.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace SparkPost 4 | { 5 | public class Response 6 | { 7 | public HttpStatusCode StatusCode { get; set; } 8 | public string ReasonPhrase { get; set; } 9 | public string Content { get; set; } 10 | 11 | protected void SetFrom(Response source) 12 | { 13 | this.ReasonPhrase = source.ReasonPhrase; 14 | this.StatusCode = source.StatusCode; 15 | this.Content = source.Content; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/SparkPost/ResponseException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SparkPost 4 | { 5 | public class ResponseException : Exception 6 | { 7 | public ResponseException(Response response) 8 | : base(string.Format("Response: {0} {1}. Content: {2}.", ((int)response.StatusCode).ToString(), response.ReasonPhrase, response.Content)) 9 | { 10 | this.Response = response; 11 | } 12 | 13 | public Response Response { get; private set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/SparkPost/RetrieveEmailValidationResponse.cs: -------------------------------------------------------------------------------- 1 | using SparkPost.Utilities; 2 | 3 | namespace SparkPost 4 | { 5 | public class RetrieveEmailValidationResponse : Response 6 | { 7 | public static EmailValidationResponse CreateFromResponse(Response response) 8 | { 9 | var jsonData = Jsonification.DeserializeObject(response.Content).results; 10 | return EmailValidationResponse.ConvertToResponse(jsonData); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/SparkPost/RetrieveRecipientListsResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Newtonsoft.Json; 4 | 5 | namespace SparkPost 6 | { 7 | public class RetrieveRecipientListsResponse : Response 8 | { 9 | public int TotalAcceptedRecipients { get; set; } 10 | 11 | public RecipientList RecipientList { get; set; } 12 | 13 | public static List CreateFromResponse(Response response) 14 | { 15 | var result = new List(); 16 | 17 | var results = JsonConvert.DeserializeObject(response.Content).results; 18 | 19 | foreach (var r in results.recipients) 20 | result.Add(ConvertToRecipient(r)); 21 | 22 | return result; 23 | } 24 | 25 | internal static Recipient ConvertToRecipient(dynamic item) 26 | { 27 | return new Recipient 28 | { 29 | Address = new Address { Email = item.address.email, Name = item.address.name }, 30 | ReturnPath = item.return_path, 31 | Metadata = ConvertToADictionary(item.metadata), 32 | SubstitutionData = ConvertToADictionary(item.substitution_data) 33 | }; 34 | } 35 | 36 | private static dynamic ConvertToADictionary(dynamic @object) 37 | { 38 | return JsonConvert.DeserializeObject>(JsonConvert.SerializeObject(@object)); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/SparkPost/RetrieveRelayWebhookResponse.cs: -------------------------------------------------------------------------------- 1 | using SparkPost.Utilities; 2 | 3 | namespace SparkPost 4 | { 5 | public class RetrieveRelayWebhookResponse : Response 6 | { 7 | public RelayWebhook RelayWebhook { get; set; } 8 | 9 | public static RetrieveRelayWebhookResponse CreateFromResponse(Response response) 10 | { 11 | var result = new RetrieveRelayWebhookResponse(); 12 | LeftRight.SetValuesToMatch(result, response); 13 | 14 | var results = Jsonification.DeserializeObject(response.Content).results; 15 | 16 | result.RelayWebhook = ListRelayWebhookResponse.ConvertToARelayWebhook(results); 17 | 18 | return result; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/SparkPost/RetrieveTemplateResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SparkPost 8 | { 9 | public class RetrieveTemplateResponse : Response 10 | { 11 | public RetrieveTemplateResponse() 12 | { 13 | Options = new TemplateOptions(); 14 | TemplateContent = new TemplateContent(); 15 | } 16 | 17 | public string Id { get; set; } 18 | public string Name { get; set; } 19 | public string Description { get; set; } 20 | public bool Published { get; set; } 21 | public DateTime LastUpdateTime { get; set; } 22 | public DateTime? LastUse { get; set; } 23 | public TemplateOptions Options { get; set; } 24 | public TemplateContent TemplateContent { get; set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/SparkPost/RetrieveTemplatesResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace SparkPost 5 | { 6 | public class RetrieveTemplatesResponse : Response 7 | { 8 | public RetrieveTemplatesResponse() 9 | { 10 | Templates = new List(); 11 | } 12 | 13 | public List Templates { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/SparkPost/RetrieveTransmissionResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SparkPost 4 | { 5 | public class RetrieveTransmissionResponse : Response 6 | { 7 | public string Id { get; set; } 8 | public string Description { get; set; } 9 | public DateTime? GenerationEndTime { get; set; } 10 | public int RecipientListTotalChunks { get; set; } 11 | public int RecipientListTotalSize { get; set; } 12 | public int NumberRecipients { get; set; } 13 | public int NumberGenerated { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/SparkPost/RetrieveWebhookResponse.cs: -------------------------------------------------------------------------------- 1 | using SparkPost.Utilities; 2 | 3 | namespace SparkPost 4 | { 5 | public class RetrieveWebhookResponse : Response 6 | { 7 | public Webhook Webhook { get; set; } 8 | 9 | public static RetrieveWebhookResponse CreateFromResponse(Response response) 10 | { 11 | var result = new RetrieveWebhookResponse(); 12 | LeftRight.SetValuesToMatch(result, response); 13 | 14 | var results = Jsonification.DeserializeObject(response.Content).results; 15 | 16 | result.Webhook = ListWebhookResponse.ConvertToAWebhook(results); 17 | 18 | return result; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/SparkPost/SendRecipientListsResponse.cs: -------------------------------------------------------------------------------- 1 | namespace SparkPost 2 | { 3 | public class SendRecipientListsResponse : Response 4 | { 5 | public string Id { get; set; } 6 | public int TotalAcceptedRecipients { get; set; } 7 | public int TotalRejectedRecipients { get; set; } 8 | public string Name { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/SparkPost/SendTransmissionResponse.cs: -------------------------------------------------------------------------------- 1 | namespace SparkPost 2 | { 3 | public class SendTransmissionResponse : Response 4 | { 5 | public string Id { get; set; } 6 | public int TotalAcceptedRecipients { get; set; } 7 | public int TotalRejectedRecipients { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/SparkPost/SendingDomain.cs: -------------------------------------------------------------------------------- 1 | namespace SparkPost 2 | { 3 | public class SendingDomain 4 | { 5 | public string Domain { get; set; } 6 | 7 | public string TrackingDomain { get; set; } 8 | 9 | public SendingDomainStatus Status { get; set; } 10 | 11 | public Dkim Dkim { get; set; } 12 | 13 | public bool GenerateDkim { get; set; } 14 | 15 | public long DkimKeyLength { get; set; } 16 | 17 | public bool SharedWithSubAccounts { get; set; } 18 | 19 | /// 20 | /// Convert json result form Sparkpost API to SendingDomain. 21 | /// 22 | /// Json result form Sparkpost API. 23 | /// 24 | public static SendingDomain ConvertToSendingDomain(dynamic result) 25 | { 26 | return result != null 27 | ? new SendingDomain 28 | { 29 | Domain = result.domain, 30 | TrackingDomain = result.tracking_domain, 31 | Status = SendingDomainStatus.ConvertToSendingDomainStatus(result.status), 32 | Dkim = SparkPost.Dkim.ConvertToDkim(result.dkim), 33 | GenerateDkim = result.generate_dkim ?? false, 34 | DkimKeyLength = result.dkim_key_length ?? 0, 35 | SharedWithSubAccounts = result.shared_with_subaccounts ?? false 36 | } 37 | : null; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/SparkPost/SendingDomainStatus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SparkPost 4 | { 5 | public class SendingDomainStatus 6 | { 7 | public bool OwnershipVerified { get; set; } 8 | 9 | public DkimStatus DkimStatus { get; set; } 10 | 11 | public SpfStatus SpfStatus { get; set; } 12 | 13 | public AbuseAtStatus AbuseAtStatus { get; set; } 14 | 15 | public PostmasterAtStatus PostmasterAtStatus { get; set; } 16 | 17 | public ComplianceStatus ComplianceStatus { get; set; } 18 | 19 | /// 20 | /// Convert json result form Sparkpost API to SendingDomainStatus. 21 | /// 22 | /// Json result form Sparkpost API. 23 | /// 24 | public static SendingDomainStatus ConvertToSendingDomainStatus(dynamic result) 25 | { 26 | if (result == null) 27 | return null; 28 | return new SendingDomainStatus 29 | { 30 | OwnershipVerified = result.ownership_verified ?? false, 31 | DkimStatus = Enum.Parse(typeof(DkimStatus), (result.dkim_status ?? DkimStatus.Unknowed).ToString(), true), 32 | SpfStatus = Enum.Parse(typeof(SpfStatus), (result.spf_status ?? SpfStatus.Unknowed).ToString(), true), 33 | AbuseAtStatus = Enum.Parse(typeof(AbuseAtStatus), (result.abuse_at_status ?? AbuseAtStatus.Unknowed).ToString(), true), 34 | PostmasterAtStatus = Enum.Parse( 35 | typeof(PostmasterAtStatus), 36 | (result.postmaster_at_status ?? PostmasterAtStatus.Unknowed).ToString(), 37 | true 38 | ), 39 | ComplianceStatus = Enum.Parse(typeof(ComplianceStatus), (result.compliance_status ?? ComplianceStatus.Unknowed).ToString(), true) 40 | }; 41 | } 42 | } 43 | 44 | public enum DkimStatus 45 | { 46 | Unverified, 47 | Pending, 48 | Invalid, 49 | Valid, 50 | Unknowed 51 | } 52 | 53 | public enum SpfStatus 54 | { 55 | Unverified, 56 | Pending, 57 | Invalid, 58 | Valid, 59 | Unknowed 60 | } 61 | 62 | public enum AbuseAtStatus 63 | { 64 | Unverified, 65 | Pending, 66 | Invalid, 67 | Valid, 68 | Unknowed 69 | } 70 | 71 | public enum PostmasterAtStatus 72 | { 73 | Unverified, 74 | Pending, 75 | Invalid, 76 | Valid, 77 | Unknowed 78 | } 79 | 80 | public enum ComplianceStatus 81 | { 82 | Pending, 83 | Valid, 84 | Unknowed 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/SparkPost/SparkPost.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | SparkPost 4 | SparkPost 5 | SparkPost 6 | SparkPost API 7 | Copyright 2022 8 | 2.0.2 9 | 2.0.2 10 | 2.0.2 11 | Darren Cauthon; Adam Hathcock 12 | https://www.sparkpost.com/sites/default/files/attachments/SparkPost_Logo_2-Color_Gray-Orange_RGB.svg 13 | SparkPost 14 | https://github.com/darrencauthon/csharp-sparkpost/blob/master/LICENSE.md 15 | https://github.com/SparkPost/csharp-sparkpost 16 | LICENSE.md 17 | Added support to pass a user-agent. 18 | email 19 | netstandard2.0 20 | 10 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | LICENSE.md 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/SparkPost/Subaccount.cs: -------------------------------------------------------------------------------- 1 | namespace SparkPost 2 | { 3 | public class Subaccount 4 | { 5 | public long Id { get; set; } 6 | public string Name { get; set; } 7 | public SubaccountStatus Status { get; set; } 8 | public string IpPool { get; set; } 9 | public string ComplianceStatus { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/SparkPost/SubaccountCreate.cs: -------------------------------------------------------------------------------- 1 | namespace SparkPost 2 | { 3 | public class SubaccountCreate 4 | { 5 | public string Name { get; set; } 6 | public string KeyLabel { get; set; } 7 | public string[] KeyGrants { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/SparkPost/SubaccountStatus.cs: -------------------------------------------------------------------------------- 1 | namespace SparkPost 2 | { 3 | public enum SubaccountStatus 4 | { 5 | Active, 6 | Suspended, 7 | Terminated 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/SparkPost/SubaccountUpdate.cs: -------------------------------------------------------------------------------- 1 | namespace SparkPost 2 | { 3 | public class SubaccountUpdate 4 | { 5 | public long Id { get; set; } 6 | public string Name { get; set; } 7 | public SubaccountStatus Status { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/SparkPost/Subaccounts.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Threading.Tasks; 3 | using SparkPost.RequestSenders; 4 | using SparkPost.Utilities; 5 | 6 | namespace SparkPost 7 | { 8 | public class Subaccounts : ISubaccounts 9 | { 10 | private readonly IClient client; 11 | private readonly IRequestSender requestSender; 12 | private readonly IDataMapper dataMapper; 13 | 14 | public Subaccounts(IClient client, IRequestSender requestSender, IDataMapper dataMapper) 15 | { 16 | this.client = client; 17 | this.requestSender = requestSender; 18 | this.dataMapper = dataMapper; 19 | } 20 | 21 | public async Task List() 22 | { 23 | var request = new Request { Url = $"/api/{client.Version}/subaccounts", Method = "GET" }; 24 | 25 | var response = await requestSender.Send(request); 26 | if (response.StatusCode != HttpStatusCode.OK) 27 | throw new ResponseException(response); 28 | 29 | return ListSubaccountResponse.CreateFromResponse(response); 30 | } 31 | 32 | public async Task Create(SubaccountCreate subaccount) 33 | { 34 | var request = new Request 35 | { 36 | Url = $"api/{client.Version}/subaccounts", 37 | Method = "POST", 38 | Data = new { name = subaccount.Name, key_label = subaccount.KeyLabel, key_grants = subaccount.KeyGrants } 39 | }; 40 | 41 | var response = await requestSender.Send(request); 42 | if (response.StatusCode != HttpStatusCode.OK) 43 | throw new ResponseException(response); 44 | 45 | var results = Jsonification.DeserializeObject(response.Content).results; 46 | 47 | return new CreateSubaccountResponse 48 | { 49 | ReasonPhrase = response.ReasonPhrase, 50 | StatusCode = response.StatusCode, 51 | Content = response.Content, 52 | Key = results.key, 53 | Label = results.label, 54 | ShortKey = results.short_key, 55 | SubaccountId = results.subaccount_id 56 | }; 57 | } 58 | 59 | public async Task Update(SubaccountUpdate subaccount) 60 | { 61 | var request = new Request 62 | { 63 | Url = $"api/{client.Version}/subaccounts/{subaccount.Id}", 64 | Method = "PUT JSON", 65 | Data = new { name = subaccount.Name, status = subaccount.Status.ToString().ToLowerInvariant() } 66 | }; 67 | 68 | var response = await requestSender.Send(request); 69 | if (response.StatusCode != HttpStatusCode.OK) 70 | throw new ResponseException(response); 71 | 72 | var results = Jsonification.DeserializeObject(response.Content).results; 73 | 74 | return new UpdateSubaccountResponse 75 | { 76 | ReasonPhrase = response.ReasonPhrase, 77 | StatusCode = response.StatusCode, 78 | Content = response.Content 79 | }; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/SparkPost/Suppression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SparkPost 4 | { 5 | public class Suppression 6 | { 7 | public bool Transactional { get; set; } 8 | public bool NonTransactional { get; set; } 9 | public string Description { get; set; } 10 | public string Email { get; set; } 11 | public string Source { get; set; } 12 | public DateTime? Created { get; set; } 13 | public DateTime? Updated { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/SparkPost/Suppressions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Net; 4 | using System.Threading.Tasks; 5 | using System.Web; 6 | using SparkPost.RequestSenders; 7 | using SparkPost.Utilities; 8 | 9 | namespace SparkPost 10 | { 11 | public class Suppressions : ISuppressions 12 | { 13 | private readonly IClient client; 14 | private readonly IRequestSender requestSender; 15 | private readonly IDataMapper dataMapper; 16 | 17 | public Suppressions(IClient client, IRequestSender requestSender, IDataMapper dataMapper) 18 | { 19 | this.client = client; 20 | this.requestSender = requestSender; 21 | this.dataMapper = dataMapper; 22 | } 23 | 24 | public async Task List(SuppressionsQuery supppressionsQuery) 25 | { 26 | return await List(supppressionsQuery as object); 27 | } 28 | 29 | public async Task List(object query = null) 30 | { 31 | if (query == null) 32 | query = new { }; 33 | var request = new Request 34 | { 35 | Url = $"/api/{client.Version}/suppression-list", 36 | Method = "GET", 37 | Data = query 38 | }; 39 | 40 | var response = await requestSender.Send(request); 41 | if (response.StatusCode != HttpStatusCode.OK) 42 | throw new ResponseException(response); 43 | 44 | var results = Jsonification.DeserializeObject(response.Content).results; 45 | 46 | return new ListSuppressionResponse 47 | { 48 | ReasonPhrase = response.ReasonPhrase, 49 | StatusCode = response.StatusCode, 50 | Content = response.Content, 51 | Suppressions = ConvertResultsToAListOfSuppressions(results) 52 | }; 53 | } 54 | 55 | public async Task Retrieve(string email) 56 | { 57 | var request = new Request { Url = $"/api/{client.Version}/suppression-list/{WebUtility.UrlEncode(email)}", Method = "GET" }; 58 | 59 | var response = await requestSender.Send(request); 60 | 61 | if (new[] { HttpStatusCode.OK, HttpStatusCode.NotFound }.Contains(response.StatusCode) == false) 62 | throw new ResponseException(response); 63 | 64 | dynamic results = response.StatusCode == HttpStatusCode.OK ? Jsonification.DeserializeObject(response.Content).results : null; 65 | 66 | return new ListSuppressionResponse 67 | { 68 | ReasonPhrase = response.ReasonPhrase, 69 | StatusCode = response.StatusCode, 70 | Content = response.Content, 71 | Suppressions = ConvertResultsToAListOfSuppressions(results) 72 | }; 73 | } 74 | 75 | public async Task CreateOrUpdate(IEnumerable emails) 76 | { 77 | var suppressions = emails.Select( 78 | email => 79 | new Suppression 80 | { 81 | Email = email, 82 | Transactional = true, 83 | NonTransactional = true 84 | } 85 | ); 86 | 87 | return await CreateOrUpdate(suppressions); 88 | } 89 | 90 | public async Task CreateOrUpdate(IEnumerable suppressions) 91 | { 92 | var request = new Request 93 | { 94 | Url = $"api/{client.Version}/suppression-list", 95 | Method = "PUT JSON", 96 | Data = new { recipients = suppressions.Select(x => dataMapper.ToDictionary(x)).ToList() } 97 | }; 98 | 99 | var response = await requestSender.Send(request); 100 | if (response.StatusCode != HttpStatusCode.OK) 101 | throw new ResponseException(response); 102 | 103 | var updateSuppressionResponse = new UpdateSuppressionResponse(); 104 | LeftRight.SetValuesToMatch(updateSuppressionResponse, response); 105 | return updateSuppressionResponse; 106 | } 107 | 108 | public async Task Delete(string email) 109 | { 110 | var request = new Request { Url = $"api/{client.Version}/suppression-list/{WebUtility.UrlEncode(email)}", Method = "DELETE" }; 111 | 112 | var response = await requestSender.Send(request); 113 | return response.StatusCode == HttpStatusCode.NoContent; 114 | } 115 | 116 | private static IEnumerable ConvertResultsToAListOfSuppressions(dynamic results) 117 | { 118 | var suppressions = new List(); 119 | 120 | if (results == null) 121 | return suppressions; 122 | 123 | foreach (var result in results) 124 | { 125 | suppressions.Add( 126 | new Suppression 127 | { 128 | Description = result.description, 129 | Transactional = result.transactional == true, 130 | NonTransactional = result.non_transactional == true, 131 | Email = result.recipient, 132 | Source = result.source, 133 | Created = result.created, 134 | Updated = result.updated 135 | } 136 | ); 137 | } 138 | return suppressions; 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/SparkPost/SuppressionsQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SparkPost 4 | { 5 | public class SuppressionsQuery 6 | { 7 | public DateTime? To { get; set; } 8 | public DateTime? From { get; set; } 9 | public int? Limit { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/SparkPost/Template.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace SparkPost 5 | { 6 | public class Template : TemplateBase 7 | { 8 | public Template() 9 | { 10 | Content = new TemplateContent(); 11 | Options = new TemplateOptions(); 12 | } 13 | 14 | public TemplateContent Content { get; set; } 15 | public TemplateOptions Options { get; set; } 16 | } 17 | 18 | public class TemplateListItem : TemplateBase 19 | { 20 | public DateTime LastUpdateTime { get; set; } 21 | } 22 | 23 | public class TemplateBase 24 | { 25 | public string Id { get; set; } 26 | public string Name { get; set; } 27 | public string Description { get; set; } 28 | public bool Published { get; set; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/SparkPost/TemplateContent.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace SparkPost 4 | { 5 | public class TemplateContent 6 | { 7 | public TemplateContent() 8 | { 9 | From = new Address(); 10 | Headers = new Dictionary(); 11 | } 12 | 13 | public string Html { get; set; } 14 | public string Text { get; set; } 15 | public string Subject { get; set; } 16 | public Address From { get; set; } 17 | public string ReplyTo { get; set; } 18 | public IDictionary Headers { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/SparkPost/TemplateOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SparkPost 4 | { 5 | public class TemplateOptions 6 | { 7 | public bool? OpenTracking { get; set; } 8 | public bool? ClickTracking { get; set; } 9 | public bool? Transactional { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/SparkPost/Transmission.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net.Mail; 3 | using SparkPost.Utilities; 4 | 5 | namespace SparkPost 6 | { 7 | public class Transmission 8 | { 9 | public Transmission() 10 | { 11 | Recipients = new List(); 12 | Metadata = new Dictionary(); 13 | SubstitutionData = new Dictionary(); 14 | Content = new Content(); 15 | Options = new Options(); 16 | } 17 | 18 | public string Id { get; set; } 19 | public string State { get; set; } 20 | public Options Options { get; set; } 21 | 22 | public IList Recipients { get; set; } 23 | public string ListId { get; set; } 24 | 25 | public string CampaignId { get; set; } 26 | public string Description { get; set; } 27 | public IDictionary Metadata { get; set; } 28 | public IDictionary SubstitutionData { get; set; } 29 | public string ReturnPath { get; set; } 30 | public Content Content { get; set; } 31 | public int TotalRecipients { get; set; } 32 | public int NumGenerated { get; set; } 33 | public int NumFailedGeneration { get; set; } 34 | public int NumInvalidRecipients { get; set; } 35 | 36 | public static Transmission Parse(MailMessage message) 37 | { 38 | return MailMessageMapping.ToTransmission(message); 39 | } 40 | 41 | public void LoadFrom(MailMessage message) 42 | { 43 | MailMessageMapping.ToTransmission(message, this); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/SparkPost/Transmissions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Net; 4 | using System.Threading.Tasks; 5 | using SparkPost.RequestSenders; 6 | using SparkPost.Utilities; 7 | 8 | namespace SparkPost 9 | { 10 | public class Transmissions : ITransmissions 11 | { 12 | private readonly Client client; 13 | private readonly IRequestSender requestSender; 14 | private readonly DataMapper dataMapper; 15 | 16 | public Transmissions(Client client, IRequestSender requestSender, DataMapper dataMapper) 17 | { 18 | this.client = client; 19 | this.requestSender = requestSender; 20 | this.dataMapper = dataMapper; 21 | } 22 | 23 | public async Task Send(Transmission transmission) 24 | { 25 | var request = new Request 26 | { 27 | Url = $"api/{client.Version}/transmissions", 28 | Method = "POST", 29 | Data = dataMapper.ToDictionary(transmission) 30 | }; 31 | 32 | var response = await requestSender.Send(request); 33 | if (response.StatusCode != HttpStatusCode.OK) 34 | throw new ResponseException(response); 35 | 36 | var results = Jsonification.DeserializeObject(response.Content).results; 37 | return new SendTransmissionResponse() 38 | { 39 | Id = results.id, 40 | ReasonPhrase = response.ReasonPhrase, 41 | StatusCode = response.StatusCode, 42 | Content = response.Content, 43 | TotalAcceptedRecipients = results.total_accepted_recipients, 44 | TotalRejectedRecipients = results.total_rejected_recipients, 45 | }; 46 | } 47 | 48 | public async Task Retrieve(string transmissionId) 49 | { 50 | var request = new Request { Url = $"api/{client.Version}/transmissions/" + transmissionId, Method = "GET", }; 51 | 52 | var response = await requestSender.Send(request); 53 | 54 | if (new[] { HttpStatusCode.OK, HttpStatusCode.NotFound }.Contains(response.StatusCode) == false) 55 | throw new ResponseException(response); 56 | 57 | var transmissionResponse = new RetrieveTransmissionResponse() 58 | { 59 | ReasonPhrase = response.ReasonPhrase, 60 | StatusCode = response.StatusCode, 61 | Content = response.Content, 62 | }; 63 | 64 | try 65 | { 66 | var results = Jsonification.DeserializeObject(response.Content).results; 67 | if (results.transmission == null) 68 | return transmissionResponse; 69 | 70 | transmissionResponse.Id = results.transmission.id; 71 | transmissionResponse.Description = results.transmission.description; 72 | transmissionResponse.GenerationEndTime = results.transmission.generation_end_time; 73 | transmissionResponse.RecipientListTotalChunks = results.transmission.rcpt_list_total_chunks ?? 0; 74 | transmissionResponse.RecipientListTotalSize = results.transmission.rcpt_list_chunk_size ?? 0; 75 | transmissionResponse.NumberRecipients = results.transmission.num_rcpts ?? 0; 76 | transmissionResponse.NumberGenerated = results.transmission.num_generated ?? 0; 77 | } 78 | catch 79 | { 80 | // ignored 81 | } 82 | 83 | return transmissionResponse; 84 | } 85 | 86 | public async Task List() 87 | { 88 | var request = new Request { Url = $"api/{client.Version}/transmissions", Method = "GET", }; 89 | 90 | var response = await requestSender.Send(request); 91 | if (response.StatusCode != HttpStatusCode.OK) 92 | throw new ResponseException(response); 93 | 94 | var transmissionResponse = new ListTransmissionResponse() 95 | { 96 | ReasonPhrase = response.ReasonPhrase, 97 | StatusCode = response.StatusCode, 98 | Content = response.Content, 99 | }; 100 | 101 | return transmissionResponse; 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/SparkPost/UpdateRecipientListResponse.cs: -------------------------------------------------------------------------------- 1 | namespace SparkPost 2 | { 3 | public class UpdateRecipientListResponse : Response { } 4 | } 5 | -------------------------------------------------------------------------------- /src/SparkPost/UpdateSendingDomainResponse.cs: -------------------------------------------------------------------------------- 1 | namespace SparkPost 2 | { 3 | public class UpdateSendingDomainResponse : Response 4 | { 5 | public string Domain { get; set; } 6 | 7 | public string TrackingDomain { get; set; } 8 | 9 | public Dkim Dkim { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/SparkPost/UpdateSubaccountResponse.cs: -------------------------------------------------------------------------------- 1 | namespace SparkPost 2 | { 3 | public class UpdateSubaccountResponse : Response { } 4 | } 5 | -------------------------------------------------------------------------------- /src/SparkPost/UpdateSuppressionResponse.cs: -------------------------------------------------------------------------------- 1 | namespace SparkPost 2 | { 3 | public class UpdateSuppressionResponse : Response { } 4 | } 5 | -------------------------------------------------------------------------------- /src/SparkPost/Utilities/Jsonification.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace SparkPost.Utilities 4 | { 5 | internal static class Jsonification 6 | { 7 | internal static T DeserializeObject(string json) 8 | { 9 | return JsonConvert.DeserializeObject(json); 10 | } 11 | 12 | internal static string SerializeObject(object @object) 13 | { 14 | return JsonConvert.SerializeObject(@object, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.None }); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/SparkPost/Utilities/MailMessageMapping.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net.Mail; 6 | using System.Net.Mime; 7 | 8 | namespace SparkPost.Utilities 9 | { 10 | public static class MailMessageMapping 11 | { 12 | private static readonly Action[] Actions = 13 | { 14 | (t, m) => t.Content.From = ConvertToAddress(m.From), 15 | (t, m) => t.Content.Subject = m.Subject, 16 | (t, m) => AddRecipients(t, m.To, RecipientType.To), 17 | (t, m) => AddRecipients(t, m.CC, RecipientType.CC), 18 | (t, m) => AddRecipients(t, m.Bcc, RecipientType.BCC), 19 | (t, m) => 20 | { 21 | if (m.ReplyToList.Any()) 22 | t.Content.ReplyTo = m.ReplyToList.First().Address; 23 | }, 24 | (t, m) => 25 | { 26 | if (m.IsBodyHtml) 27 | t.Content.Html = m.Body; 28 | }, 29 | (t, m) => 30 | { 31 | if (!m.IsBodyHtml) 32 | t.Content.Text = m.Body; 33 | }, 34 | (t, m) => 35 | { 36 | foreach (var attachment in m.Attachments) 37 | t.Content.Attachments.Add(File.Create(attachment.ContentStream, attachment.ContentType.Name)); 38 | }, 39 | (t, m) => 40 | { 41 | var text = GetTheAlternativeView(m.AlternateViews, MediaTypeNames.Text.Plain); 42 | if (text != null) 43 | t.Content.Text = text; 44 | }, 45 | (t, m) => 46 | { 47 | var html = GetTheAlternativeView(m.AlternateViews, MediaTypeNames.Text.Html); 48 | if (html != null) 49 | t.Content.Html = html; 50 | } 51 | }; 52 | 53 | public static Transmission ToTransmission(MailMessage message) 54 | { 55 | var transmission = new Transmission(); 56 | ToTransmission(message, transmission); 57 | return transmission; 58 | } 59 | 60 | public static Transmission ToTransmission(MailMessage message, Transmission transmission) 61 | { 62 | foreach (var action in Actions) 63 | action(transmission, message); 64 | return transmission; 65 | } 66 | 67 | private static string GetTheAlternativeView(AlternateViewCollection views, string type) 68 | { 69 | return AlternativeViewsAreAvailable(views) ? GetViewContent(views, type) : null; 70 | } 71 | 72 | private static bool AlternativeViewsAreAvailable(AlternateViewCollection views) 73 | { 74 | var textTypes = new[] { MediaTypeNames.Text.Plain, MediaTypeNames.Text.Html }; 75 | return views.Any() && (views.Count <= 2) && !views.Select(av => av.ContentType.MediaType).Except(textTypes).Any(); 76 | } 77 | 78 | private static Address ConvertToAddress(MailAddress address) 79 | { 80 | return new Address(address.Address, address.DisplayName); 81 | } 82 | 83 | private static string GetViewContent(AlternateViewCollection views, string type) 84 | { 85 | var view = views.FirstOrDefault(v => v.ContentType.MediaType == type); 86 | return view == null ? null : GetViewContent(view); 87 | } 88 | 89 | private static string GetViewContent(AttachmentBase view) 90 | { 91 | var reader = new StreamReader(view.ContentStream); 92 | 93 | if (view.ContentStream.CanSeek) 94 | view.ContentStream.Position = 0; 95 | 96 | return reader.ReadToEnd(); 97 | } 98 | 99 | private static void AddRecipients(Transmission transmission, MailAddressCollection addresses, RecipientType type) 100 | { 101 | foreach (var recipient in ConvertToRecipients(addresses, type)) 102 | transmission.Recipients.Add(recipient); 103 | } 104 | 105 | private static IEnumerable ConvertToRecipients(MailAddressCollection addresses, RecipientType type) 106 | { 107 | return addresses.Select(a => ConvertToARecipient(type, a)); 108 | } 109 | 110 | private static Recipient ConvertToARecipient(RecipientType type, MailAddress address) 111 | { 112 | return new Recipient { Type = type, Address = ConvertToAddress(address) }; 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/SparkPost/Utilities/SnakeCase.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace SparkPost.Utilities 4 | { 5 | public static class SnakeCase 6 | { 7 | public static string Convert(string input) 8 | { 9 | if (input == null) 10 | return null; 11 | 12 | var regex = new Regex("[A-Z]"); 13 | 14 | var matches = regex.Matches(input); 15 | 16 | for (var i = 0; i < matches.Count; i++) 17 | input = input.Replace(matches[i].Value, "_" + matches[i].Value.ToLower()); 18 | 19 | if (input.StartsWith("_")) 20 | input = input.Substring(1, input.Length - 1); 21 | 22 | return input; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/SparkPost/Utilities/StringHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace SparkPost.Utilities 5 | { 6 | public static class StringHelper 7 | { 8 | public static string[] SplitOn(this string value, string separator) 9 | { 10 | return value.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries); 11 | } 12 | 13 | public static string JoinWith(this IEnumerable value, string separator) 14 | { 15 | return string.Join(separator, value); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/SparkPost/ValueMappers/AnonymousValueMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace SparkPost.ValueMappers 5 | { 6 | public class AnonymousValueMapper : IValueMapper 7 | { 8 | private readonly IDataMapper dataMapper; 9 | 10 | public AnonymousValueMapper(IDataMapper dataMapper) 11 | { 12 | this.dataMapper = dataMapper; 13 | } 14 | 15 | public bool CanMap(Type propertyType, object value) 16 | { 17 | return ThisIsAnAnonymousType(value); 18 | } 19 | 20 | public object Map(Type propertyType, object value) 21 | { 22 | var newValue = new Dictionary(); 23 | foreach (var property in value.GetType().GetProperties()) 24 | newValue[property.Name] = property.GetValue(value); 25 | return dataMapper.GetTheValue(newValue.GetType(), newValue); 26 | } 27 | 28 | private static bool ThisIsAnAnonymousType(object value) 29 | { 30 | return value != null && (value.GetType().Name.Contains("AnonymousType") || value.GetType().Name.Contains("AnonType")); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/SparkPost/ValueMappers/BooleanValueMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SparkPost.ValueMappers 4 | { 5 | public class BooleanValueMapper : IValueMapper 6 | { 7 | public bool CanMap(Type propertyType, object value) 8 | { 9 | return value is bool?; 10 | } 11 | 12 | public object Map(Type propertyType, object value) 13 | { 14 | return value as bool? == true; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/SparkPost/ValueMappers/DateTimeOffsetValueMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SparkPost.ValueMappers 4 | { 5 | public class DateTimeOffsetValueMapper : IValueMapper 6 | { 7 | public bool CanMap(Type propertyType, object value) 8 | { 9 | return value is DateTimeOffset?; 10 | } 11 | 12 | public object Map(Type propertyType, object value) 13 | { 14 | return string.Format("{0:s}{0:zzz}", (DateTimeOffset?)value); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/SparkPost/ValueMappers/DateTimeValueMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SparkPost.ValueMappers 4 | { 5 | public class DateTimeValueMapper : IValueMapper 6 | { 7 | public bool CanMap(Type propertyType, object value) 8 | { 9 | return value is DateTime; 10 | } 11 | 12 | public object Map(Type propertyType, object value) 13 | { 14 | return ((DateTime)value).ToString("yyyy-MM-ddTHH:mm"); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/SparkPost/ValueMappers/EnumValueMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SparkPost.ValueMappers 4 | { 5 | public class EnumValueMapper : IValueMapper 6 | { 7 | public bool CanMap(Type propertyType, object value) 8 | { 9 | return propertyType.IsEnum; 10 | } 11 | 12 | public object Map(Type propertyType, object value) 13 | { 14 | return value.ToString().ToLowerInvariant(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/SparkPost/ValueMappers/EnumerableValueMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Linq; 4 | 5 | namespace SparkPost.ValueMappers 6 | { 7 | public class EnumerableValueMapper : IValueMapper 8 | { 9 | private readonly IDataMapper mapper; 10 | 11 | public EnumerableValueMapper(IDataMapper mapper) 12 | { 13 | this.mapper = mapper; 14 | } 15 | 16 | public bool CanMap(Type propertyType, object value) 17 | { 18 | return value != null && value.GetType() != typeof(string) && value is IEnumerable; 19 | } 20 | 21 | public object Map(Type propertyType, object value) 22 | { 23 | var things = (from object thing in (IEnumerable)value select mapper.GetTheValue(thing.GetType(), thing)).ToList(); 24 | return things.Count > 0 ? things : null; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/SparkPost/ValueMappers/MapASetOfItemsUsingToDictionary.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Reflection; 6 | using SparkPost.Utilities; 7 | 8 | namespace SparkPost.ValueMappers 9 | { 10 | public class MapASetOfItemsUsingToDictionary : IValueMapper 11 | { 12 | private readonly IDictionary converters; 13 | private readonly IDataMapper dataMapper; 14 | 15 | public MapASetOfItemsUsingToDictionary(IDataMapper dataMapper) 16 | { 17 | this.dataMapper = dataMapper; 18 | converters = dataMapper.ToDictionaryMethods(); 19 | } 20 | 21 | public bool CanMap(Type propertyType, object value) 22 | { 23 | return value != null 24 | && propertyType.Name.EndsWith("List`1") 25 | && propertyType.GetGenericArguments().Count() == 1 26 | && converters.ContainsKey(propertyType.GetGenericArguments().First()); 27 | } 28 | 29 | public object Map(Type propertyType, object value) 30 | { 31 | var converter = converters[propertyType.GetGenericArguments().First()]; 32 | 33 | var list = (value as IEnumerable).ToList(); 34 | 35 | if (list.Any()) 36 | value = list.Select(x => converter.Invoke(dataMapper, BindingFlags.Default, null, new[] { x }, CultureInfo.CurrentCulture)).ToList(); 37 | else 38 | value = null; 39 | 40 | return value; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/SparkPost/ValueMappers/MapASingleItemUsingToDictionary.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Reflection; 6 | using SparkPost.Utilities; 7 | 8 | namespace SparkPost.ValueMappers 9 | { 10 | public class MapASingleItemUsingToDictionary : IValueMapper 11 | { 12 | private readonly IDictionary converters; 13 | private readonly IDataMapper dataMapper; 14 | 15 | public MapASingleItemUsingToDictionary(IDataMapper dataMapper) 16 | { 17 | this.dataMapper = dataMapper; 18 | converters = dataMapper.ToDictionaryMethods(); 19 | } 20 | 21 | public bool CanMap(Type propertyType, object value) 22 | { 23 | return propertyType != typeof(int) && converters.ContainsKey(propertyType); 24 | } 25 | 26 | public object Map(Type propertyType, object value) 27 | { 28 | return converters[propertyType].Invoke(dataMapper, BindingFlags.Default, null, new[] { value }, CultureInfo.CurrentCulture); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/SparkPost/ValueMappers/StringObjectDictionaryValueMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using SparkPost.Utilities; 5 | 6 | namespace SparkPost.ValueMappers 7 | { 8 | public class StringObjectDictionaryValueMapper : IValueMapper 9 | { 10 | private readonly IDataMapper dataMapper; 11 | 12 | public StringObjectDictionaryValueMapper(IDataMapper dataMapper) 13 | { 14 | this.dataMapper = dataMapper; 15 | } 16 | 17 | public bool CanMap(Type propertyType, object value) 18 | { 19 | return value is IDictionary; 20 | } 21 | 22 | public object Map(Type propertyType, object value) 23 | { 24 | var original = (IDictionary)value; 25 | var dictionary = new Dictionary(); 26 | foreach (var item in original.Where(i => i.Value != null)) 27 | { 28 | var itemKey = SnakeCase.Convert(item.Key); 29 | var itemValue = item.Value; 30 | dictionary[itemKey] = dataMapper.GetTheValue(itemValue.GetType(), itemValue); 31 | } 32 | return dictionary.Count > 0 ? dictionary : null; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/SparkPost/ValueMappers/StringStringDictionaryValueMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace SparkPost.ValueMappers 5 | { 6 | public class StringStringDictionaryValueMapper : IValueMapper 7 | { 8 | public bool CanMap(Type propertyType, object value) 9 | { 10 | return value is IDictionary; 11 | } 12 | 13 | public object Map(Type propertyType, object value) 14 | { 15 | var dictionary = (IDictionary)value; 16 | return dictionary.Count > 0 ? dictionary : null; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/SparkPost/VerifySendingDomain.cs: -------------------------------------------------------------------------------- 1 | namespace SparkPost 2 | { 3 | public class VerifySendingDomain 4 | { 5 | public string Domain { get; set; } 6 | 7 | public bool SpfVerify { get; set; } 8 | 9 | public bool DkimVerify { get; set; } 10 | 11 | public bool PostmasterAtVerify { get; set; } 12 | 13 | public string PostmasterAtToken { get; set; } 14 | 15 | public string AbuseAtToken { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/SparkPost/VerifySendingDomainResponse.cs: -------------------------------------------------------------------------------- 1 | namespace SparkPost 2 | { 3 | public class VerifySendingDomainResponse : Response 4 | { 5 | public VerifySendingDomainStatus Status { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/SparkPost/VerifySendingDomainStatus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SparkPost 4 | { 5 | public class VerifySendingDomainStatus : SendingDomainStatus 6 | { 7 | public Dns Dns { get; set; } 8 | 9 | /// 10 | /// Convert json result form Sparkpost API to VerifySendingDomainStatus. 11 | /// 12 | /// Json result form Sparkpost API. 13 | /// 14 | public static VerifySendingDomainStatus ConvertToVerifySendingDomainStatus(dynamic result) 15 | { 16 | return result != null 17 | ? new VerifySendingDomainStatus 18 | { 19 | OwnershipVerified = result.ownership_verified, 20 | DkimStatus = Enum.Parse(typeof(DkimStatus), (result.dkim_status ?? DkimStatus.Unknowed).ToString(), true), 21 | SpfStatus = Enum.Parse(typeof(SpfStatus), (result.spf_status ?? SpfStatus.Unknowed).ToString(), true), 22 | AbuseAtStatus = Enum.Parse(typeof(AbuseAtStatus), (result.abuse_at_status ?? AbuseAtStatus.Unknowed).ToString(), true), 23 | PostmasterAtStatus = Enum.Parse( 24 | typeof(PostmasterAtStatus), 25 | (result.postmaster_at_status ?? PostmasterAtStatus.Unknowed).ToString(), 26 | true 27 | ), 28 | ComplianceStatus = Enum.Parse(typeof(ComplianceStatus), (result.compliance_status ?? ComplianceStatus.Unknowed).ToString(), true), 29 | Dns = SparkPost.Dns.ConvertToDns(result.dns) 30 | } 31 | : null; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/SparkPost/Webhook.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace SparkPost 5 | { 6 | public class Webhook 7 | { 8 | public Webhook() 9 | { 10 | Events = new List(); 11 | } 12 | 13 | public string Id { get; set; } 14 | public string Name { get; set; } 15 | public string Target { get; set; } 16 | public IList Events { get; set; } 17 | 18 | public DateTime? LastSuccessful { get; set; } 19 | public DateTime? LastFailure { get; set; } 20 | 21 | public string AuthType { get; set; } 22 | public string AuthToken { get; set; } 23 | public object AuthRequestDetails { get; set; } 24 | public object AuthCredentials { get; set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/SparkPost/Webhooks.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Threading.Tasks; 3 | using SparkPost.RequestSenders; 4 | 5 | namespace SparkPost 6 | { 7 | public interface IWebhooks 8 | { 9 | Task List(object query = null); 10 | Task Create(Webhook webhook); 11 | Task Retrieve(string id); 12 | Task Delete(string id); 13 | } 14 | 15 | public class Webhooks : IWebhooks 16 | { 17 | private readonly IClient client; 18 | private readonly IDataMapper dataMapper; 19 | private readonly IRequestSender requestSender; 20 | 21 | public Webhooks(IClient client, IRequestSender requestSender, IDataMapper dataMapper) 22 | { 23 | this.client = client; 24 | this.requestSender = requestSender; 25 | this.dataMapper = dataMapper; 26 | } 27 | 28 | public async Task List(object query = null) 29 | { 30 | if (query == null) 31 | query = new { }; 32 | var request = new Request 33 | { 34 | Url = $"/api/{client.Version}/webhooks", 35 | Method = "GET", 36 | Data = query 37 | }; 38 | 39 | var response = await requestSender.Send(request); 40 | if (response.StatusCode != HttpStatusCode.OK) 41 | throw new ResponseException(response); 42 | 43 | return ListWebhookResponse.CreateFromResponse(response); 44 | } 45 | 46 | public async Task Retrieve(string id) 47 | { 48 | var request = new Request { Url = $"/api/{client.Version}/webhooks/{id}", Method = "GET" }; 49 | 50 | var response = await requestSender.Send(request); 51 | if (response.StatusCode != HttpStatusCode.OK) 52 | throw new ResponseException(response); 53 | 54 | return RetrieveWebhookResponse.CreateFromResponse(response); 55 | } 56 | 57 | public async Task Create(Webhook webhook) 58 | { 59 | var request = new Request 60 | { 61 | Url = $"api/{client.Version}/webhooks", 62 | Method = "POST", 63 | Data = dataMapper.ToDictionary(webhook) 64 | }; 65 | 66 | var response = await requestSender.Send(request); 67 | if (response.StatusCode != HttpStatusCode.OK) 68 | throw new ResponseException(response); 69 | 70 | var updateSuppressionResponse = new Response(); 71 | LeftRight.SetValuesToMatch(updateSuppressionResponse, response); 72 | return updateSuppressionResponse; 73 | } 74 | 75 | public async Task Delete(string id) 76 | { 77 | var request = new Request { Url = $"/api/{client.Version}/webhooks/{id}", Method = "DELETE" }; 78 | 79 | var response = await requestSender.Send(request); 80 | return response.StatusCode == HttpStatusCode.NoContent; 81 | } 82 | } 83 | } 84 | --------------------------------------------------------------------------------