diff --git a/Example Projects/dotNetCoreExample/Examples/Basic/BasicSendWithAttachment.cs b/Example Projects/dotNetCoreExample/Examples/Basic/BasicSendWithAttachment.cs
index 7d82b9f..6d53a36 100644
--- a/Example Projects/dotNetCoreExample/Examples/Basic/BasicSendWithAttachment.cs
+++ b/Example Projects/dotNetCoreExample/Examples/Basic/BasicSendWithAttachment.cs
@@ -22,7 +22,7 @@ public SendResponse RunExample()
message.ReplyTo.Email = "replyto@example.com";
message.To.Add("recipient1@example.com");
- var attachment = message.Attachments.AddAsync("bus.png", MimeType.PNG, @".\examples\img\bus.png").Result;
+ var attachment = message.Attachments.Add("bus.png", MimeType.PNG, @".\examples\img\bus.png");
attachment.CustomHeaders.Add(new CustomHeader("Color", "Orange"));
attachment.CustomHeaders.Add(new CustomHeader("Place", "Beach"));
diff --git a/Example Projects/dotNetCoreExample/Examples/Basic/BasicSendWithRetry.cs b/Example Projects/dotNetCoreExample/Examples/Basic/BasicSendWithRetry.cs
new file mode 100644
index 0000000..cf4024c
--- /dev/null
+++ b/Example Projects/dotNetCoreExample/Examples/Basic/BasicSendWithRetry.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Net;
+using SocketLabs.InjectionApi;
+using SocketLabs.InjectionApi.Message;
+
+namespace dotNetCoreExample.Examples.Basic
+{
+ public class BasicSendWithRetry : IExample
+ {
+ public SendResponse RunExample()
+ {
+ var proxy = new WebProxy("http://localhost:4433", false);
+
+ var client = new SocketLabsClient(ExampleConfig.ServerId, ExampleConfig.ApiKey, proxy)
+ {
+ EndpointUrl = ExampleConfig.TargetApi,
+ RequestTimeout = 120,
+ NumberOfRetries = 3
+ };
+
+ var message = new BasicMessage();
+
+ message.Subject = "Sending A Test Message With Retry Enabled";
+ message.HtmlBody = "This is the Html Body of my message.";
+ message.PlainTextBody = "This is the Plain Text Body of my message.";
+
+ message.From.Email = "from@example.com";
+ message.ReplyTo.Email = "replyto@example.com";
+ message.To.Add("recipient1@example.com");
+
+ return client.Send(message);
+ }
+ }
+}
diff --git a/Example Projects/dotNetCoreExample/Program.cs b/Example Projects/dotNetCoreExample/Program.cs
index 10b3c87..7e797bb 100644
--- a/Example Projects/dotNetCoreExample/Program.cs
+++ b/Example Projects/dotNetCoreExample/Program.cs
@@ -53,23 +53,24 @@ private static void DisplayTheMenu()
Console.WriteLine(" 6: Basic Send With Custom-Headers ");
Console.WriteLine(" 7: Basic Send With Embedded Image ");
Console.WriteLine(" 8: Basic Send With Proxy ");
- Console.WriteLine(" 9: Basic Send Complex Example ");
+ Console.WriteLine(" 9: Basic Send With Retry ");
+ Console.WriteLine(" 10: Basic Send Complex Example ");
Console.WriteLine();
Console.WriteLine(" Validation Error Handling Examples: ");
- Console.WriteLine(" 10: Basic Send With Invalid Attachment");
- Console.WriteLine(" 11: Basic Send With Invalid From ");
- Console.WriteLine(" 12: Basic Send With Invalid Recipients ");
+ Console.WriteLine(" 11: Basic Send With Invalid Attachment");
+ Console.WriteLine(" 12: Basic Send With Invalid From ");
+ Console.WriteLine(" 13: Basic Send With Invalid Recipients ");
Console.WriteLine();
Console.WriteLine(" Bulk Send Examples: ");
- Console.WriteLine(" 13: Bulk Send ");
- Console.WriteLine(" 14: Bulk Send With MergeData ");
- Console.WriteLine(" 15: Bulk Send With Ascii Charset And MergeData ");
- Console.WriteLine(" 16: Bulk Send From DataSource With MergeData ");
- Console.WriteLine(" 17: Bulk Send Complex Example (Everything including the Kitchen Sink) ");
+ Console.WriteLine(" 14: Bulk Send ");
+ Console.WriteLine(" 15: Bulk Send With MergeData ");
+ Console.WriteLine(" 16: Bulk Send With Ascii Charset And MergeData ");
+ Console.WriteLine(" 17: Bulk Send From DataSource With MergeData ");
+ Console.WriteLine(" 18: Bulk Send Complex Example (Everything including the Kitchen Sink) ");
Console.WriteLine();
Console.WriteLine(" Amp Examples: ");
- Console.WriteLine(" 18: Basic Send With Amp Body ");
- Console.WriteLine(" 19: Bulk Send With Amp Body ");
+ Console.WriteLine(" 19: Basic Send With Amp Body ");
+ Console.WriteLine(" 20: Bulk Send With Amp Body ");
Console.WriteLine();
Console.WriteLine("-------------------------------------------------------------------------");
}
@@ -91,18 +92,22 @@ private static string GetExampleName(string selection)
case 5: return "dotNetCoreExample.Examples.Basic.BasicSendWithAttachment";
case 6: return "dotNetCoreExample.Examples.Basic.BasicSendWithCustomHeaders";
case 7: return "dotNetCoreExample.Examples.Basic.BasicSendWithEmbeddedImage";
- case 8: return "dotNetCoreExample.Examples.Basic.BasicSendWithProxy";
- case 9: return "dotNetCoreExample.Examples.Basic.BasicComplexExample";
- case 10: return "dotNetCoreExample.Examples.Basic.Invalid.BasicSendWithInvalidAttachment";
- case 11: return "dotNetCoreExample.Examples.Basic.Invalid.BasicSendWithInvalidFrom";
- case 12: return "dotNetCoreExample.Examples.Basic.Invalid.BasicSendWithInvalidRecipients";
- case 13: return "dotNetCoreExample.Examples.Bulk.BulkSend";
- case 14: return "dotNetCoreExample.Examples.Bulk.BulkSendWithMergeData";
- case 15: return "dotNetCoreExample.Examples.Bulk.BulkSendWithAsciiCharsetMergeData";
- case 16: return "dotNetCoreExample.Examples.Bulk.BulkSendFromDataSourceWithMerge";
- case 17: return "dotNetCoreExample.Examples.Bulk.BulkSendComplexExample";
- case 18: return "dotNetCoreExample.Examples.Basic.BasicSendWithAmpBody";
- case 19: return "dotNetCoreExample.Examples.Bulk.BulkSendWithAmpBody";
+ case 8: return "dotNetCoreExample.Examples.Basic.BasicSendWithRetry";
+ case 9: return "dotNetCoreExample.Examples.Basic.BasicSendWithProxy";
+ case 10: return "dotNetCoreExample.Examples.Basic.BasicComplexExample";
+
+ case 11: return "dotNetCoreExample.Examples.Basic.Invalid.BasicSendWithInvalidAttachment";
+ case 12: return "dotNetCoreExample.Examples.Basic.Invalid.BasicSendWithInvalidFrom";
+ case 13: return "dotNetCoreExample.Examples.Basic.Invalid.BasicSendWithInvalidRecipients";
+
+ case 14: return "dotNetCoreExample.Examples.Bulk.BulkSend";
+ case 15: return "dotNetCoreExample.Examples.Bulk.BulkSendWithMergeData";
+ case 16: return "dotNetCoreExample.Examples.Bulk.BulkSendWithAsciiCharsetMergeData";
+ case 17: return "dotNetCoreExample.Examples.Bulk.BulkSendFromDataSourceWithMerge";
+ case 18: return "dotNetCoreExample.Examples.Bulk.BulkSendComplexExample";
+
+ case 19: return "dotNetCoreExample.Examples.Basic.BasicSendWithAmpBody";
+ case 20: return "dotNetCoreExample.Examples.Bulk.BulkSendWithAmpBody";
default:
Console.WriteLine("Invalid Input (Out of Range)");
diff --git a/README.MD b/README.MD
index 02b5853..98f071c 100644
--- a/README.MD
+++ b/README.MD
@@ -173,6 +173,9 @@ This example demonstrates how to add custom headers to your email message.
### [Basic send with a web proxy](https://github.com/socketlabs/socketlabs-csharp/blob/master/Example%20Projects/dotNetCoreExample/Examples/Basic/BasicSendWithProxy.cs)
This example demonstrates how to use a proxy with your HTTP client.
+### [Basic send with retry enabled](https://github.com/socketlabs/socketlabs-csharp/blob/master/Example%20Projects/dotNetCoreExample/Examples/Basic/BasicSendWithRetry.cs)
+This example demonstrates how to use the retry logic with your HTTP client.
+
### [Basic send complex example](https://github.com/socketlabs/socketlabs-csharp/blob/master/Example%20Projects/dotNetCoreExample/Examples/Basic/BasicComplexExample.cs)
This example demonstrates many features of the Basic Send, including adding multiple recipients, adding message and mailing id's, and adding an embedded image.
@@ -205,6 +208,7 @@ For more information about AMP please see [AMP Project](https://amp.dev/document
# Version
+* 1.2.2 - Adding optional retry logic for Http requests. If configured, the request will retry when certain 500 errors occur (500, 502, 503, 504)
* 1.2.1 - Adding request timeout value on the client for Http requests
* 1.2.0 - Can now pass in instance of HTTP client, adds .NET 5.0 support
* 1.1.0 - Adds Amp Html Support
diff --git a/manifest/socketlabs.nuspec b/manifest/socketlabs.nuspec
index c463abb..03ee81b 100644
--- a/manifest/socketlabs.nuspec
+++ b/manifest/socketlabs.nuspec
@@ -2,7 +2,7 @@
SocketLabs.EmailDelivery
- 1.2.1
+ 1.2.2
SocketLabs.EmailDelivery
Joe Cooper, David Schrenker, Matt Reibach, Ryan Lydzinski (Contributor), Mike Goodfellow (Contributor), Saranya Kavuri (Contributor)
license.txt
diff --git a/src/SocketLabs/InjectionApi/Core/RetryHandler.cs b/src/SocketLabs/InjectionApi/Core/RetryHandler.cs
new file mode 100644
index 0000000..e6924fe
--- /dev/null
+++ b/src/SocketLabs/InjectionApi/Core/RetryHandler.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+
+// ReSharper disable MethodSupportsCancellation
+namespace SocketLabs.InjectionApi.Core
+{
+ internal class RetryHandler
+ {
+
+ private readonly HttpClient HttpClient;
+ private readonly string EndpointUrl;
+ private readonly RetrySettings RetrySettings;
+
+ private readonly List ErrorStatusCodes = new List()
+ {
+ HttpStatusCode.InternalServerError,
+ HttpStatusCode.BadGateway,
+ HttpStatusCode.ServiceUnavailable,
+ HttpStatusCode.GatewayTimeout
+ };
+
+ ///
+ /// Creates a new instance of the RetryHandler.
+ ///
+ /// A HttpClient instance
+ /// The SocketLabs Injection API endpoint Url
+ /// A RetrySettings instance
+ public RetryHandler(HttpClient httpClient, string endpointUrl, RetrySettings settings)
+ {
+ HttpClient = httpClient;
+ EndpointUrl = endpointUrl;
+ RetrySettings = settings;
+ }
+
+
+ public async Task SendAsync(StringContent content, CancellationToken cancellationToken)
+ {
+ if (RetrySettings.MaximumNumberOfRetries == 0)
+ return await HttpClient
+ .PostAsync(EndpointUrl, content, cancellationToken)
+ .ConfigureAwait(false);
+
+ var attempts = 0;
+ do
+ {
+ var waitInterval = RetrySettings.GetNextWaitInterval(attempts);
+
+ try
+ {
+ var response = await HttpClient.PostAsync(EndpointUrl, content, cancellationToken)
+ .ConfigureAwait(false);
+
+ if (ErrorStatusCodes.Contains(response.StatusCode))
+ throw new HttpRequestException(
+ $"HttpStatusCode: '{response.StatusCode}'. Response contains server error.");
+
+ return response;
+ }
+ catch (TaskCanceledException)
+ {
+ attempts++;
+ if (attempts > RetrySettings.MaximumNumberOfRetries) throw new TimeoutException();
+ await Task.Delay(waitInterval).ConfigureAwait(false);
+ }
+ catch (HttpRequestException)
+ {
+ attempts++;
+ if (attempts > RetrySettings.MaximumNumberOfRetries) throw;
+ await Task.Delay(waitInterval).ConfigureAwait(false);
+ }
+
+ } while (true);
+
+ }
+
+
+
+ }
+}
diff --git a/src/SocketLabs/InjectionApi/RetrySettings.cs b/src/SocketLabs/InjectionApi/RetrySettings.cs
new file mode 100644
index 0000000..a066bd0
--- /dev/null
+++ b/src/SocketLabs/InjectionApi/RetrySettings.cs
@@ -0,0 +1,65 @@
+using System;
+
+namespace SocketLabs.InjectionApi
+{
+ ///
+ ///
+ ///
+ internal class RetrySettings
+ {
+
+ private const int _defaultNumberOfRetries = 0;
+ private const int _maximumAllowedNumberOfRetries = 5;
+ private readonly TimeSpan _minimumRetryTime = TimeSpan.FromSeconds(1);
+ private readonly TimeSpan _maximumRetryTime = TimeSpan.FromSeconds(10);
+
+ ///
+ /// Creates a new instance of the RetrySettings.
+ ///
+ ///
+ public RetrySettings(int? maximumNumberOfRetries = null)
+ {
+
+ if (maximumNumberOfRetries != null)
+ {
+ if (maximumNumberOfRetries < 0) throw new ArgumentOutOfRangeException(nameof(maximumNumberOfRetries), "maximumNumberOfRetries must be greater than 0");
+ if (maximumNumberOfRetries > 5) throw new ArgumentOutOfRangeException(nameof(maximumNumberOfRetries), $"The maximum number of allowed retries is {_maximumAllowedNumberOfRetries}");
+
+ MaximumNumberOfRetries = maximumNumberOfRetries.Value;
+ }
+ else
+ MaximumNumberOfRetries = _defaultNumberOfRetries;
+
+ }
+
+
+ ///
+ /// The maximum number of retries when sending an Injection API Request before throwing an exception. Default: 0, no retries, you must explicitly enable retry settings
+ ///
+ public int MaximumNumberOfRetries { get; }
+
+
+ ///
+ /// Get the time period to wait before next call
+ ///
+ ///
+ ///
+ public TimeSpan GetNextWaitInterval(int numberOfAttempts)
+ {
+ var interval = (int)Math.Min(
+ _minimumRetryTime.TotalMilliseconds + GetRetryDelta(numberOfAttempts),
+ _maximumRetryTime.TotalMilliseconds);
+
+ return TimeSpan.FromMilliseconds(interval);
+ }
+ internal virtual int GetRetryDelta(int numberOfAttempts)
+ {
+ var random = new Random();
+
+ var min = (int)(TimeSpan.FromSeconds(1).TotalMilliseconds * 0.8);
+ var max = (int)(TimeSpan.FromSeconds(1).TotalMilliseconds * 1.2);
+
+ return (int)((Math.Pow(2.0, numberOfAttempts) - 1.0) * random.Next(min, max));
+ }
+ }
+}
diff --git a/src/SocketLabs/InjectionApi/SocketLabsClient.cs b/src/SocketLabs/InjectionApi/SocketLabsClient.cs
index 948d00b..6d61a00 100644
--- a/src/SocketLabs/InjectionApi/SocketLabsClient.cs
+++ b/src/SocketLabs/InjectionApi/SocketLabsClient.cs
@@ -37,7 +37,8 @@ public class SocketLabsClient : ISocketLabsClient, IDisposable
private readonly int _serverId;
private readonly string _apiKey;
private readonly HttpClient _httpClient;
-
+
+
///
/// The SocketLabs Injection API endpoint Url
///
@@ -47,7 +48,12 @@ public class SocketLabsClient : ISocketLabsClient, IDisposable
/// A timeout period for the Injection API request (in Seconds). Default: 120s
///
public int RequestTimeout { get; set; } = 120;
-
+
+ ///
+ /// RetrySettings object to define retry setting for the Injection API request.
+ ///
+ public int NumberOfRetries { get; set; } = 0;
+
///
/// Creates a new instance of the SocketLabsClient.
///
@@ -220,30 +226,25 @@ public static SendResponse QuickSend(
///
public async Task SendAsync(IBasicMessage message, CancellationToken cancellationToken)
{
- try
- {
- var validator = new SendValidator();
+ var validator = new SendValidator();
- var validationResult = validator.ValidateCredentials(_serverId, _apiKey);
- if (validationResult.Result != SendResult.Success) return validationResult;
+ var validationResult = validator.ValidateCredentials(_serverId, _apiKey);
+ if (validationResult.Result != SendResult.Success) return validationResult;
- validationResult = validator.ValidateMessage(message);
- if (validationResult.Result != SendResult.Success) return validationResult;
+ validationResult = validator.ValidateMessage(message);
+ if (validationResult.Result != SendResult.Success) return validationResult;
- var factory = new InjectionRequestFactory(_serverId, _apiKey);
- var injectionRequest = factory.GenerateRequest(message);
- var json = injectionRequest.GetAsJson();
+ var factory = new InjectionRequestFactory(_serverId, _apiKey);
+ var injectionRequest = factory.GenerateRequest(message);
+ var json = injectionRequest.GetAsJson();
- _httpClient.Timeout = TimeSpan.FromSeconds(RequestTimeout);
- var httpResponse = await _httpClient.PostAsync(EndpointUrl, json, cancellationToken);
-
- var response = new InjectionResponseParser().Parse(httpResponse);
- return response;
- }
- catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
- {
- throw new TimeoutException();
- }
+ _httpClient.Timeout = TimeSpan.FromSeconds(RequestTimeout);
+
+ var retryHandler = new RetryHandler(_httpClient, EndpointUrl, new RetrySettings(NumberOfRetries));
+ var httpResponse = await retryHandler.SendAsync(json, cancellationToken);
+
+ var response = new InjectionResponseParser().Parse(httpResponse);
+ return response;
}
///
@@ -280,29 +281,25 @@ public async Task SendAsync(IBasicMessage message, CancellationTok
///
public async Task SendAsync(IBulkMessage message, CancellationToken cancellationToken)
{
- try
- {
- var validator = new SendValidator();
+ var validator = new SendValidator();
- var validationResult = validator.ValidateCredentials(_serverId, _apiKey);
- if (validationResult.Result != SendResult.Success) return validationResult;
+ var validationResult = validator.ValidateCredentials(_serverId, _apiKey);
+ if (validationResult.Result != SendResult.Success) return validationResult;
- validationResult = validator.ValidateMessage(message);
- if (validationResult.Result != SendResult.Success) return validationResult;
+ validationResult = validator.ValidateMessage(message);
+ if (validationResult.Result != SendResult.Success) return validationResult;
- var factory = new InjectionRequestFactory(_serverId, _apiKey);
- var injectionRequest = factory.GenerateRequest(message);
+ var factory = new InjectionRequestFactory(_serverId, _apiKey);
+ var injectionRequest = factory.GenerateRequest(message);
+ var json = injectionRequest.GetAsJson();
- _httpClient.Timeout = TimeSpan.FromSeconds(RequestTimeout);
- var httpResponse = await _httpClient.PostAsync(EndpointUrl, injectionRequest.GetAsJson(), cancellationToken);
+ _httpClient.Timeout = TimeSpan.FromSeconds(RequestTimeout);
- var response = new InjectionResponseParser().Parse(httpResponse);
- return response;
- }
- catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
- {
- throw new TimeoutException();
- }
+ var retryHandler = new RetryHandler(_httpClient, EndpointUrl, new RetrySettings(NumberOfRetries));
+ var httpResponse = await retryHandler.SendAsync(json, cancellationToken);
+
+ var response = new InjectionResponseParser().Parse(httpResponse);
+ return response;
}
///
diff --git a/src/SocketLabs/SocketLabs.csproj b/src/SocketLabs/SocketLabs.csproj
index dd8a864..685bd05 100644
--- a/src/SocketLabs/SocketLabs.csproj
+++ b/src/SocketLabs/SocketLabs.csproj
@@ -8,7 +8,7 @@
SocketLabs
false
false
- 1.2.1
+ 1.2.2
(C) 2018-2021 SocketLabs
https://www.socketlabs.com/assets/socketlabs-logo1.png
SocketLabs Development Team