diff --git a/CHANGELOG.md b/CHANGELOG.md index 44c06cf1b..34aa6fadd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,18 @@ -# Change Log +# Change Log All notable changes to this project will be documented in this file. +## [6.3.0] - 2015-11-24 +###Added +- Send emails using API Key + ## [6.2.0] - 2015-11-18 ###Added -- Added support for using the Web API v3 endpoints. +- Added support for using the Web API v3 endpoints - Implemented the api_keys endpoint [GET, POST, PATCH, DELETE] ## [6.1.0] - 2015-4-27 ###Added -- Added support for sending via API keys in addition to credentials. Pass an API Key string to the Web transport constructor. +- Added support for sending via API keys in addition to credentials. Pass an API Key string to the Web transport constructor ## [6.0.1] - 2015-4-24 ###Fixed diff --git a/README.md b/README.md index bbfd8bfe5..dd28d28e3 100644 --- a/README.md +++ b/README.md @@ -61,17 +61,8 @@ After creating an email message, you can send it using the Web API provided by S Sending email requires that you supply your SendGrid account credentials (username and password) OR a SendGrid API Key. API Key is the preferred method. API Keys are in beta. To configure API keys, visit https://sendgrid.com/beta/settings/api_keys -Using Credentials -```csharp -// Create network credentials to access your SendGrid account. -var username = "your_sendgrid_username"; -var pswd = "your_sendgrid_password"; - -var credentials = new NetworkCredential(username, pswd); -``` To send an email message, use the **DeliverAsync** method on the **Web** transport class, which calls the SendGrid Web API. The following example shows how to send a message. - ```csharp // Create the email object first, then add the properties. SendGridMessage myMessage = new SendGridMessage(); @@ -80,18 +71,12 @@ myMessage.From = new MailAddress("john@example.com", "John Smith"); myMessage.Subject = "Testing the SendGrid Library"; myMessage.Text = "Hello World!"; -// Create credentials, specifying your user name and password. -var credentials = new NetworkCredential("username", "password"); - -// Create an Web transport for sending email, using credentials... -//var transportWeb = new Web(credentials); - -// ...OR create a Web transport, using API Key (preferred) -var transportWeb = new Web("This string is an API key"); +// Create a Web transport, using API Key +var transportWeb = new Web("This string is a SendGrid API key"); // Send the email. transportWeb.DeliverAsync(myMessage); -// If your developing a Console Application, use the following +// NOTE: If your developing a Console Application, use the following so that the API call has time to complete // transportWeb.DeliverAsync(myMessage).Wait(); ``` diff --git a/SendGrid/Example/Program.cs b/SendGrid/Example/Program.cs index 4cdc71ae0..62e80604e 100644 --- a/SendGrid/Example/Program.cs +++ b/SendGrid/Example/Program.cs @@ -22,15 +22,9 @@ private static void Main() private static void SendAsync(SendGrid.SendGridMessage message) { - // Create credentials, specifying your user Name and password. - var username = Environment.GetEnvironmentVariable("SENDGRID_USERNAME"); - var password = Environment.GetEnvironmentVariable("SENDGRID_PASSWORD"); - //string apikey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY"); - var credentials = new NetworkCredential(username, password); - + string apikey = Environment.GetEnvironmentVariable("SENDGRID_APIKEY"); // Create a Web transport for sending email. - var transportWeb = new SendGrid.Web(credentials); - //var transportWeb2 = new SendGrid.Web(apikey); + var transportWeb = new SendGrid.Web(apikey); // Send the email. try diff --git a/SendGrid/SendGrid/Properties/AssemblyInfo.cs b/SendGrid/SendGrid/Properties/AssemblyInfo.cs index ce788f2f7..84b1ab95b 100644 --- a/SendGrid/SendGrid/Properties/AssemblyInfo.cs +++ b/SendGrid/SendGrid/Properties/AssemblyInfo.cs @@ -6,9 +6,9 @@ // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("SendGrid")] -[assembly: AssemblyDescription("")] +[assembly: AssemblyDescription("A client library for SendGrid Web API v3 endpoints")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] +[assembly: AssemblyCompany("SendGrid")] [assembly: AssemblyProduct("SendGrid")] [assembly: AssemblyCopyright("Copyright © 2015")] [assembly: AssemblyTrademark("")] @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("6.3.0")] +[assembly: AssemblyFileVersion("6.3.0")] diff --git a/SendGrid/SendGridMail/Properties/AssemblyInfo.cs b/SendGrid/SendGridMail/Properties/AssemblyInfo.cs index 5ca685d32..bedd624df 100644 --- a/SendGrid/SendGridMail/Properties/AssemblyInfo.cs +++ b/SendGrid/SendGridMail/Properties/AssemblyInfo.cs @@ -58,5 +58,5 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("6.0.0")] -[assembly: AssemblyFileVersion("6.0.0")] \ No newline at end of file +[assembly: AssemblyVersion("6.3.0")] +[assembly: AssemblyFileVersion("6.3.0")] \ No newline at end of file diff --git a/SendGrid/SendGridMail/Transport/Web.cs b/SendGrid/SendGridMail/Transport/Web.cs index e08a5a28e..eb664278c 100644 --- a/SendGrid/SendGridMail/Transport/Web.cs +++ b/SendGrid/SendGridMail/Transport/Web.cs @@ -14,154 +14,175 @@ // ReSharper disable MemberCanBePrivate.Global namespace SendGrid { - public class Web : ITransport - { - #region Properties - - //TODO: Make this configurable - public const String Endpoint = "https://api.sendgrid.com/api/mail.send.xml"; - - private readonly NetworkCredential _credentials; - private readonly HttpClient _client; - - #endregion - - /// - /// Creates a new Web interface for sending mail - /// - /// SendGridMessage user parameters + public class Web : ITransport + { + #region Properties + + //TODO: Make this configurable + public const String Endpoint = "https://api.sendgrid.com/api/mail.send.xml"; + private readonly NetworkCredential _credentials; + private readonly HttpClient _client; + private readonly string _apiKey; + + #endregion + + /// + /// Creates a new Web interface for sending mail + /// + /// The API Key with which to send + public Web(string apiKey) + : this(apiKey, null, TimeSpan.FromSeconds(100)) { } + + /// + /// Creates a new Web interface for sending mail + /// + /// SendGridMessage user parameters public Web(NetworkCredential credentials) - : this(credentials, TimeSpan.FromSeconds(100)) { } + : this(null, credentials, TimeSpan.FromSeconds(100)) { } /// /// Creates a new Web interface for sending mail. /// + /// The API Key with which to send /// SendGridMessage user parameters /// HTTP request timeout - public Web(NetworkCredential credentials, TimeSpan httpTimeout) - { - _credentials = credentials; + public Web(string apiKey, NetworkCredential credentials, TimeSpan httpTimeout) + { + _credentials = credentials; _client = new HttpClient(); - + _apiKey = apiKey; + var version = Assembly.GetExecutingAssembly().GetName().Version.ToString(); + if (credentials == null) + { + _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _apiKey); + } _client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "sendgrid/" + version + ";csharp"); _client.Timeout = httpTimeout; - } - - /// - /// Asynchronously delivers a message over SendGrid's Web interface - /// - /// - public async Task DeliverAsync(ISendGrid message) - { - var content = new MultipartFormDataContent(); - AttachFormParams(message, content); - AttachFiles(message, content); - var response = await _client.PostAsync(Endpoint, content); - await ErrorChecker.CheckForErrorsAsync(response); - } - - #region Support Methods - - private void AttachFormParams(ISendGrid message, MultipartFormDataContent content) - { - var formParams = FetchFormParams(message); - foreach (var keyValuePair in formParams) - { - content.Add(new StringContent(keyValuePair.Value), keyValuePair.Key); - } - } - - private void AttachFiles(ISendGrid message, MultipartFormDataContent content) - { - var files = FetchFileBodies(message); - foreach (var file in files) - { - var fs = new FileStream(file.Key, FileMode.Open, FileAccess.Read); - var fileContent = new StreamContent(fs); - - fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") - { - Name = "files[" + Path.GetFileName(file.Key) + "]", - FileName = Path.GetFileName(file.Key) - }; - - fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/octet-stream"); - content.Add(fileContent); - } - - var streamingFiles = FetchStreamingFileBodies(message); - foreach (var file in streamingFiles) - { - var stream = file.Value; - var fileContent = new StreamContent(stream); - - fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") - { - Name = "files[" + Path.GetFileName(file.Key) + "]", - FileName = Path.GetFileName(file.Key) - }; - - fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/octet-stream"); - content.Add(fileContent); - } - } - - - - internal List> FetchFormParams(ISendGrid message) - { - var result = new List> - { - new KeyValuePair("api_user", _credentials.UserName), - new KeyValuePair("api_key", _credentials.Password), - new KeyValuePair("headers", - message.Headers.Count == 0 ? null : Utils.SerializeDictionary(message.Headers)), - new KeyValuePair("replyto", - message.ReplyTo.Length == 0 ? null : message.ReplyTo.ToList().First().Address), - new KeyValuePair("from", message.From.Address), - new KeyValuePair("fromname", message.From.DisplayName), - new KeyValuePair("subject", message.Subject), - new KeyValuePair("text", message.Text), - new KeyValuePair("html", message.Html), - new KeyValuePair("x-smtpapi", message.Header.JsonString() ?? "") - }; - if (message.To != null) - { - result = result.Concat(message.To.ToList().Select(a => new KeyValuePair("to[]", a.Address))) - .Concat(message.To.ToList().Select(a => new KeyValuePair("toname[]", a.DisplayName))) - .ToList(); - } - - if (message.Cc != null) - { - result.AddRange(message.Cc.Select(c => new KeyValuePair("cc[]", c.Address))); - } - - if (message.Bcc != null) - { - result.AddRange(message.Bcc.Select(c => new KeyValuePair("bcc[]", c.Address))); - } - - if (message.GetEmbeddedImages().Count > 0) { - result = result.Concat(message.GetEmbeddedImages().ToList().Select(x => new KeyValuePair(string.Format("content[{0}]", x.Key), x.Value))) - .ToList(); - } - return result.Where(r => !String.IsNullOrEmpty(r.Value)).ToList(); - } - - internal IEnumerable> FetchStreamingFileBodies(ISendGrid message) - { - return message.StreamedAttachments.Select(kvp => kvp).ToList(); - } - - internal List> FetchFileBodies(ISendGrid message) - { - return message.Attachments == null - ? new List>() - : message.Attachments.Select(name => new KeyValuePair(name, new FileInfo(name))).ToList(); - } - - #endregion - } + } + + /// + /// Asynchronously delivers a message over SendGrid's Web interface + /// + /// + public async Task DeliverAsync(ISendGrid message) + { + var content = new MultipartFormDataContent(); + AttachFormParams(message, content); + AttachFiles(message, content); + var response = await _client.PostAsync(Endpoint, content); + await ErrorChecker.CheckForErrorsAsync(response); + } + + #region Support Methods + + private void AttachFormParams(ISendGrid message, MultipartFormDataContent content) + { + var formParams = FetchFormParams(message); + foreach (var keyValuePair in formParams) + { + content.Add(new StringContent(keyValuePair.Value), keyValuePair.Key); + } + } + + private void AttachFiles(ISendGrid message, MultipartFormDataContent content) + { + var files = FetchFileBodies(message); + foreach (var file in files) + { + var fs = new FileStream(file.Key, FileMode.Open, FileAccess.Read); + var fileContent = new StreamContent(fs); + + fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") + { + Name = "files[" + Path.GetFileName(file.Key) + "]", + FileName = Path.GetFileName(file.Key) + }; + + fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/octet-stream"); + content.Add(fileContent); + } + + var streamingFiles = FetchStreamingFileBodies(message); + foreach (var file in streamingFiles) + { + var stream = file.Value; + var fileContent = new StreamContent(stream); + + fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") + { + Name = "files[" + Path.GetFileName(file.Key) + "]", + FileName = Path.GetFileName(file.Key) + }; + + fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/octet-stream"); + content.Add(fileContent); + } + } + + internal List> FetchFormParams(ISendGrid message) + { + var result = new List> + { + new KeyValuePair("headers", + message.Headers.Count == 0 ? null : Utils.SerializeDictionary(message.Headers)), + new KeyValuePair("replyto", + message.ReplyTo.Length == 0 ? null : message.ReplyTo.ToList().First().Address), + new KeyValuePair("from", message.From.Address), + new KeyValuePair("fromname", message.From.DisplayName), + new KeyValuePair("subject", message.Subject), + new KeyValuePair("text", message.Text), + new KeyValuePair("html", message.Html), + new KeyValuePair("x-smtpapi", message.Header.JsonString() ?? "") + }; + + //If the API key is not specified, use the username and password + if (_credentials != null) + { + var creds = new List> + { + new KeyValuePair("api_user", _credentials.UserName), + new KeyValuePair("api_key", _credentials.Password) + }; + result.AddRange(creds); + } + + if (message.To != null) + { + result = result.Concat(message.To.ToList().Select(a => new KeyValuePair("to[]", a.Address))) + .Concat(message.To.ToList().Select(a => new KeyValuePair("toname[]", a.DisplayName))) + .ToList(); + } + + if (message.Cc != null) + { + result.AddRange(message.Cc.Select(c => new KeyValuePair("cc[]", c.Address))); + } + + if (message.Bcc != null) + { + result.AddRange(message.Bcc.Select(c => new KeyValuePair("bcc[]", c.Address))); + } + + if (message.GetEmbeddedImages().Count > 0) { + result = result.Concat(message.GetEmbeddedImages().ToList().Select(x => new KeyValuePair(string.Format("content[{0}]", x.Key), x.Value))) + .ToList(); + } + return result.Where(r => !String.IsNullOrEmpty(r.Value)).ToList(); + } + + internal IEnumerable> FetchStreamingFileBodies(ISendGrid message) + { + return message.StreamedAttachments.Select(kvp => kvp).ToList(); + } + + internal List> FetchFileBodies(ISendGrid message) + { + return message.Attachments == null + ? new List>() + : message.Attachments.Select(name => new KeyValuePair(name, new FileInfo(name))).ToList(); + } + + #endregion + } } diff --git a/SendGrid/Tests/Transport/TestWebApi.cs b/SendGrid/Tests/Transport/TestWebApi.cs index 5e09b9d33..ecb1ad8ee 100644 --- a/SendGrid/Tests/Transport/TestWebApi.cs +++ b/SendGrid/Tests/Transport/TestWebApi.cs @@ -9,76 +9,89 @@ namespace Transport { - [TestFixture] - internal class TestWebApi - { - private const string TestUsername = "username"; - private const string TestPassword = "password"; + [TestFixture] + internal class TestWebApi + { + private const string TestUsername = "username"; + private const string TestPassword = "password"; + private const string TestApiKey = "apikey"; - - [Test] - public void TestFetchFileBodies() - { - var webApi = new Web(new NetworkCredential(TestUsername, TestPassword)); - var message = new Mock(); - var attachments = new[] {"foo", "bar", "foobar"}; - message.SetupProperty(foo => foo.Attachments, null); - var result = webApi.FetchFileBodies(message.Object); - Assert.AreEqual(0, result.Count); + [Test] + public void TestFetchFileBodies() + { + TestFetchFileBodiesHelper(new Web(new NetworkCredential(TestUsername, TestPassword))); + TestFetchFileBodiesHelper(new Web(TestApiKey)); + } - message.SetupProperty(foo => foo.Attachments, attachments); - result = webApi.FetchFileBodies(message.Object); - Assert.AreEqual(attachments.Count(), result.Count); - for (var index = 0; index < attachments.Length; index++) - Assert.AreEqual(result[index].Value.Name, attachments[index]); - } + public void TestFetchFileBodiesHelper(Web webApi) + { + // Test using credentials + var message = new Mock(); + var attachments = new[] {"foo", "bar", "foobar"}; + message.SetupProperty(foo => foo.Attachments, null); + var result = webApi.FetchFileBodies(message.Object); + Assert.AreEqual(0, result.Count); - [Test] - public void TestFetchFormParams() - { - // Test Variables - const string toAddress = "foobar@outlook.com"; - const string ccAddress = "cc@outlook.com"; - const string bcc1Address = "bcc1@outlook.com"; - const string bcc2Address = "bcc2@outlook.com"; - MailAddress[] bccAddresses = {new MailAddress(bcc1Address), new MailAddress(bcc2Address)}; - const string fromAddress = "test@outlook.com"; - const string subject = "Test Subject"; - const string textBody = "Test Text Body"; - const string htmlBody = "

Test HTML Body

"; - const string headerKey = "headerkey"; - var testHeader = new Dictionary { { headerKey, "headervalue" } }; - const string categoryName = "Example Category"; + message.SetupProperty(foo => foo.Attachments, attachments); + result = webApi.FetchFileBodies(message.Object); + Assert.AreEqual(attachments.Count(), result.Count); + for (var index = 0; index < attachments.Length; index++) + Assert.AreEqual(result[index].Value.Name, attachments[index]); + } - var message = new SendGridMessage(); - message.AddTo(toAddress); + [Test] + public void TestFetchFormParams() + { + TestFetchFormParamsHelper(new Web(new NetworkCredential(TestUsername, TestPassword)), true); + TestFetchFormParamsHelper(new Web(TestApiKey), false); + } + + public void TestFetchFormParamsHelper(Web webApi, bool credentials) + { + // Test Variables + const string toAddress = "foobar@outlook.com"; + const string ccAddress = "cc@outlook.com"; + const string bcc1Address = "bcc1@outlook.com"; + const string bcc2Address = "bcc2@outlook.com"; + MailAddress[] bccAddresses = {new MailAddress(bcc1Address), new MailAddress(bcc2Address)}; + const string fromAddress = "test@outlook.com"; + const string subject = "Test Subject"; + const string textBody = "Test Text Body"; + const string htmlBody = "

Test HTML Body

"; + const string headerKey = "headerkey"; + var testHeader = new Dictionary { { headerKey, "headervalue" } }; + const string categoryName = "Example Category"; + + var message = new SendGridMessage(); + message.AddTo(toAddress); message.AddCc(ccAddress); - message.Bcc = bccAddresses; - message.From = new MailAddress(fromAddress); - message.Subject = subject; - message.Text = textBody; - message.Html = htmlBody; - message.AddHeaders(testHeader); - message.Header.SetCategory(categoryName); + message.Bcc = bccAddresses; + message.From = new MailAddress(fromAddress); + message.Subject = subject; + message.Text = textBody; + message.Html = htmlBody; + message.AddHeaders(testHeader); + message.Header.SetCategory(categoryName); - var webApi = new Web(new NetworkCredential(TestUsername, TestPassword)); - var result = webApi.FetchFormParams(message); - Assert.True(result.Any(r => r.Key == "api_user" && r.Value == TestUsername)); - Assert.True(result.Any(r => r.Key == "api_key" && r.Value == TestPassword)); - Assert.True(result.Any(r => r.Key == "to[]" && r.Value == toAddress)); + var result = webApi.FetchFormParams(message); + if (credentials) + { + Assert.True(result.Any(r => r.Key == "api_user" && r.Value == TestUsername)); + Assert.True(result.Any(r => r.Key == "api_key" && r.Value == TestPassword)); + } + Assert.True(result.Any(r => r.Key == "to[]" && r.Value == toAddress)); Assert.True(result.Any(r => r.Key == "cc[]" && r.Value == ccAddress)); Assert.True(result.Any(r => r.Key == "bcc[]" && r.Value == bcc1Address)); Assert.True(result.Any(r => r.Key == "bcc[]" && r.Value == bcc2Address)); - Assert.True(result.Any(r => r.Key == "from" && r.Value == fromAddress)); - Assert.True(result.Any(r => r.Key == "subject" && r.Value == subject)); - Assert.True(result.Any(r => r.Key == "text" && r.Value == textBody)); - Assert.True(result.Any(r => r.Key == "html" && r.Value == htmlBody)); - Assert.True( - result.Any( - r => r.Key == "headers" && r.Value == String.Format("{{\"{0}\":\"{1}\"}}", headerKey, testHeader[headerKey]))); - Assert.True( - result.Any(r => r.Key == "x-smtpapi" && r.Value == String.Format("{{\"category\" : \"{0}\"}}", categoryName))); - } - - } -} \ No newline at end of file + Assert.True(result.Any(r => r.Key == "from" && r.Value == fromAddress)); + Assert.True(result.Any(r => r.Key == "subject" && r.Value == subject)); + Assert.True(result.Any(r => r.Key == "text" && r.Value == textBody)); + Assert.True(result.Any(r => r.Key == "html" && r.Value == htmlBody)); + Assert.True( + result.Any( + r => r.Key == "headers" && r.Value == String.Format("{{\"{0}\":\"{1}\"}}", headerKey, testHeader[headerKey]))); + Assert.True( + result.Any(r => r.Key == "x-smtpapi" && r.Value == String.Format("{{\"category\" : \"{0}\"}}", categoryName))); + } + } +}