EmmaSharper 8.0.1
dotnet add package EmmaSharper --version 8.0.1
NuGet\Install-Package EmmaSharper -Version 8.0.1
<PackageReference Include="EmmaSharper" Version="8.0.1" />
<PackageVersion Include="EmmaSharper" Version="8.0.1" />
<PackageReference Include="EmmaSharper" />
paket add EmmaSharper --version 8.0.1
#r "nuget: EmmaSharper, 8.0.1"
#:package EmmaSharper@8.0.1
#addin nuget:?package=EmmaSharper&version=8.0.1
#tool nuget:?package=EmmaSharper&version=8.0.1
EmmaSharper
A .NET client for the Emma (Marigold) API.
This is the maintained continuation of
kylegregory/EmmaSharp, which last shipped in 2019, by way ofBinaryPatrick/EmmaSharper. Several bugs still shown as open on those repositories are fixed here โ see Upstream issues fixed in this fork.
๐ Documentation wiki โ getting started, enterprise multi-account use, paging, rate limiting, recipes, and notes on the Emma API's own quirks.
๐งช Runnable samples โ dotnet run --project samples/EmmaSharper.Samples -- --help.
Built by CI, so the documented patterns are guaranteed to compile.
Targets: netstandard2.0, net8.0, net10.0 โ so .NET Framework 4.6.2+ works too.
Dependencies: two on the modern targets, both Microsoft.Extensions.*.
Install
dotnet add package EmmaSharper
Quick start
using EmmaSharper;
services.AddEmmaApiProviders(options =>
{
options.AccountId = "your account id";
options.PublicKey = "your public key";
options.SecretKey = "your secret key";
// options.BaseUrl defaults to https://api.e2ma.net
});
Or bind from configuration โ this reads the "Emma" section:
services.AddEmmaApiProviders(builder.Configuration);
{
"Emma": {
"AccountId": "your account id",
"PublicKey": "your public key",
"SecretKey": "your secret key"
}
}
Pass
sectionName: nullto bind the configuration root instead, which is how 7.x behaved.
Then inject any provider:
public sealed class MemberSync(IEmmaMemberProvider members)
{
public async Task<int> CountActiveAsync(CancellationToken ct)
=> await members.GetMemberCount(cancellationToken: ct);
}
Working with multiple accounts
Emma enterprise accounts authenticate once and then address many subaccounts. Use
IEmmaAccountScopeFactory rather than registering a container per account โ a scope reuses the same
credentials and the same pooled HttpClient, changing only the account segment of the request path.
public sealed class QuotaSweep(IEmmaAccountScopeFactory scopeFactory)
{
public async Task RunAsync(IEnumerable<string> subaccountIds, CancellationToken ct)
{
foreach (string accountId in subaccountIds)
{
IEmmaAccountScope scope = scopeFactory.ForAccount(accountId);
int active = await scope.Members.GetMemberCount(cancellationToken: ct);
}
}
}
Rate limiting
Emma signals throttling with 403 Forbidden as well as the conventional 429. This is the
least obvious behaviour in the API โ a naive client reads the 403 as an auth failure and gives up
instead of backing off.
Both map to EmmaRateLimitException, which carries RetryAfter when Emma supplies it:
try
{
await members.GetMemberCount(cancellationToken: ct);
}
catch (EmmaRateLimitException ex)
{
await Task.Delay(ex.RetryAfter ?? TimeSpan.FromSeconds(5), ct);
}
AddEmmaApiProviders returns the IHttpClientBuilder, so you can attach a resilience handler
instead of catching:
services.AddEmmaApiProviders(configuration)
.AddStandardResilienceHandler();
If you do, raise the per-attempt timeout. The standard handler defaults to 10 seconds, which is not enough to fetch a 500-record member page.
Errors
Every non-success response raises EmmaException with typed detail โ no string matching required:
catch (EmmaException ex)
{
logger.LogError("Emma {Status} on {Method} {Resource}: {Body}",
ex.StatusCode, ex.Method, ex.Resource, ex.ResponseBody);
}
Providers
| Interface | Covers |
|---|---|
IEmmaAutomationProvider |
Automation workflows |
IEmmaFieldsProvider |
Custom member fields, including ClearField to reset a single field across every member |
IEmmaGroupProvider |
Groups and bulk group membership |
IEmmaMailingProvider |
Mailings, their HTML, recipients; pausing and cancelling |
IEmmaMemberProvider |
Members, statuses, and bulk imports โ prefer the bulk calls over looping |
IEmmaResponseProvider |
Mailing response data, down to who opened what |
IEmmaSearchProvider |
Saved searches and their matching members |
IEmmaSignupFormProvider |
Sign-up forms |
IEmmaSubscriptionProvider |
Subscriptions and subscription members |
IEmmaWebhookProvider |
Webhooks |
All methods are asynchronous and accept a trailing CancellationToken.
Paging
Endpoints that page take start and end. Emma's range is inclusive, so a 500-record page is
end = start + 499. Omit both and you get the first page.
Versioning
8.0.0 is a breaking release โ see the
changelog. The short version: RestSharp and
Newtonsoft.Json removed, EmmaException no longer exposes a RestSharp type, ids widened from int
to long, and every method gained a CancellationToken.
Contributing
This project is not affiliated with Emma. Everyone working on it is a
volunteer. Fork the repo, make your
changes, and open a pull request โ CI builds all three target frameworks, runs the tests on
net472, net8.0 and net10.0, and runs CodeQL.
Emma's own API documentation is at https://api.myemma.com/.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Microsoft.Extensions.Http (>= 8.0.0 && < 9.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 8.0.0 && < 9.0.0)
- System.Text.Json (>= 8.0.5 && < 9.0.0)
-
net10.0
- Microsoft.Extensions.Http (>= 10.0.0 && < 11.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.0 && < 11.0.0)
-
net8.0
- Microsoft.Extensions.Http (>= 8.0.0 && < 9.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 8.0.0 && < 9.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.