Skip to content

Commit 246cff4

Browse files
authored
feat: Add data residency for eu and global regions (#1190)
1 parent e66576c commit 246cff4

12 files changed

+189
-42
lines changed

USE_CASES.md

+24
Original file line numberDiff line numberDiff line change
@@ -920,6 +920,30 @@ var client = new SendGridClient(options);
920920

921921
```
922922

923+
The SendGridClientOptions object can also be used to set the region to "eu", which will send the
924+
request to https://api.eu.sendgrid.com/. By default it is set to https://api.sendgrid.com/, e.g.
925+
926+
```csharp
927+
928+
var options = new SendGridClientOptions
929+
{
930+
ApiKey = Environment.GetEnvironmentVariable("NAME_OF_THE_ENVIRONMENT_VARIABLE_FOR_YOUR_SENDGRID_KEY"),
931+
ReliabilitySettings = new ReliabilitySettings(2, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(3)),
932+
Host = "Your-Host",
933+
UrlPath = "Url-Path",
934+
Version = "3",
935+
RequestHeaders = new Dictionary<string, string>() {{"header-key", "header-value"}}
936+
};
937+
options.SetDataResidency("eu");
938+
var client = new SendGridClient(options);
939+
940+
OR
941+
942+
options.SetDataResidency("global");
943+
var client = new SendGridClient(options);
944+
945+
```
946+
923947
<a name="domain-authentication"></a>
924948
# How to Setup a Domain Authentication
925949

examples/clients/clientOptions.cs

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using Newtonsoft.Json;
2+
using SendGrid;
3+
using SendGrid.Helpers.Mail; // If you are using the Mail Helper
4+
using System;
5+
6+
7+
var apiKey = Environment.GetEnvironmentVariable("NAME_OF_THE_ENVIRONMENT_VARIABLE_FOR_YOUR_SENDGRID_KEY");
8+
var options = new SendGridClientOptions();
9+
10+
11+
////////////////////////////////////////////////////////
12+
// Available Sendgrid Client Options to set region as "eu"
13+
14+
var client_option_region = "eu";
15+
options.SetDataResidency(client_option_region);
16+
var client = new SendGridClient(options);
17+
var response = await client.RequestAsync(method: SendGridClient.Method.GET, urlPath: "clients/stats");
18+
Console.WriteLine(response.StatusCode);
19+
Console.WriteLine(response.Body.ReadAsStringAsync().Result);
20+
Console.WriteLine(response.Headers.ToString());
21+
Console.ReadLine();
22+
23+
////////////////////////////////////////////////////////
24+
// Available Sendgrid Client Options to set region as "global"
25+
26+
var client_option_region = "global";
27+
options.SetDataResidency(client_option_region);
28+
var client = new SendGridClient(options);
29+
var response = await client.RequestAsync(method: SendGridClient.Method.GET, urlPath: "clients/stats");
30+
Console.WriteLine(response.StatusCode);
31+
Console.WriteLine(response.Body.ReadAsStringAsync().Result);
32+
Console.WriteLine(response.Headers.ToString());
33+
Console.ReadLine();
34+

src/SendGrid/Permissions/ScopeOptions.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ public enum ScopeOptions
1414
/// <summary>
1515
/// Read-only scopes. When fitlering scopes this will include only those that end with ".read"
1616
/// </summary>
17-
ReadOnly
17+
ReadOnly
1818
}
1919
}

src/SendGrid/Permissions/SendGridPermissionsBuilder.Scopes.cs

+30-30
Original file line numberDiff line numberDiff line change
@@ -296,17 +296,17 @@ partial class SendGridPermissionsBuilder
296296
#endregion
297297
#region Mail
298298
,{ SendGridPermission.Mail, new[]
299-
{
299+
{
300300
"mail.batch.create",
301301
"mail.batch.delete",
302302
"mail.batch.read",
303303
"mail.batch.update",
304304
"mail.send"
305-
}}
305+
}}
306306
#endregion
307307
#region Mail Settings
308308
,{ SendGridPermission.MailSettings, new[]
309-
{
309+
{
310310
"mail_settings.address_whitelist.read",
311311
"mail_settings.address_whitelist.update",
312312
"mail_settings.bounce_purge.read",
@@ -319,56 +319,56 @@ partial class SendGridPermissionsBuilder
319319
"mail_settings.forward_spam.update",
320320
"mail_settings.template.read",
321321
"mail_settings.template.update"
322-
}}
322+
}}
323323
#endregion
324324
#region Marketing Campaigns
325325
,{ SendGridPermission.MarketingCampaigns, new[]
326-
{
326+
{
327327
"marketing_campaigns.create",
328328
"marketing_campaigns.delete",
329329
"marketing_campaigns.read",
330330
"marketing_campaigns.update"
331-
}}
331+
}}
332332
#endregion
333333
#region Newsletter
334334
,{ SendGridPermission.Newsletter, new[]
335-
{
335+
{
336336
"newsletter.create",
337337
"newsletter.delete",
338338
"newsletter.read",
339339
"newsletter.update"
340-
}}
340+
}}
341341
#endregion
342342
#region PartnerSettings
343343
,{ SendGridPermission.PartnerSettings, new[]
344-
{
344+
{
345345
"partner_settings.new_relic.read",
346346
"partner_settings.new_relic.update",
347347
"partner_settings.read"
348-
}}
348+
}}
349349
#endregion
350350
#region Reverse DNS
351351
,{ SendGridPermission.ReverseDNS, new[]
352-
{
352+
{
353353
"access_settings.activity.read",
354354
"access_settings.whitelist.create",
355355
"access_settings.whitelist.delete",
356356
"access_settings.whitelist.read",
357357
"access_settings.whitelist.update"
358-
}}
358+
}}
359359
#endregion
360360
#region Scheduled Sends
361361
,{ SendGridPermission.ScheduledSends, new[]
362-
{
362+
{
363363
"user.scheduled_sends.create",
364364
"user.scheduled_sends.delete",
365365
"user.scheduled_sends.read",
366366
"user.scheduled_sends.update"
367-
}}
367+
}}
368368
#endregion
369369
#region Stats
370370
,{ SendGridPermission.Stats, new[]
371-
{
371+
{
372372
"email_activity.read",
373373
"stats.read",
374374
"stats.global.read",
@@ -381,11 +381,11 @@ partial class SendGridPermissionsBuilder
381381
"clients.stats.read",
382382
"clients.tablet.stats.read",
383383
"clients.webmail.stats.read"
384-
}}
384+
}}
385385
#endregion
386386
#region Subusers
387387
,{ SendGridPermission.Subusers, new[]
388-
{
388+
{
389389
"subusers.create",
390390
"subusers.delete",
391391
"subusers.read",
@@ -407,11 +407,11 @@ partial class SendGridPermissionsBuilder
407407
"subusers.stats.monthly.read",
408408
"subusers.stats.sums.read",
409409
"subusers.summary.read"
410-
}}
410+
}}
411411
#endregion
412412
#region Suppressions
413413
,{ SendGridPermission.Suppressions, new[]
414-
{
414+
{
415415
"suppression.create",
416416
"suppression.delete",
417417
"suppression.read",
@@ -436,20 +436,20 @@ partial class SendGridPermissionsBuilder
436436
"suppression.unsubscribes.read",
437437
"suppression.unsubscribes.update",
438438
"suppression.unsubscribes.delete"
439-
}}
439+
}}
440440
#endregion
441441
#region Teammates
442442
,{ SendGridPermission.Teammates, new[]
443-
{
443+
{
444444
"teammates.create",
445445
"teammates.read",
446446
"teammates.update",
447447
"teammates.delete"
448-
}}
448+
}}
449449
#endregion
450450
#region Templates
451451
,{ SendGridPermission.Templates, new[]
452-
{
452+
{
453453
"templates.create",
454454
"templates.delete",
455455
"templates.read",
@@ -462,11 +462,11 @@ partial class SendGridPermissionsBuilder
462462
"templates.versions.delete",
463463
"templates.versions.read",
464464
"templates.versions.update"
465-
}}
465+
}}
466466
#endregion
467467
#region Tracking
468468
,{ SendGridPermission.Tracking, new[]
469-
{
469+
{
470470
"tracking_settings.click.read",
471471
"tracking_settings.click.update",
472472
"tracking_settings.google_analytics.read",
@@ -476,11 +476,11 @@ partial class SendGridPermissionsBuilder
476476
"tracking_settings.read",
477477
"tracking_settings.subscription.read",
478478
"tracking_settings.subscription.update"
479-
}}
479+
}}
480480
#endregion
481481
#region User Settings
482482
,{ SendGridPermission.UserSettings, new[]
483-
{
483+
{
484484
"user.account.read",
485485
"user.credits.read",
486486
"user.email.create",
@@ -501,11 +501,11 @@ partial class SendGridPermissionsBuilder
501501
"user.timezone.update",
502502
"user.username.read",
503503
"user.username.update"
504-
}}
504+
}}
505505
#endregion
506506
#region Webhooks
507507
,{ SendGridPermission.Webhook, new[]
508-
{
508+
{
509509
"user.webhooks.event.settings.read",
510510
"user.webhooks.event.settings.update",
511511
"user.webhooks.event.test.create",
@@ -516,7 +516,7 @@ partial class SendGridPermissionsBuilder
516516
"user.webhooks.parse.settings.read",
517517
"user.webhooks.parse.settings.update",
518518
"user.webhooks.parse.stats.read"
519-
}}
519+
}}
520520
#endregion
521521
};
522522
}

src/SendGrid/SendGridClient.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
23
using System.Net;
34
using System.Net.Http;
45

@@ -90,5 +91,6 @@ private static SendGridClientOptions buildOptions(string apiKey, string host, Di
9091
HttpErrorAsException = httpErrorAsException
9192
};
9293
}
94+
9395
}
9496
}

src/SendGrid/SendGridClientOptions.cs

+31
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Net.Http.Headers;
34

45
namespace SendGrid
@@ -8,6 +9,12 @@ namespace SendGrid
89
/// </summary>
910
public class SendGridClientOptions : BaseClientOptions
1011
{
12+
Dictionary<string, string> REGION_HOST_MAP = new Dictionary<string, string>
13+
{
14+
{"eu", "https://api.eu.sendgrid.com/"},
15+
{"global", "https://api.sendgrid.com/"}
16+
};
17+
1118
/// <summary>
1219
/// Initializes a new instance of the <see cref="SendGridClientOptions"/> class.
1320
/// </summary>
@@ -36,5 +43,29 @@ public string ApiKey
3643
Auth = new AuthenticationHeaderValue("Bearer", apiKey);
3744
}
3845
}
46+
47+
/// <summary>
48+
/// Sets the data residency for the SendGrid client.
49+
/// </summary>
50+
/// <param name="region">The desired data residency region ("global" or "eu").</param>
51+
/// Global is the default residency (or region)
52+
/// Global region means the message will be sent through https://api.sendgrid.com
53+
/// EU region means the message will be sent through https://api.eu.sendgrid.com
54+
/// <returns>The updated SendGridClientOptions instance.</returns>
55+
public SendGridClientOptions SetDataResidency(string region)
56+
{
57+
if (string.IsNullOrWhiteSpace(region))
58+
{
59+
throw new ArgumentNullException(nameof(region));
60+
}
61+
62+
if (!REGION_HOST_MAP.ContainsKey(region))
63+
{
64+
throw new InvalidOperationException("Region can only be 'global' or 'eu'.");
65+
}
66+
string result = REGION_HOST_MAP.ContainsKey(region) ? REGION_HOST_MAP[region] : "https://api.sendgrid.com";
67+
Host = result;
68+
return this;
69+
}
3970
}
4071
}

tests/SendGrid.Tests/Integration.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -6123,7 +6123,7 @@ public async Task TestRetryBehaviourSucceedsOnSecondAttempt()
61236123
[Fact]
61246124
public async void TestHttpErrorAsExceptionWhenSetInOptions()
61256125
{
6126-
await TestHttpErrorAsException((client, apiKey) => new SendGridClient(client, new SendGridClientOptions {ApiKey = apiKey, HttpErrorAsException = true}));
6126+
await TestHttpErrorAsException((client, apiKey) => new SendGridClient(client, new SendGridClientOptions { ApiKey = apiKey, HttpErrorAsException = true }));
61276127
}
61286128

61296129
[Fact]

tests/SendGrid.Tests/LicenseTests.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
namespace SendGrid.Tests
22
{
3-
using System;
4-
using System.IO;
5-
using System.Linq;
6-
using System.Reflection;
7-
using Xunit;
3+
using System;
4+
using System.IO;
5+
using System.Linq;
6+
using System.Reflection;
7+
using Xunit;
88

99
public class LicenseTests
1010
{

tests/SendGrid.Tests/PermissionsBuilderTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public void IncludeThrowsIfAnyScopeParamIsInvalid()
7878
public void IncludeThrowsIfAnyScopeIsInvalid()
7979
{
8080
var sb = new SendGridPermissionsBuilder();
81-
Assert.Throws<InvalidOperationException>(() => sb.Include(new [] {"alert.create", "bad.scope" }));
81+
Assert.Throws<InvalidOperationException>(() => sb.Include(new[] { "alert.create", "bad.scope" }));
8282
}
8383

8484
[Fact]

tests/SendGrid.Tests/Reliability/ReliabilitySettingsTests.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ public class ReliabilitySettingsTests
99
[Fact]
1010
public void ShouldNotAllowNegativeRetryCount()
1111
{
12-
var exception = Assert.Throws<ArgumentOutOfRangeException>(() =>
13-
new ReliabilitySettings(-1,
12+
var exception = Assert.Throws<ArgumentOutOfRangeException>(() =>
13+
new ReliabilitySettings(-1,
1414
TimeSpan.FromSeconds(1),
1515
TimeSpan.FromSeconds(1),
1616
TimeSpan.FromSeconds(1)));

tests/SendGrid.Tests/RequiredFilesExistTest.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ public class TestRequiredFilesExist
77
{
88

99
// ./Docker or docker/Docker
10-
public void checkDockerExists() {
10+
public void checkDockerExists()
11+
{
1112
bool dockerExists = File.Exists("./Dockerfile") ||
1213
File.Exists("./docker/Dockerfile");
1314
Assert.True(dockerExists);

0 commit comments

Comments
 (0)