Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public SendResponse RunExample()
message.ReplyTo.Email = "[email protected]";
message.To.Add("[email protected]");

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"));
Expand Down
Original file line number Diff line number Diff line change
@@ -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 = "<html>This is the Html Body of my message.</html>";
message.PlainTextBody = "This is the Plain Text Body of my message.";

message.From.Email = "[email protected]";
message.ReplyTo.Email = "[email protected]";
message.To.Add("[email protected]");

return client.Send(message);
}
}
}
51 changes: 28 additions & 23 deletions Example Projects/dotNetCoreExample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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("-------------------------------------------------------------------------");
}
Expand All @@ -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)");
Expand Down
4 changes: 4 additions & 0 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -205,6 +208,7 @@ For more information about AMP please see [AMP Project](https://amp.dev/document

<a name="version"></a>
# 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
Expand Down
2 changes: 1 addition & 1 deletion manifest/socketlabs.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
<metadata>
<id>SocketLabs.EmailDelivery</id>
<version>1.2.1</version>
<version>1.2.2</version>
<title>SocketLabs.EmailDelivery</title>
<authors>Joe Cooper, David Schrenker, Matt Reibach, Ryan Lydzinski (Contributor), Mike Goodfellow (Contributor), Saranya Kavuri (Contributor)</authors>
<license type="file">license.txt</license>
Expand Down
83 changes: 83 additions & 0 deletions src/SocketLabs/InjectionApi/Core/RetryHandler.cs
Original file line number Diff line number Diff line change
@@ -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<HttpStatusCode> ErrorStatusCodes = new List<HttpStatusCode>()
{
HttpStatusCode.InternalServerError,
HttpStatusCode.BadGateway,
HttpStatusCode.ServiceUnavailable,
HttpStatusCode.GatewayTimeout
};

/// <summary>
/// Creates a new instance of the <c>RetryHandler</c>.
/// </summary>
/// <param name="httpClient">A <c>HttpClient</c> instance</param>
/// <param name="endpointUrl">The SocketLabs Injection API endpoint Url</param>
/// <param name="settings">A <c>RetrySettings</c> instance</param>
public RetryHandler(HttpClient httpClient, string endpointUrl, RetrySettings settings)
{
HttpClient = httpClient;
EndpointUrl = endpointUrl;
RetrySettings = settings;
}


public async Task<HttpResponseMessage> 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);

}



}
}
65 changes: 65 additions & 0 deletions src/SocketLabs/InjectionApi/RetrySettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System;

namespace SocketLabs.InjectionApi
{
/// <summary>
///
/// </summary>
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);

/// <summary>
/// Creates a new instance of the <c>RetrySettings</c>.
/// </summary>
/// <param name="maximumNumberOfRetries"></param>
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;

}


/// <summary>
/// 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
/// </summary>
public int MaximumNumberOfRetries { get; }


/// <summary>
/// Get the time period to wait before next call
/// </summary>
/// <param name="numberOfAttempts"></param>
/// <returns></returns>
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));
}
}
}
Loading