diff --git a/createsend-netstandard.sln b/createsend-netstandard.sln new file mode 100644 index 0000000..8587382 --- /dev/null +++ b/createsend-netstandard.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26403.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "createsend-netstandard", "createsend-netstandard\createsend-netstandard.csproj", "{74932B63-D31D-4682-BE98-5F96D0B91E04}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {74932B63-D31D-4682-BE98-5F96D0B91E04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {74932B63-D31D-4682-BE98-5F96D0B91E04}.Debug|Any CPU.Build.0 = Debug|Any CPU + {74932B63-D31D-4682-BE98-5F96D0B91E04}.Release|Any CPU.ActiveCfg = Release|Any CPU + {74932B63-D31D-4682-BE98-5F96D0B91E04}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/createsend-netstandard/Account.cs b/createsend-netstandard/Account.cs new file mode 100644 index 0000000..4b8fae2 --- /dev/null +++ b/createsend-netstandard/Account.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.Collections.Specialized; + +namespace createsend_dotnet +{ + public class Account : CreateSendBase + { + public Account(AuthenticationDetails auth) : base(auth) { } + + public string GetPrimaryContact() + { + return HttpGet("/primarycontact.json", null) + .EmailAddress; + } + + public string SetPrimaryContact(string emailAddress) + { + return HttpPut("/primarycontact.json", + new NameValueCollection { { "email", emailAddress } }, null) + .EmailAddress; + } + + public IEnumerable Administrators() + { + return HttpGet>( + "/admins.json", null); + } + } +} diff --git a/createsend-netstandard/Administrator.cs b/createsend-netstandard/Administrator.cs new file mode 100644 index 0000000..cd41622 --- /dev/null +++ b/createsend-netstandard/Administrator.cs @@ -0,0 +1,35 @@ +using System.Collections.Specialized; + +namespace createsend_dotnet +{ + public class Administrator : CreateSendBase + { + private string AdminsUrl { get { return string.Format("/admins.json"); } } + + public Administrator(AuthenticationDetails auth) : base(auth) { } + + public AdministratorDetails Details(string emailAddress) + { + return HttpGet( + AdminsUrl, new NameValueCollection {{"email", emailAddress}}); + } + + public string Add(AdministratorDetails admin) + { + return HttpPost( + AdminsUrl, null, admin).EmailAddress; + } + + public string Update(string emailAddress, AdministratorDetails admin) + { + return HttpPut( + AdminsUrl, new NameValueCollection {{ "email", emailAddress }}, + admin).EmailAddress; + } + + public void Delete(string emailAddress) + { + HttpDelete(AdminsUrl, new NameValueCollection {{"email", emailAddress}}); + } + } +} diff --git a/createsend-netstandard/AuthenticationDetails.cs b/createsend-netstandard/AuthenticationDetails.cs new file mode 100644 index 0000000..7639c01 --- /dev/null +++ b/createsend-netstandard/AuthenticationDetails.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace createsend_dotnet +{ + public abstract class AuthenticationDetails { } + + public class OAuthAuthenticationDetails : AuthenticationDetails + { + public string AccessToken { get; set; } + public string RefreshToken { get; set; } + + public OAuthAuthenticationDetails( + string accessToken, + string refreshToken) + { + AccessToken = accessToken; + RefreshToken = refreshToken; + } + } + + public class ApiKeyAuthenticationDetails : AuthenticationDetails + { + public string ApiKey { get; set; } + + public ApiKeyAuthenticationDetails(string apiKey) + { + ApiKey = apiKey; + } + } + + internal sealed class ClientApiKey : ApiKeyAuthenticationDetails + { + public ClientApiKey(string apiKey) : base(apiKey) + { + + } + } + + internal sealed class AccountApiKey : ApiKeyAuthenticationDetails, IProvideClientId + { + public string ClientId { get; private set; } + public AccountApiKey(string apiKey, string clientId = null) : base(apiKey) + { + ClientId = clientId; + } + } + + internal sealed class OAuthWithClientId : OAuthAuthenticationDetails, IProvideClientId + { + public string ClientId { get; private set; } + public OAuthWithClientId( + string accessToken, + string refreshToken, + string clientId) : base(accessToken, refreshToken) + { + if(clientId == null) throw new ArgumentNullException("clientId"); + + ClientId = clientId; + } + } + + public interface IProvideClientId + { + string ClientId { get; } + } + + public class BasicAuthAuthenticationDetails : AuthenticationDetails + { + public string Username { get; set; } + public string Password { get; set; } + + public BasicAuthAuthenticationDetails( + string username, + string password) + { + Username = username; + Password = password; + } + } +} \ No newline at end of file diff --git a/createsend-netstandard/Campaign.cs b/createsend-netstandard/Campaign.cs new file mode 100644 index 0000000..88d2380 --- /dev/null +++ b/createsend-netstandard/Campaign.cs @@ -0,0 +1,375 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Globalization; + +namespace createsend_dotnet +{ + public class Campaign : CreateSendBase + { + public string CampaignID { get; set; } + + public Campaign(AuthenticationDetails auth, string campaignID) + : base(auth) + { + CampaignID = campaignID; + } + + /// + /// Creates a campaign using the API key and campaign details provided. + /// + /// API key to use + /// Client ID of the client for whom the + /// campaign should be created + /// A subject for the campaign + /// A name for the campaign + /// From name for the campaign + /// From email address for the campaign + /// Reply-to address for the campaign + /// URL for the HTML content for the + /// campaign + /// URL for the text content for the campaign. + /// Note that you may provide textUrl as null or an empty string and + /// the text content for the campaign will be generated from the HTML + /// content. + /// IDs of the lists to which the campaign + /// will be sent + /// IDs of the segments to which the + /// campaign will be sent + /// The ID of the newly created campaign + public static string Create(AuthenticationDetails auth, string clientID, + string subject, string name, string fromName, string fromEmail, + string replyTo, string htmlUrl, string textUrl, List + listIDs, List segmentIDs) + { + return HttpHelper.Post, string>( + auth, string.Format("/campaigns/{0}.json", clientID), null, + new Dictionary() + { + { "Subject", subject }, + { "Name", name }, + { "FromName", fromName}, + { "FromEmail", fromEmail }, + { "ReplyTo", replyTo }, + { "HtmlUrl", htmlUrl }, + { "TextUrl", textUrl }, + { "ListIDs", listIDs }, + { "SegmentIDs", segmentIDs } + }); + } + + public static string CreateFromTemplate(AuthenticationDetails auth, string clientID, + string subject, string name, string fromName, string fromEmail, + string replyTo, List listIDs, List segmentIDs, + string templateID, TemplateContent templateContent) + { + return HttpHelper.Post, string>( + auth, string.Format("/campaigns/{0}/fromtemplate.json", clientID), null, + new Dictionary() + { + { "Subject", subject }, + { "Name", name }, + { "FromName", fromName}, + { "FromEmail", fromEmail }, + { "ReplyTo", replyTo }, + { "ListIDs", listIDs }, + { "SegmentIDs", segmentIDs }, + { "TemplateID", templateID }, + { "TemplateContent", templateContent } + }); + } + + public void SendPreview(List recipients, string personalize) + { + HttpPost, string>( + string.Format("/campaigns/{0}/sendpreview.json", CampaignID), + null, + new Dictionary() + { + { "PreviewRecipients", recipients}, + { "Personalize", personalize} + }); + } + + public void Send(string confirmationEmail) + { + Send(confirmationEmail, "immediately"); + } + + public void Send(string confirmationEmail, DateTime sendDate) + { + Send(confirmationEmail, sendDate.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)); + } + + private void Send(string confirmationEmail, string sendDate) + { + HttpPost, string>( + string.Format("/campaigns/{0}/send.json", CampaignID), + null, + new Dictionary() + { + { "ConfirmationEmail", confirmationEmail }, + { "SendDate", sendDate } + }); + } + + public void Unschedule() + { + HttpPost, string>( + string.Format("/campaigns/{0}/unschedule.json", CampaignID), + null, null); + } + + public void Delete() + { + HttpDelete(string.Format("/campaigns/{0}.json", CampaignID), null); + } + + public CampaignSummary Summary() + { + return HttpGet( + string.Format("/campaigns/{0}/summary.json", CampaignID), null); + } + + public IEnumerable EmailClientUsage() + { + return HttpGet>( + string.Format("/campaigns/{0}/emailclientusage.json", + CampaignID), null); + } + + public CampaignListsAndSegments ListsAndSegments() + { + return HttpGet( + string.Format("/campaigns/{0}/listsandsegments.json", CampaignID), null); + } + + public PagedCollection Recipients( + int page, int pageSize, string orderField, string orderDirection) + { + NameValueCollection queryArguments = new NameValueCollection(); + queryArguments.Add("page", page.ToString()); + queryArguments.Add("pagesize", pageSize.ToString()); + queryArguments.Add("orderfield", orderField); + queryArguments.Add("orderdirection", orderDirection); + + return HttpGet>( + string.Format("/campaigns/{0}/recipients.json", CampaignID), queryArguments); + } + + public PagedCollection Opens() + { + return Opens(1, 1000, "date", "asc"); + } + + public PagedCollection Opens( + int page, + int pageSize, + string orderField, + string orderDirection) + { + return Opens("", page, pageSize, orderField, orderDirection); + } + + public PagedCollection Opens( + DateTime fromDate, + int page, + int pageSize, + string orderField, + string orderDirection) + { + return Opens(fromDate.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture), page, + pageSize, orderField, orderDirection); + } + + private PagedCollection Opens( + string fromDate, + int page, + int pageSize, + string orderField, + string orderDirection) + { + NameValueCollection queryArguments = new NameValueCollection(); + queryArguments.Add("date", fromDate); + queryArguments.Add("page", page.ToString()); + queryArguments.Add("pagesize", pageSize.ToString()); + queryArguments.Add("orderfield", orderField); + queryArguments.Add("orderdirection", orderDirection); + + return HttpGet>( + string.Format("/campaigns/{0}/opens.json", CampaignID), queryArguments); + } + + public PagedCollection Unsubscribes() + { + return Unsubscribes(1, 1000, "date", "asc"); + } + + public PagedCollection Unsubscribes( + int page, + int pageSize, + string orderField, + string orderDirection) + { + return Unsubscribes("", page, pageSize, orderField, orderDirection); + } + + public PagedCollection Unsubscribes( + DateTime fromDate, + int page, + int pageSize, + string orderField, + string orderDirection) + { + return Unsubscribes(fromDate.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture), + page, pageSize, orderField, orderDirection); + } + + private PagedCollection Unsubscribes( + string fromDate, + int page, + int pageSize, + string orderField, + string orderDirection) + { + NameValueCollection queryArguments = new NameValueCollection(); + queryArguments.Add("date", fromDate); + queryArguments.Add("page", page.ToString()); + queryArguments.Add("pagesize", pageSize.ToString()); + queryArguments.Add("orderfield", orderField); + queryArguments.Add("orderdirection", orderDirection); + + return HttpGet>( + string.Format("/campaigns/{0}/unsubscribes.json", CampaignID), queryArguments); + } + + public PagedCollection SpamComplaints() + { + return SpamComplaints(1, 1000, "date", "asc"); + } + + public PagedCollection SpamComplaints( + int page, + int pageSize, + string orderField, + string orderDirection) + { + return SpamComplaints("", page, pageSize, orderField, orderDirection); + } + + public PagedCollection SpamComplaints( + DateTime fromDate, + int page, + int pageSize, + string orderField, + string orderDirection) + { + return SpamComplaints(fromDate.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture), + page, pageSize, orderField, orderDirection); + } + + private PagedCollection SpamComplaints( + string fromDate, + int page, + int pageSize, + string orderField, + string orderDirection) + { + NameValueCollection queryArguments = new NameValueCollection(); + queryArguments.Add("date", fromDate); + queryArguments.Add("page", page.ToString()); + queryArguments.Add("pagesize", pageSize.ToString()); + queryArguments.Add("orderfield", orderField); + queryArguments.Add("orderdirection", orderDirection); + + return HttpGet>( + string.Format("/campaigns/{0}/spam.json", CampaignID), queryArguments); + } + + public PagedCollection Clicks() + { + return Clicks(1, 1000, "date", "asc"); + } + + public PagedCollection Clicks( + int page, + int pageSize, + string orderField, + string orderDirection) + { + return Clicks("", page, pageSize, orderField, orderDirection); + } + + public PagedCollection Clicks( + DateTime fromDate, + int page, + int pageSize, + string orderField, + string orderDirection) + { + return Clicks(fromDate.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture), + page, pageSize, orderField, orderDirection); + } + + private PagedCollection Clicks( + string fromDate, + int page, + int pageSize, + string orderField, + string orderDirection) + { + NameValueCollection queryArguments = new NameValueCollection(); + queryArguments.Add("date", fromDate); + queryArguments.Add("page", page.ToString()); + queryArguments.Add("pagesize", pageSize.ToString()); + queryArguments.Add("orderfield", orderField); + queryArguments.Add("orderdirection", orderDirection); + + return HttpGet>( + string.Format("/campaigns/{0}/clicks.json", CampaignID), queryArguments); + } + + public PagedCollection Bounces() + { + return Bounces(1, 1000, "date", "asc"); + } + + public PagedCollection Bounces( + int page, + int pageSize, + string orderField, + string orderDirection) + { + return Bounces("", page, pageSize, orderField, orderDirection); + } + + public PagedCollection Bounces( + DateTime fromDate, + int page, + int pageSize, + string orderField, + string orderDirection) + { + return Bounces(fromDate.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture), + page, pageSize, orderField, orderDirection); + } + + private PagedCollection Bounces( + string fromDate, + int page, + int pageSize, + string orderField, + string orderDirection) + { + NameValueCollection queryArguments = new NameValueCollection(); + queryArguments.Add("date", fromDate); + queryArguments.Add("page", page.ToString()); + queryArguments.Add("pagesize", pageSize.ToString()); + queryArguments.Add("orderfield", orderField); + queryArguments.Add("orderdirection", orderDirection); + + return HttpGet>( + string.Format("/campaigns/{0}/bounces.json", CampaignID), queryArguments); + } + } +} diff --git a/createsend-netstandard/Client.cs b/createsend-netstandard/Client.cs new file mode 100644 index 0000000..dbeeb3b --- /dev/null +++ b/createsend-netstandard/Client.cs @@ -0,0 +1,196 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; + +namespace createsend_dotnet +{ + public class Client : CreateSendBase + { + public string ClientID { get; set; } + + public Client(AuthenticationDetails auth, string clientID) + : base(auth) + { + ClientID = clientID; + } + + public static string Create(AuthenticationDetails auth, string companyName, string country, string timezone) + { + return HttpHelper.Post( + auth, "/clients.json", null, + new ClientDetail() + { + CompanyName = companyName, + Country = country, + TimeZone = timezone + }); + } + + public ClientWithSettings Details() + { + return HttpGet(string.Format("/clients/{0}.json", ClientID), null); + } + + public IEnumerable Campaigns() + { + return HttpGet(string.Format("/clients/{0}/campaigns.json", ClientID), null); + } + + public IEnumerable Scheduled() + { + return HttpGet(string.Format("/clients/{0}/scheduled.json", ClientID), null); + } + + public IEnumerable Drafts() + { + return HttpGet(string.Format("/clients/{0}/drafts.json", ClientID), null); + } + + public IEnumerable Lists() + { + return HttpGet(string.Format("/clients/{0}/lists.json", ClientID), null); + } + + public IEnumerable ListsForEmail(string email) + { + NameValueCollection args = new NameValueCollection(); + args.Add("email", email); + return HttpGet(string.Format("/clients/{0}/listsforemail.json", ClientID), args); + } + + public IEnumerable Segments() + { + return HttpGet(string.Format("/clients/{0}/segments.json", ClientID), null); + } + + public PagedCollection SuppressionList(int page, int pageSize, string orderField, string orderDirection) + { + NameValueCollection queryArguments = new NameValueCollection(); + queryArguments.Add("page", page.ToString()); + queryArguments.Add("pagesize", pageSize.ToString()); + queryArguments.Add("orderfield", orderField); + queryArguments.Add("orderdirection", orderDirection); + + return HttpGet>(string.Format("/clients/{0}/suppressionlist.json", ClientID), queryArguments); + } + + public void Suppress(string[] emails) + { + HttpPost( + string.Format("/clients/{0}/suppress.json", ClientID), null, + new SuppressionDetails { EmailAddresses = emails }); + } + + public void Unsuppress(string email) + { + HttpPut( + string.Format("/clients/{0}/unsuppress.json", ClientID), + new NameValueCollection { { "email", email } }, null); + } + + public IEnumerable Templates() + { + return HttpGet(string.Format("/clients/{0}/templates.json", ClientID), null); + } + + public void SetBasics(string companyName, string country, string timezone) + { + HttpPut( + string.Format("/clients/{0}/setbasics.json", ClientID), null, + new ClientDetail() + { + CompanyName = companyName, + Country = country, + TimeZone = timezone + }); + } + + public void SetPAYGBilling(string currency, bool clientPays, bool canPurchaseCredits, int markupPercentage, decimal markupOnDelivery, decimal markupPerRecipient, decimal markupOnDesignSpamTest) + { + HttpPut( + string.Format("/clients/{0}/setpaygbilling.json", ClientID), null, + new BillingOptions() + { + Currency = currency, + ClientPays = clientPays, + CanPurchaseCredits = canPurchaseCredits, + MarkupPercentage = markupPercentage, + MarkupOnDelivery = markupOnDelivery, + MarkupPerRecipient = markupPerRecipient, + MarkupOnDesignSpamTest = markupOnDesignSpamTest + }); + } + + public void SetMonthlyBilling(string currency, bool clientPays, bool canPurchaseCredits, int markupPercentage) + { + SetMonthlyBilling(currency, clientPays, canPurchaseCredits, markupPercentage, null); + } + + public void SetMonthlyBilling(string currency, bool clientPays, bool canPurchaseCredits, int markupPercentage, MonthlyScheme scheme) + { + SetMonthlyBilling(currency, clientPays, canPurchaseCredits, markupPercentage, (MonthlyScheme?)scheme); + } + + private void SetMonthlyBilling(string currency, bool clientPays, bool canPurchaseCredits, int markupPercentage, MonthlyScheme? scheme) + { + HttpPut( + string.Format("/clients/{0}/setmonthlybilling.json", ClientID), null, + new BillingOptions() + { + Currency = currency, + ClientPays = clientPays, + CanPurchaseCredits = canPurchaseCredits, + MarkupPercentage = markupPercentage, + MonthlyScheme = scheme + }); + } + + /// + /// Transfer credits to or from this client. + /// + /// The number of credits to transfer. This + /// value may be either positive if you want to allocate credits + /// from your account to the client, or negative if you want to + /// deduct credits from the client back into your account. + /// If set to true, will + /// allow the client to continue sending using your credits or payment + /// details once they run out of credits, and if set to false, will + /// prevent the client from using your credits to continue sending + /// until you allocate more credits to them. + /// The details of the credits transfer, including the credits + /// in your account now, as well as the credits belonging to the client + /// now. + public CreditsTransferResult TransferCredits( + int credits, bool canUseMyCreditsWhenTheyRunOut) + { + return HttpPost( + string.Format("/clients/{0}/credits.json", ClientID), null, + new CreditsTransferDetails + { + Credits = credits, + CanUseMyCreditsWhenTheyRunOut = + canUseMyCreditsWhenTheyRunOut + }); + } + + public void Delete() + { + HttpDelete(string.Format("/clients/{0}.json", ClientID), null); + } + + public string GetPrimaryContact() + { + return HttpGet(string.Format("/clients/{0}/primarycontact.json", ClientID), null).EmailAddress; + } + + public string SetPrimaryContact(string emailAddress) + { + return HttpPut(string.Format("/clients/{0}/primarycontact.json", ClientID), new NameValueCollection { { "email", emailAddress } }, null).EmailAddress; + } + + public IEnumerable People() + { + return HttpGet>(string.Format("/clients/{0}/people.json", ClientID), null); + } + } +} diff --git a/createsend-netstandard/CreateSendBase.cs b/createsend-netstandard/CreateSendBase.cs new file mode 100644 index 0000000..c3d120b --- /dev/null +++ b/createsend-netstandard/CreateSendBase.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Text; +using Microsoft.AspNetCore.WebUtilities; + +namespace createsend_dotnet +{ + public abstract class CreateSendBase + { + private readonly ICreateSendOptions options; + + public CreateSendBase(AuthenticationDetails auth, ICreateSendOptions options = null) + { + this.options = options ?? new CreateSendOptionsWrapper(); + + Authenticate(auth); + } + + public AuthenticationDetails AuthDetails { get; set; } + + public void Authenticate(AuthenticationDetails auth) + { + AuthDetails = auth; + } + + public string HttpDelete(string path, NameValueCollection queryArguments) + { + return HttpHelper.Delete(AuthDetails, path, queryArguments, options.BaseUri, HttpHelper.APPLICATION_JSON_CONTENT_TYPE); + } + + public U HttpGet(string path, NameValueCollection queryArguments) + { + return HttpGet(path, queryArguments); + } + + public U HttpGet(string path, NameValueCollection queryArguments) + where EX : ErrorResult + { + return HttpHelper.Get(AuthDetails, options.BaseUri, path, queryArguments); + } + + public U HttpPost(string path, NameValueCollection queryArguments, T payload) + where T : class + { + return HttpPost(path, queryArguments, payload); + } + + public U HttpPost(string path, NameValueCollection queryArguments, T payload) + where T : class + where EX : ErrorResult + { + return HttpHelper.Post(AuthDetails, path, queryArguments, payload, options.BaseUri, HttpHelper.APPLICATION_JSON_CONTENT_TYPE); + } + + public U HttpPut(string path, NameValueCollection queryArguments, T payload) where T : class + { + return HttpHelper.Put(AuthDetails, path, queryArguments, payload, options.BaseUri, HttpHelper.APPLICATION_JSON_CONTENT_TYPE); + } + + public OAuthTokenDetails RefreshToken() + { + if (AuthDetails == null || + !(AuthDetails is OAuthAuthenticationDetails) || + string.IsNullOrEmpty( + (AuthDetails as OAuthAuthenticationDetails) + .RefreshToken)) + throw new InvalidOperationException( + "You cannot refresh an OAuth token when you don't have a refresh token."); + + string refreshToken = (this.AuthDetails as OAuthAuthenticationDetails) + .RefreshToken; + + var values = new Dictionary(); + values.Add("grant_type", "refresh_token"); + values.Add("refresh_token", refreshToken); + string body = QueryHelpers.AddQueryString("", values); + + OAuthTokenDetails newTokenDetails = + HttpHelper.Post( + null, "/token", new NameValueCollection(), body, + options.BaseOAuthUri, + HttpHelper.APPLICATION_FORM_URLENCODED_CONTENT_TYPE); + Authenticate( + new OAuthAuthenticationDetails( + newTokenDetails.access_token, newTokenDetails.refresh_token)); + return newTokenDetails; + } + } +} \ No newline at end of file diff --git a/createsend-netstandard/CreateSendOptions.cs b/createsend-netstandard/CreateSendOptions.cs new file mode 100644 index 0000000..a066f70 --- /dev/null +++ b/createsend-netstandard/CreateSendOptions.cs @@ -0,0 +1,36 @@ +using System; + +namespace createsend_dotnet +{ + public static class CreateSendOptions + { + private static string base_oauth_uri; + private static string base_uri; + + static CreateSendOptions() + { + base_uri = "https://api.createsend.com/api/v3.1"; + base_oauth_uri = "https://api.createsend.com/oauth"; + } + + public static string BaseOAuthUri + { + get { return base_oauth_uri; } + set { base_oauth_uri = value; } + } + + public static string BaseUri + { + get { return base_uri; } + set { base_uri = value; } + } + + public static string VersionNumber + { + get + { + return "4.2.2"; + } + } + } +} \ No newline at end of file diff --git a/createsend-netstandard/CreateSendOptionsWrapper.cs b/createsend-netstandard/CreateSendOptionsWrapper.cs new file mode 100644 index 0000000..7aad761 --- /dev/null +++ b/createsend-netstandard/CreateSendOptionsWrapper.cs @@ -0,0 +1,24 @@ +using System; + +namespace createsend_dotnet +{ + public class CreateSendOptionsWrapper : ICreateSendOptions + { + public string BaseUri + { + get { return CreateSendOptions.BaseUri; } + set { CreateSendOptions.BaseUri = value; } + } + + public string BaseOAuthUri + { + get { return CreateSendOptions.BaseOAuthUri; } + set { CreateSendOptions.BaseOAuthUri = value; } + } + + public string VersionNumber + { + get { return CreateSendOptions.VersionNumber; } + } + } +} diff --git a/createsend-netstandard/CustomCreateSendOptions.cs b/createsend-netstandard/CustomCreateSendOptions.cs new file mode 100644 index 0000000..e5af113 --- /dev/null +++ b/createsend-netstandard/CustomCreateSendOptions.cs @@ -0,0 +1,24 @@ +using System; + +namespace createsend_dotnet +{ + public class CustomCreateSendOptions : ICreateSendOptions + { + public string BaseUri + { + get; + set; + } + + public string BaseOAuthUri + { + get; + set; + } + + public string VersionNumber + { + get { return CreateSendOptions.VersionNumber; } + } + } +} \ No newline at end of file diff --git a/createsend-netstandard/General.cs b/createsend-netstandard/General.cs new file mode 100644 index 0000000..d47cf20 --- /dev/null +++ b/createsend-netstandard/General.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using Microsoft.AspNetCore.WebUtilities; + +namespace createsend_dotnet +{ + public class General : CreateSendBase + { + public General() : base(null) + { + } + + public General(AuthenticationDetails auth) : base(auth) + { + } + + public static string AuthorizeUrl( + int clientID, + string redirectUri, + string scope) + { + return AuthorizeUrl( + clientID, + redirectUri, + scope, + null); + } + + public static string AuthorizeUrl( + int clientID, + string redirectUri, + string scope, + string state) + { + var values = new Dictionary(); + values.Add("client_id", clientID.ToString()); + values.Add("redirect_uri", redirectUri); + if (!string.IsNullOrEmpty(state)) + values.Add("state", state); + values.Add("scope", scope); + + string result = CreateSendOptions.BaseOAuthUri; + result += QueryHelpers.AddQueryString(result, values); + + return result; + } + + public static OAuthTokenDetails ExchangeToken( + int clientID, + string clientSecret, + string redirectUri, + string code) + { + string body = "grant_type=authorization_code"; + + var values = new Dictionary(); + values.Add("client_id", clientID.ToString()); + values.Add("client_secret", clientSecret); + values.Add("redirect_uri", redirectUri); + values.Add("code", code); + + body = QueryHelpers.AddQueryString(body, values); + + return HttpHelper.Post( + null, "/token", new NameValueCollection(), body, + CreateSendOptions.BaseOAuthUri, + HttpHelper.APPLICATION_FORM_URLENCODED_CONTENT_TYPE); + } + + public BillingDetails BillingDetails() + { + return HttpGet("/billingdetails.json", null); + } + + public IEnumerable Clients() + { + return HttpGet("/clients.json", null); + } + + public IEnumerable Countries(string apiKey) + { + return HttpGet("/countries.json", null); + } + + public string ExternalSessionUrl( + ExternalSessionOptions options) + { + return HttpPut( + "/externalsession.json", null, options).SessionUrl; + } + + public DateTime SystemDate() + { + return HttpGet("/systemdate.json", null).SystemDate; + } + + public IEnumerable Timezones() + { + return HttpGet("/timezones.json", null); + } + } +} \ No newline at end of file diff --git a/createsend-netstandard/HttpHelper.cs b/createsend-netstandard/HttpHelper.cs new file mode 100644 index 0000000..5ebb014 --- /dev/null +++ b/createsend-netstandard/HttpHelper.cs @@ -0,0 +1,320 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Net.Http; +using System.Collections.Specialized; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using System.IO; +using createsend_dotnet.Transactional; +using System.Linq; + +#if SUPPORTED_FRAMEWORK_VERSION +using createsend_dotnet.Transactional; +#endif + +using Newtonsoft.Json; + +namespace createsend_dotnet +{ + public static class HttpHelper + { + public const string APPLICATION_FORM_URLENCODED_CONTENT_TYPE = "application/x-www-form-urlencoded"; + public const string APPLICATION_JSON_CONTENT_TYPE = "application/json"; + + public static string Delete(AuthenticationDetails auth, string path, NameValueCollection queryArguments) + { + return MakeRequest("DELETE", auth, path, queryArguments, null); + } + + // NEW + public static string Delete(AuthenticationDetails auth, string path, NameValueCollection queryArguments, string baseUri, string contentType) + { + return MakeRequestAsync("DELETE", auth, path, queryArguments, null, baseUri, contentType).Result; + } + + public static U Get( + AuthenticationDetails auth, + string path, + NameValueCollection queryArguments) + { + return Get(auth, path, queryArguments); + } + + public static U Get( + AuthenticationDetails auth, + string path, + NameValueCollection queryArguments) + where EX : ErrorResult + { + return MakeRequest("GET", auth, path, queryArguments, null); + } + + public static U Get( + AuthenticationDetails auth, + string baseUri, + string path, + NameValueCollection queryArguments) + where EX : ErrorResult + { + return MakeRequestAsync("GET", auth, path, queryArguments, null, baseUri, APPLICATION_JSON_CONTENT_TYPE).Result; + } + + public static U Post( + AuthenticationDetails auth, + string path, + NameValueCollection queryArguments, + T payload) + where T : class + { + return Post(auth, path, queryArguments, payload); + } + + public static U Post( + AuthenticationDetails auth, + string path, + NameValueCollection queryArguments, + T payload) + where T : class + where EX : ErrorResult + { + return MakeRequest("POST", auth, path, queryArguments, payload); + } + + public static U Post( + AuthenticationDetails auth, + string path, + NameValueCollection queryArguments, + T payload, + string baseUri, + string contentType) + where T : class + where EX : ErrorResult + { + return MakeRequestAsync("POST", auth, path, queryArguments, payload, baseUri, contentType).Result; + } + + public static U Put(AuthenticationDetails auth, string path, NameValueCollection queryArguments, T payload) where T : class + { + return MakeRequest("PUT", auth, path, queryArguments, payload); + } + + // NEW + public static U Put(AuthenticationDetails auth, string path, NameValueCollection queryArguments, T payload, string baseUri, string contentType) where T : class + { + return MakeRequestAsync("PUT", auth, path, queryArguments, payload, baseUri, contentType).Result; + } + + private static U MakeRequest( + string method, + AuthenticationDetails auth, + string path, + NameValueCollection queryArguments, + T payload) + where T : class + where EX : ErrorResult + { + return MakeRequestAsync(method, auth, path, queryArguments, + payload, CreateSendOptions.BaseUri, APPLICATION_JSON_CONTENT_TYPE).Result; + } + + private static async Task MakeRequestAsync( + string method, + AuthenticationDetails auth, + string path, + NameValueCollection queryArguments, + T payload, + string baseUri, + string contentType) + where T : class + where EX : ErrorResult + { + try + { + JsonSerializerSettings serialiserSettings = new JsonSerializerSettings(); + serialiserSettings.NullValueHandling = NullValueHandling.Ignore; + serialiserSettings.MissingMemberHandling = MissingMemberHandling.Ignore; + serialiserSettings.Converters.Add(new EmailAddressConverter()); + + string uri = baseUri + path + NameValueCollectionExtension.ToQueryString(queryArguments); + + HttpClientHandler handler = new HttpClientHandler(); + handler.AutomaticDecompression = System.Net.DecompressionMethods.GZip; + + HttpClient client = new HttpClient(handler); + + if (auth != null) + { + if (auth is OAuthAuthenticationDetails) + { + OAuthAuthenticationDetails oauthDetails = auth as OAuthAuthenticationDetails; + client.DefaultRequestHeaders.Add("Authorization", "Bearer " + oauthDetails.AccessToken); + } + else if (auth is ApiKeyAuthenticationDetails) + { + ApiKeyAuthenticationDetails apiKeyDetails = auth as ApiKeyAuthenticationDetails; + client.DefaultRequestHeaders.Add("Authorization", "Basic " + Convert.ToBase64String( + Encoding.GetEncoding(0).GetBytes(apiKeyDetails.ApiKey + ":x"))); + } + else if (auth is BasicAuthAuthenticationDetails) + { + BasicAuthAuthenticationDetails basicDetails = auth as BasicAuthAuthenticationDetails; + client.DefaultRequestHeaders.Add("Authorization", "Basic " + Convert.ToBase64String( + Encoding.GetEncoding(0).GetBytes(basicDetails.Username + ":" + basicDetails.Password))); + } + } + + HttpContent content = null; + HttpResponseMessage response = null; + + if (method != "GET") + { + Stream s = new MemoryStream(); + Stream requestStream = new MemoryStream(); + + if (payload != null) + { + using (System.IO.StreamWriter os = new System.IO.StreamWriter(s)) + { + if (contentType == APPLICATION_FORM_URLENCODED_CONTENT_TYPE) + os.Write(payload); + else + { + string json = JsonConvert.SerializeObject(payload, Formatting.None, serialiserSettings); + os.Write(json); + } + + await os.FlushAsync(); + s.Seek(0, SeekOrigin.Begin); + await s.CopyToAsync(requestStream); + os.Dispose(); + } + + requestStream.Seek(0, SeekOrigin.Begin); + content = new StreamContent(requestStream); + response = await client.PostAsync(uri, content); + } + else + { + response = await client.PostAsync(uri, null); + } + } + else + { + response = await client.GetAsync(uri); + } + + if (response.IsSuccessStatusCode) + { + var resp = await response.Content.ReadAsStreamAsync(); + + if (resp == null) + return default(U); + + { + using (var sr = new System.IO.StreamReader(resp)) + { + var type = typeof(U); + if (type.GetGenericTypeDefinition() == typeof(RateLimited<>)) + { + var responseType = type.GenericTypeArguments[0]; + var result = JsonConvert.DeserializeObject(sr.ReadToEnd().Trim(), responseType, serialiserSettings); + var status = new RateLimitStatus + { + Credit = response.Headers.Contains("X-RateLimit-Limit") ? uint.Parse(response.Headers.GetValues("X-RateLimit-Limit").First()) : 0, + Remaining = response.Headers.Contains("X-RateLimit-Remaining") ? uint.Parse(response.Headers.GetValues("X-RateLimit-Remaining").First()) : 0, + Reset = response.Headers.Contains("X-RateLimit-Reset") ? uint.Parse(response.Headers.GetValues("X-RateLimit-Reset").First()) : 0 + }; + return (U)Activator.CreateInstance(type, result, status); + } + return JsonConvert.DeserializeObject(sr.ReadToEnd().Trim(), serialiserSettings); + } + } + } + else + { + switch (response.StatusCode) + { + case System.Net.HttpStatusCode.BadRequest: + case System.Net.HttpStatusCode.Unauthorized: + throw ThrowReworkedCustomException(response); + case System.Net.HttpStatusCode.NotFound: + default: + throw new HttpRequestException(response.Content.ReadAsStringAsync().Result); + } + } + } + catch (Exception ex) + { + throw; + } + } + +#if SUPPORTED_FRAMEWORK_VERSION + private static uint UInt(this string value, uint defaultValue) + { + uint v; + if (uint.TryParse(value, out v)) + { + return v; + } + return defaultValue; + } +#endif + + private static Exception ThrowReworkedCustomException(HttpResponseMessage messageResponse) where EX : ErrorResult + { + using (System.IO.StreamReader sr = new System.IO.StreamReader(messageResponse.Content.ReadAsStreamAsync().Result)) + { + string response = sr.ReadToEnd().Trim(); + ErrorResult result = JsonConvert.DeserializeObject(response); + string message; + + if (result is OAuthErrorResult) + message = string.Format( + "The CreateSend OAuth receiver responded with the following error - {0}: {1}", + (result as OAuthErrorResult).error, + (result as OAuthErrorResult).error_description); + else // Regular ErrorResult format. + message = string.Format( + "The CreateSend API responded with the following error - {0}: {1}", + result.Code, result.Message); + + CreatesendException exception; + if (result.Code == "121") + exception = new ExpiredOAuthTokenException(message); + else + exception = new CreatesendException(message); + + exception.Data.Add("ErrorResponse", response); + exception.Data.Add("ErrorResult", result); + return exception; + } + } + } + + public static class NameValueCollectionExtension + { + public static string ToQueryString(NameValueCollection nvc) + { + string url = string.Empty; + if (nvc == null) + { + return url; + } + + Dictionary queryValues = new Dictionary(); + + foreach (string key in nvc) + { + if (!string.IsNullOrWhiteSpace(nvc[key])) + { + queryValues.Add(key, nvc[key]); + } + } + + return Microsoft.AspNetCore.WebUtilities.QueryHelpers.AddQueryString(url, queryValues); + } + } +} \ No newline at end of file diff --git a/createsend-netstandard/ICreateSendOptions.cs b/createsend-netstandard/ICreateSendOptions.cs new file mode 100644 index 0000000..88f30db --- /dev/null +++ b/createsend-netstandard/ICreateSendOptions.cs @@ -0,0 +1,11 @@ +using System; + +namespace createsend_dotnet +{ + public interface ICreateSendOptions + { + string BaseUri { get; set; } + string BaseOAuthUri { get; set; } + string VersionNumber { get; } + } +} diff --git a/createsend-netstandard/List.cs b/createsend-netstandard/List.cs new file mode 100644 index 0000000..ea3efdb --- /dev/null +++ b/createsend-netstandard/List.cs @@ -0,0 +1,283 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Globalization; + +namespace createsend_dotnet +{ + public class List : CreateSendBase + { + public List(AuthenticationDetails auth, string listID) + : base(auth) + { + ListID = listID; + } + + public string ListID { get; set; } + + public static string Create( + AuthenticationDetails auth, + string clientID, + string title, + string unsubscribePage, + bool confirmedOptIn, + string confirmationSuccessPage, + UnsubscribeSetting unsubscribeSetting) + { + return HttpHelper.Post( + auth, string.Format("/lists/{0}.json", clientID), null, + new ListDetail() + { + Title = title, + UnsubscribePage = unsubscribePage, + ConfirmedOptIn = confirmedOptIn, + ConfirmationSuccessPage = confirmationSuccessPage, + UnsubscribeSetting = unsubscribeSetting.ToString() + }); + } + + public void ActivateWebhook(string webhookID) + { + HttpPut( + string.Format("/lists/{0}/webhooks/{1}/activate.json", + ListID, System.Net.WebUtility.HtmlEncode(webhookID)), null, null); + } + + public PagedCollection Active() + { + return GenericPagedSubscriberGet("active", "", 1, 1000, "email", "asc"); + } + + public PagedCollection Active(DateTime fromDate, int page, int pageSize, string orderField, string orderDirection) + { + return GenericPagedSubscriberGet("active", fromDate, page, pageSize, orderField, orderDirection); + } + + public PagedCollection Bounced() + { + return GenericPagedSubscriberGet("bounced", "", 1, 1000, "email", "asc"); + } + + public PagedCollection Bounced(DateTime fromDate, int page, int pageSize, string orderField, string orderDirection) + { + return GenericPagedSubscriberGet("bounced", fromDate, page, pageSize, orderField, orderDirection); + } + + public string CreateCustomField( + string fieldName, + CustomFieldDataType dataType, + List options) + { + return CreateCustomField(fieldName, dataType, options, true); + } + + public string CreateCustomField( + string fieldName, + CustomFieldDataType dataType, + List options, + bool visibleInPreferenceCenter) + { + return HttpPost, string>( + string.Format("/lists/{0}/customfields.json", ListID), null, + new Dictionary() + { + { "FieldName", fieldName }, + { "DataType", dataType.ToString() }, + { "Options", options }, + { "VisibleInPreferenceCenter", visibleInPreferenceCenter } + }); + } + + public string CreateWebhook(List events, string url, string payloadFormat) + { + return HttpPost, string>( + string.Format("/lists/{0}/webhooks.json", ListID), null, + new Dictionary() + { + { "Events", events }, + { "Url", url }, + { "PayloadFormat", payloadFormat } + }); + } + + public IEnumerable CustomFields() + { + return HttpGet( + string.Format("/lists/{0}/customfields.json", ListID), null); + } + + public void DeactivateWebhook(string webhookID) + { + HttpPut( + string.Format("/lists/{0}/webhooks/{1}/deactivate.json", + ListID, System.Net.WebUtility.HtmlEncode(webhookID)), null, null); + } + + public void Delete() + { + HttpDelete(string.Format("/lists/{0}.json", ListID), null); + } + + public void DeleteCustomField(string customFieldKey) + { + HttpDelete( + string.Format("/lists/{0}/customfields/{1}.json", + ListID, System.Net.WebUtility.HtmlEncode(customFieldKey)), null); + } + + public PagedCollection Deleted() + { + return GenericPagedSubscriberGet("deleted", "", 1, 1000, "email", "asc"); + } + + public PagedCollection Deleted(DateTime fromDate, int page, int pageSize, string orderField, string orderDirection) + { + return GenericPagedSubscriberGet("deleted", fromDate, page, pageSize, orderField, orderDirection); + } + + public void DeleteWebhook(string webhookID) + { + HttpDelete( + string.Format("/lists/{0}/webhooks/{1}.json", + ListID, System.Net.WebUtility.HtmlEncode(webhookID)), null); + } + + public ListDetail Details() + { + return HttpGet( + string.Format("/lists/{0}.json", ListID), null); + } + + public IEnumerable Segments() + { + return HttpGet( + string.Format("/lists/{0}/segments.json", ListID), null); + } + + public ListStats Stats() + { + return HttpGet( + string.Format("/lists/{0}/stats.json", ListID), null); + } + + public bool TestWebhook(string webhookID) + { + HttpGet>( + string.Format("/lists/{0}/webhooks/{1}/test.json", + ListID, System.Net.WebUtility.HtmlEncode(webhookID)), null); + + return true; //an exception will be thrown if there is a problem + } + + public PagedCollection Unconfirmed() + { + return GenericPagedSubscriberGet("unconfirmed", "", 1, 1000, "email", "asc"); + } + + public PagedCollection Unconfirmed(DateTime fromDate, int page, int pageSize, string orderField, string orderDirection) + { + return GenericPagedSubscriberGet("unconfirmed", fromDate, page, pageSize, orderField, orderDirection); + } + + public PagedCollection Unsubscribed() + { + return GenericPagedSubscriberGet("unsubscribed", "", 1, 1000, "email", "asc"); + } + + public PagedCollection Unsubscribed(DateTime fromDate, int page, int pageSize, string orderField, string orderDirection) + { + return GenericPagedSubscriberGet("unsubscribed", fromDate, page, pageSize, orderField, orderDirection); + } + + public void Update( + string title, + string unsubscribePage, + bool confirmedOptIn, + string confirmationSuccessPage, + UnsubscribeSetting unsubscribeSetting, + bool addUnsubscribesToSuppList, + bool scrubActiveWithSuppList) + { + HttpPut( + string.Format("/lists/{0}.json", ListID), null, + new ListDetailForUpdate() + { + Title = title, + UnsubscribePage = unsubscribePage, + ConfirmedOptIn = confirmedOptIn, + ConfirmationSuccessPage = confirmationSuccessPage, + UnsubscribeSetting = unsubscribeSetting.ToString(), + AddUnsubscribesToSuppList = addUnsubscribesToSuppList, + ScrubActiveWithSuppList = scrubActiveWithSuppList + }); + } + + public string UpdateCustomField( + string customFieldKey, + string fieldName, + bool visibleInPreferenceCenter) + { + return HttpPut, string>( + string.Format("/lists/{0}/customfields/{1}.json", + ListID, System.Net.WebUtility.HtmlEncode(customFieldKey)), null, + new Dictionary() + { + { "FieldName", fieldName }, + { "VisibleInPreferenceCenter", visibleInPreferenceCenter } + }); + } + + public void UpdateCustomFieldOptions( + string customFieldKey, + List options, + bool keepExistingOptions) + { + HttpPut( + string.Format("/lists/{0}/customfields/{1}/options.json", + ListID, System.Net.WebUtility.HtmlEncode(customFieldKey)), null, + new + { + KeepExistingOptions = keepExistingOptions, + Options = options + }); + } + + [Obsolete("Use UpdateCustomFieldOptions instead. UpdateCustomFields will eventually be removed.", false)] + public void UpdateCustomFields(string customFieldKey, List options, bool keepExistingOptions) + { + UpdateCustomFieldOptions(customFieldKey, options, keepExistingOptions); + } + + public IEnumerable Webhooks() + { + return HttpGet( + string.Format("/lists/{0}/webhooks.json", ListID), null); + } + + private PagedCollection GenericPagedSubscriberGet( + string type, + DateTime fromDate, + int page, + int pageSize, + string orderField, + string orderDirection) + { + return GenericPagedSubscriberGet(type, + fromDate.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture), page, pageSize, + orderField, orderDirection); + } + + private PagedCollection GenericPagedSubscriberGet(string type, string fromDate, int page, int pageSize, string orderField, string orderDirection) + { + NameValueCollection queryArguments = new NameValueCollection(); + queryArguments.Add("date", fromDate); + queryArguments.Add("page", page.ToString()); + queryArguments.Add("pagesize", pageSize.ToString()); + queryArguments.Add("orderfield", orderField); + queryArguments.Add("orderdirection", orderDirection); + + return HttpGet>( + string.Format("/lists/{0}/{1}.json", ListID, type), queryArguments); + } + } +} \ No newline at end of file diff --git a/createsend-netstandard/Models/Administrator.cs b/createsend-netstandard/Models/Administrator.cs new file mode 100644 index 0000000..227cf42 --- /dev/null +++ b/createsend-netstandard/Models/Administrator.cs @@ -0,0 +1,17 @@ +namespace createsend_dotnet +{ + public class AdministratorDetails + { + public string EmailAddress { get; set; } + public string Name { get; set; } + /// + /// status is only used when retrieving an administrator (or administrators) + /// + public string Status { get; set; } + } + + public class AdministratorResult + { + public string EmailAddress { get; set; } + } +} diff --git a/createsend-netstandard/Models/BillingDetails.cs b/createsend-netstandard/Models/BillingDetails.cs new file mode 100644 index 0000000..7290ecf --- /dev/null +++ b/createsend-netstandard/Models/BillingDetails.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace createsend_dotnet +{ + public class BillingDetails + { + public int Credits { get; set; } + } +} diff --git a/createsend-netstandard/Models/Campaign.cs b/createsend-netstandard/Models/Campaign.cs new file mode 100644 index 0000000..9b8bc84 --- /dev/null +++ b/createsend-netstandard/Models/Campaign.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace createsend_dotnet +{ + public class CampaignDetail + { + public string CampaignID { get; set; } + public string Subject { get; set; } + public string Name { get; set; } + public string FromName { get; set; } + public string FromEmail { get; set; } + public string ReplyTo { get; set; } + public string SentDate { get; set; } + public int TotalRecipients { get; set; } + public string WebVersionURL { get; set; } + public string WebVersionTextURL { get; set; } + } + + public class ScheduledCampaignDetail + { + public string CampaignID { get; set; } + public string Subject { get; set; } + public string Name { get; set; } + public string FromName { get; set; } + public string FromEmail { get; set; } + public string ReplyTo { get; set; } + public string DateCreated { get; set; } + public string PreviewURL { get; set; } + public string PreviewTextURL { get; set; } + public string DateScheduled { get; set; } + public string ScheduledTimeZone { get; set; } + } + + public class DraftDetail + { + public string CampaignID { get; set; } + public string Subject { get; set; } + public string Name { get; set; } + public string FromName { get; set; } + public string FromEmail { get; set; } + public string ReplyTo { get; set; } + public string DateCreated { get; set; } + public string PreviewURL { get; set; } + public string PreviewTextURL { get; set; } + } + + public class CampaignSummary + { + public int Recipients { get; set; } + public int TotalOpened { get; set; } + public int Clicks { get; set; } + public int Unsubscribed { get; set; } + public int SpamComplaints { get; set; } + public int Bounced { get; set; } + public int UniqueOpened { get; set; } + public int Mentions { get; set; } + public int Forwards { get; set; } + public int Likes { get; set; } + public string WebVersionURL { get; set; } + public string WebVersionTextURL { get; set; } + public string WorldviewURL { get; set; } + } + + public class CampaignDetailBase + { + public string EmailAddress { get; set; } + public string ListID { get; set; } + public DateTime Date { get; set; } + } + + public class CampaignDetailBaseWithIPAddress : CampaignDetailBase + { + public string IPAddress { get; set; } + } + + public class CampaignDetailWithGeoBase : CampaignDetailBaseWithIPAddress + { + public double Latitude { get; set; } + public double Longitude { get; set; } + public string City { get; set; } + public string Region { get; set; } + public string CountryCode { get; set; } + public string CountryName { get; set; } + } + + public class CampaignOpenDetail : CampaignDetailWithGeoBase { } + + public class CampaignClickDetail : CampaignDetailWithGeoBase + { + public string URL { get; set; } + } + + public class CampaignUnsubscribeDetail : CampaignDetailBaseWithIPAddress { } + + public class CampaignSpamComplaint : CampaignDetailBase { } + + public class CampaignBounceDetail : CampaignDetailBaseWithIPAddress + { + public string BounceType { get; set; } + public string Reason { get; set; } + } + + public class CampaignListsAndSegments + { + public Segments Segments { get; set; } + public Lists Lists { get; set; } + } +} diff --git a/createsend-netstandard/Models/Client.cs b/createsend-netstandard/Models/Client.cs new file mode 100644 index 0000000..17bfbce --- /dev/null +++ b/createsend-netstandard/Models/Client.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Xml.Serialization; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace createsend_dotnet +{ + public class BasicClient + { + public string ClientID { get; set; } + public string Name { get; set; } + } + + public class Clients : List { } + + public class ClientWithSettings + { + public string ApiKey { get; set; } + public ClientDetail BasicDetails { get; set; } + public ClientAccessSettings AccessDetails { get; set; } + public BillingDetail BillingDetails { get; set; } + } + + public class ClientDetail + { + public string ClientID { get; set; } + public string CompanyName { get; set; } + public string ContactName { get; set; } + public string EmailAddress { get; set; } + public string Country { get; set; } + public string TimeZone { get; set; } + } + + public class BillingOptions + { + public string Currency { get; set; } + public bool ClientPays { get; set; } + public int MarkupPercentage { get; set; } + public bool CanPurchaseCredits { get; set; } + public int Credits { get; set; } + public decimal? MarkupOnDelivery { get; set; } + public decimal? MarkupPerRecipient { get; set; } + public decimal? MarkupOnDesignSpamTest { get; set; } + [JsonConverter(typeof(StringEnumConverter))] + public MonthlyScheme? MonthlyScheme { get; set; } + } + + public class BillingDetail : BillingOptions + { + public string CurrentTier { get; set; } + public decimal CurrentMonthlyRate { get; set; } + public decimal BaseDeliveryRate { get; set; } + public decimal BaseRatePerRecipient { get; set; } + public decimal BaseDesignSpamTestRate { get; set; } + } + + public class ClientAccessSettings + { + public int AccessLevel { get; set; } + public string Username { get; set; } + public string Password { get; set; } + } + + public class SuppressionDetails + { + public string[] EmailAddresses { get; set; } + } + + public class CreditsTransferDetails + { + public int Credits { get; set; } + public bool CanUseMyCreditsWhenTheyRunOut { get; set; } + } + + public class CreditsTransferResult + { + public int AccountCredits { get; set; } + public int ClientCredits { get; set; } + } +} diff --git a/createsend-netstandard/Models/CreatesendException.cs b/createsend-netstandard/Models/CreatesendException.cs new file mode 100644 index 0000000..bb6f701 --- /dev/null +++ b/createsend-netstandard/Models/CreatesendException.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace createsend_dotnet +{ + public class ExpiredOAuthTokenException : CreatesendException + { + public ExpiredOAuthTokenException(string message) : base(message) { } + } + + public class CreatesendException : Exception + { + public ErrorResult Error { get { return Data["ErrorResult"] as ErrorResult; } } + + public CreatesendException(string message) : base (message) { } + } +} diff --git a/createsend-netstandard/Models/EmailClient.cs b/createsend-netstandard/Models/EmailClient.cs new file mode 100644 index 0000000..add0b0e --- /dev/null +++ b/createsend-netstandard/Models/EmailClient.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace createsend_dotnet +{ + public class EmailClient + { + public string Client { get; set; } + public string Version { get; set; } + public double Percentage { get; set; } + public int Subscribers { get; set; } + } +} diff --git a/createsend-netstandard/Models/ExternalSession.cs b/createsend-netstandard/Models/ExternalSession.cs new file mode 100644 index 0000000..3a50f7b --- /dev/null +++ b/createsend-netstandard/Models/ExternalSession.cs @@ -0,0 +1,16 @@ +namespace createsend_dotnet +{ + public class ExternalSessionOptions + { + public string Email { get; set; } + public string Chrome { get; set; } + public string Url { get; set; } + public string IntegratorID { get; set; } + public string ClientID { get; set; } + } + + public class ExternalSessionResult + { + public string SessionUrl { get; set; } + } +} diff --git a/createsend-netstandard/Models/List.cs b/createsend-netstandard/Models/List.cs new file mode 100644 index 0000000..0d7caca --- /dev/null +++ b/createsend-netstandard/Models/List.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace createsend_dotnet +{ + public class BasicList + { + public string ListID { get; set; } + public string Name { get; set; } + } + + public class Lists : List { } + + public class ListDetail + { + public string ListID { get; set; } + public string Title { get; set; } + public string UnsubscribePage { get; set; } + public bool ConfirmedOptIn { get; set; } + public string ConfirmationSuccessPage { get; set; } + public string UnsubscribeSetting { get; set; } + } + + public class ListDetailForUpdate : ListDetail + { + public bool AddUnsubscribesToSuppList { get; set; } + public bool ScrubActiveWithSuppList { get; set; } + } + + public class ListCustomField + { + public string FieldName { get; set; } + public string Key { get; set; } + public string DataType { get; set; } + public List FieldOptions { get; set; } + public bool VisibleInPreferenceCenter { get; set; } + } + + public enum UnsubscribeSetting + { + AllClientLists, + OnlyThisList + } + + public enum CustomFieldDataType + { + Text = 1, + Number = 2, + MultiSelectOne = 3, + MultiSelectMany = 4, + Date = 5, + } + + public class ListStats + { + public int TotalActiveSubscribers { get; set; } + public int NewActiveSubscribersToday { get; set; } + public int NewActiveSubscribersYesterday { get; set; } + public int NewActiveSubscribersThisWeek { get; set; } + public int NewActiveSubscribersThisMonth { get; set; } + public int NewActiveSubscribersThisYear { get; set; } + public int TotalUnsubscribes { get; set; } + public int UnsubscribesToday { get; set; } + public int UnsubscribesYesterday { get; set; } + public int UnsubscribesThisWeek { get; set; } + public int UnsubscribesThisMonth { get; set; } + public int UnsubscribesThisYear { get; set; } + public int TotalDeleted { get; set; } + public int DeletedToday { get; set; } + public int DeletedYesterday { get; set; } + public int DeletedThisWeek { get; set; } + public int DeletedThisMonth { get; set; } + public int DeletedThisYear { get; set; } + public int TotalBounces { get; set; } + public int BouncesToday { get; set; } + public int BouncesYesterday { get; set; } + public int BouncesThisWeek { get; set; } + public int BouncesThisMonth { get; set; } + public int BouncesThisYear { get; set; } + } + + public class ListForEmail + { + public string ListID { get; set; } + public string ListName { get; set; } + public string SubscriberState { get; set; } + public DateTime DateSubscriberAdded { get; set; } + } +} diff --git a/createsend-netstandard/Models/MailAddress.cs b/createsend-netstandard/Models/MailAddress.cs new file mode 100644 index 0000000..e194539 --- /dev/null +++ b/createsend-netstandard/Models/MailAddress.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace createsend_netstandard.Models +{ + public class MailAddress + { + private string displayName; + + private string emailAddress; + + public MailAddress(string emailAddress, string displayName) + { + this.emailAddress = emailAddress; + this.displayName = displayName; + } + + public string Address { get { return this.emailAddress; } } + + public string DisplayName + { + get + { + if (string.IsNullOrWhiteSpace(this.displayName)) + { + return this.emailAddress; + } + return this.displayName; + } + } + } +} \ No newline at end of file diff --git a/createsend-netstandard/Models/OAuthTokenDetails.cs b/createsend-netstandard/Models/OAuthTokenDetails.cs new file mode 100644 index 0000000..a123d63 --- /dev/null +++ b/createsend-netstandard/Models/OAuthTokenDetails.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace createsend_dotnet +{ + public class OAuthTokenDetails + { + public string access_token; + public int expires_in; + public string refresh_token; + } +} diff --git a/createsend-netstandard/Models/PagedCollection.cs b/createsend-netstandard/Models/PagedCollection.cs new file mode 100644 index 0000000..1ebebe8 --- /dev/null +++ b/createsend-netstandard/Models/PagedCollection.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace createsend_dotnet +{ + public class PagedCollection + { + //deserializer does not allow deserialization from JSON list straight to IEnumerable + public List Results { get; set; } + public string ResultsOrderedBy { get; set; } + public string OrderDirection { get; set; } + public int PageNumber { get; set; } + public int PageSize { get; set; } + public int RecordsOnThisPage { get; set; } + public int TotalNumberOfRecords { get; set; } + public int NumberOfPages { get; set; } + } +} diff --git a/createsend-netstandard/Models/Person.cs b/createsend-netstandard/Models/Person.cs new file mode 100644 index 0000000..8bb158a --- /dev/null +++ b/createsend-netstandard/Models/Person.cs @@ -0,0 +1,22 @@ +namespace createsend_dotnet +{ + public class PersonDetails + { + public string EmailAddress { get; set; } + public string Name { get; set; } + public int AccessLevel { get; set; } + /// + /// status is only used when retrieving a person or people + /// + public string Status { get; set; } + /// + /// password is only used when adding a person + /// + public string Password { get; set; } + } + + public class PersonResult + { + public string EmailAddress { get; set; } + } +} diff --git a/createsend-netstandard/Models/Result.cs b/createsend-netstandard/Models/Result.cs new file mode 100644 index 0000000..f72e81d --- /dev/null +++ b/createsend-netstandard/Models/Result.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Xml.Serialization; +using System.Collections; + +namespace createsend_dotnet +{ + public class ApiKeyResult + { + public string ApiKey { get; set; } + } + + public class BulkImportResults + { + public List DuplicateEmailsInSubmission { get; set; } + public List FailureDetails { get; set; } + public int TotalExistingSubscribers { get; set; } + public int TotalNewSubscribers { get; set; } + public int TotalUniqueEmailsSubmitted { get; set; } + } + + public class ErrorResult : ErrorResult + { + public ErrorResult() : base() + { + } + + public ErrorResult(ErrorResult errorResult) + { + Message = errorResult.Message; + Code = errorResult.Code; + } + + public T ResultData { get; set; } + } + + public class ErrorResult + { + public string Code { get; set; } + public string Message { get; set; } + } + + public class ImportResult : ErrorResult + { + public string EmailAddress { get; set; } + } + + public class OAuthErrorResult : ErrorResult + { + public string error { get; set; } + public string error_description { get; set; } + } + + public class SystemDateResult + { + public DateTime SystemDate { get; set; } + } +} \ No newline at end of file diff --git a/createsend-netstandard/Models/Segment.cs b/createsend-netstandard/Models/Segment.cs new file mode 100644 index 0000000..4100ce2 --- /dev/null +++ b/createsend-netstandard/Models/Segment.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace createsend_dotnet +{ + public class BasicSegment + { + public string ListID { get; set; } + public string SegmentID { get; set; } + public string Title { get; set; } + } + + public class Segments : List { } + + public class SegmentDetail : BasicSegment + { + public int ActiveSubscribers { get; set; } + public SegmentRuleGroups RuleGroups { get; set; } + } + + public class SegmentRules : List { } + + public class Rule + { + public string RuleType { get; set; } + public string Clause { get; set; } + } + + public class SegmentRuleGroups : List { } + + public class SegmentRuleGroup + { + public SegmentRules Rules { get; set; } + } + + public class RuleErrorResults : List { } + + public class RuleErrorResult + { + public string Subject { get; set; } + public string Code { get; set; } + public string Message { get; set; } + public ClauseErrorResults ClauseResults { get; set; } + } + + public class ClauseErrorResults : List { } + + public class ClauseErrorResult + { + public string Clause { get; set; } + public string Code { get; set; } + public string Message { get; set; } + } +} \ No newline at end of file diff --git a/createsend-netstandard/Models/Subscriber.cs b/createsend-netstandard/Models/Subscriber.cs new file mode 100644 index 0000000..586abc2 --- /dev/null +++ b/createsend-netstandard/Models/Subscriber.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace createsend_dotnet +{ + public class BasicSubscriber + { + public string EmailAddress { get; set; } + public DateTime Date { get; set; } + public string State { get; set; } + } + + public class SuppressedSubscriber : BasicSubscriber + { + public string SuppressionReason { get; set; } + } + + public class CampaignRecipient + { + public string EmailAddress { get; set; } + public string ListID { get; set; } + } + + public class CampaignRecipients : List { } + + public class SubscriberCustomField + { + public string Key { get; set; } + public string Value{ get ;set; } + public bool Clear { get; set; } + } + + public class SubscriberDetail : BasicSubscriber + { + public SubscriberDetail() : base() { } + + public SubscriberDetail( + string emailAddress, + string name, + List customFields) + { + EmailAddress = emailAddress; + Name = name; + CustomFields = customFields; + } + + public string Name { get; set; } + public List CustomFields { get; set; } + public string ReadsEmailWith { get; set; } + } +} diff --git a/createsend-netstandard/Models/SubscriberHistory.cs b/createsend-netstandard/Models/SubscriberHistory.cs new file mode 100644 index 0000000..70afa59 --- /dev/null +++ b/createsend-netstandard/Models/SubscriberHistory.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace createsend_dotnet +{ + public class HistoryItem + { + public string ID { get; set; } + public string Type { get; set; } + public string Name { get; set; } + public IEnumerable Actions { get; set; } + } + + public class SubscriberAction + { + public string Event { get; set; } + public string IPAddress { get; set; } + public string Detail { get; set; } + public DateTime Date { get; set; } + } +} diff --git a/createsend-netstandard/Models/Template.cs b/createsend-netstandard/Models/Template.cs new file mode 100644 index 0000000..4921aea --- /dev/null +++ b/createsend-netstandard/Models/Template.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace createsend_dotnet +{ + public class BasicTemplate + { + public string TemplateID { get; set; } + public string Name { get; set; } + public string PreviewURL { get; set; } + public string ScreenshotURL { get; set; } + } +} \ No newline at end of file diff --git a/createsend-netstandard/Models/TemplateContent.cs b/createsend-netstandard/Models/TemplateContent.cs new file mode 100644 index 0000000..cdd468e --- /dev/null +++ b/createsend-netstandard/Models/TemplateContent.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace createsend_dotnet +{ + public class EditableField + { + public string Content; + public string Alt; + public string Href; + } + + public class Repeater + { + public List Items; + } + + public class RepeaterItem + { + public string Layout; + public List Singlelines; + public List Multilines; + public List Images; + } + + public class TemplateContent + { + public List Singlelines; + public List Multilines; + public List Images; + public List Repeaters; + } +} diff --git a/createsend-netstandard/Models/Webhook.cs b/createsend-netstandard/Models/Webhook.cs new file mode 100644 index 0000000..d837563 --- /dev/null +++ b/createsend-netstandard/Models/Webhook.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace createsend_dotnet +{ + public class BasicWebhook + { + public string WebhookID { get; set; } + public List Events { get; set; } + public string Url { get; set; } + public string Status { get; set; } + public string PayloadFormat { get; set; } + } + + public class WebhookTestErrorResult + { + public string FailureStatus { get; set; } + public string FailureResponseMessage { get; set; } + public string FailureResponseCode { get; set; } + public string FailureResponse { get; set; } + } +} diff --git a/createsend-netstandard/MonthlyScheme.cs b/createsend-netstandard/MonthlyScheme.cs new file mode 100644 index 0000000..7fbde30 --- /dev/null +++ b/createsend-netstandard/MonthlyScheme.cs @@ -0,0 +1,9 @@ +namespace createsend_dotnet +{ + public enum MonthlyScheme + { + Basic, + Unlimited, + Premier + } +} diff --git a/createsend-netstandard/Person.cs b/createsend-netstandard/Person.cs new file mode 100644 index 0000000..3f85b24 --- /dev/null +++ b/createsend-netstandard/Person.cs @@ -0,0 +1,42 @@ +using System.Collections.Specialized; + +namespace createsend_dotnet +{ + public class Person : CreateSendBase + { + private readonly string clientID; + + public Person(AuthenticationDetails auth, string clientID) + : base(auth) + { + this.clientID = clientID; + } + + private string PeopleUrl { get { return string.Format("/clients/{0}/people.json", clientID); }} + + public PersonDetails Details(string emailAddress) + { + return HttpGet( + PeopleUrl, new NameValueCollection {{"email", emailAddress}}); + } + + public string Add(PersonDetails person) + { + return HttpPost( + PeopleUrl, null, person).EmailAddress; + } + + public string Update(string emailAddress, PersonDetails person) + { + return HttpPut( + PeopleUrl, new NameValueCollection {{"email", emailAddress}}, + person).EmailAddress; + } + + public void Delete(string emailAddress) + { + HttpDelete(PeopleUrl, + new NameValueCollection {{"email", emailAddress}}); + } + } +} diff --git a/createsend-netstandard/Segment.cs b/createsend-netstandard/Segment.cs new file mode 100644 index 0000000..4452b44 --- /dev/null +++ b/createsend-netstandard/Segment.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Globalization; + +namespace createsend_dotnet +{ + public class Segment : CreateSendBase + { + public string SegmentID { get; set; } + + public Segment(AuthenticationDetails auth, string segmentID) + : base(auth) + { + SegmentID = segmentID; + } + + public static string Create( + AuthenticationDetails auth, string listID, string title, SegmentRuleGroups ruleGroups) + { + return HttpHelper.Post, string, ErrorResult>( + auth, string.Format("/segments/{0}.json", listID), null, + new Dictionary() + { + { "ListID", listID }, + { "Title", title }, + { "RuleGroups", ruleGroups } + }); + } + + public void Update(string title, SegmentRuleGroups ruleGroups) + { + HttpPut, string>( + string.Format("/segments/{0}.json", SegmentID), null, + new Dictionary() + { + { "Title", title }, + { "RuleGroups", ruleGroups } + }); + } + + public void AddRuleGroup(SegmentRuleGroup ruleGroup) + { + HttpPost, string>( + string.Format("/segments/{0}/rules.json", SegmentID), null, + new Dictionary() + { + { "Rules", ruleGroup.Rules } + }); + } + + public PagedCollection Subscribers() + { + return Subscribers(1, 1000, "email", "asc"); + } + + public PagedCollection Subscribers( + int page, + int pageSize, + string orderField, + string orderDirection) + { + return Subscribers("", page, pageSize, orderField, + orderDirection); + } + + public PagedCollection Subscribers( + DateTime fromDate, + int page, + int pageSize, + string orderField, + string orderDirection) + { + return Subscribers(fromDate.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture), + page, pageSize, orderField, orderDirection); + } + + private PagedCollection Subscribers( + string fromDate, + int page, + int pageSize, + string orderField, + string orderDirection) + { + NameValueCollection queryArguments = new NameValueCollection(); + queryArguments.Add("date", fromDate); + queryArguments.Add("page", page.ToString()); + queryArguments.Add("pagesize", pageSize.ToString()); + queryArguments.Add("orderfield", orderField); + queryArguments.Add("orderdirection", orderDirection); + + return HttpGet>( + string.Format("/segments/{0}/active.json", SegmentID), queryArguments); + } + + public SegmentDetail Details() + { + return HttpGet( + string.Format("/segments/{0}.json", SegmentID), null); + } + + public void ClearRules() + { + HttpDelete( + string.Format("/segments/{0}/rules.json", SegmentID), null); + } + + public void Delete() + { + HttpDelete(string.Format("/segments/{0}.json", SegmentID), null); + } + } +} diff --git a/createsend-netstandard/Subscriber.cs b/createsend-netstandard/Subscriber.cs new file mode 100644 index 0000000..d15134b --- /dev/null +++ b/createsend-netstandard/Subscriber.cs @@ -0,0 +1,148 @@ +using System.Collections.Generic; +using System.Collections.Specialized; + +namespace createsend_dotnet +{ + public class Subscriber : CreateSendBase + { + public string ListID { get; set; } + + public Subscriber(AuthenticationDetails auth, string listID) + : base(auth) + { + ListID = listID; + } + + public SubscriberDetail Get(string emailAddress) + { + NameValueCollection queryArguments = new NameValueCollection(); + queryArguments.Add("email", emailAddress); + + return HttpGet( + string.Format("/subscribers/{0}.json", ListID), queryArguments); + } + + public IEnumerable GetHistory(string emailAddress) + { + NameValueCollection queryArguments = new NameValueCollection(); + queryArguments.Add("email", emailAddress); + + return HttpGet>( + string.Format("/subscribers/{0}/history.json", ListID), + queryArguments); + } + + public string Add(string emailAddress, string name, + List customFields, bool resubscribe) + { + return Add(emailAddress, name, customFields, resubscribe, false); + } + + public string Add(string emailAddress, string name, + List customFields, bool resubscribe, + bool restartSubscriptionBasedAutoresponders) + { + return HttpPost, string>( + string.Format("/subscribers/{0}.json", ListID), null, + new Dictionary() + { + { "EmailAddress", emailAddress }, + { "Name", name }, + { "CustomFields", customFields }, + { "Resubscribe", resubscribe }, + { "RestartSubscriptionBasedAutoresponders", + restartSubscriptionBasedAutoresponders } + }); + } + + public void Update(string emailAddress, string newEmailAddress, + string name, List customFields, + bool resubscribe) + { + Update(emailAddress, newEmailAddress, name, customFields, + resubscribe, false); + } + + public void Update(string emailAddress, string newEmailAddress, + string name, List customFields, + bool resubscribe, bool restartSubscriptionBasedAutoresponders) + { + NameValueCollection queryArguments = new NameValueCollection(); + queryArguments.Add("email", emailAddress); + + HttpPut, string>( + string.Format("/subscribers/{0}.json", ListID), queryArguments, + new Dictionary() + { + { "EmailAddress", newEmailAddress }, + { "Name", name }, + { "CustomFields", customFields }, + { "Resubscribe", resubscribe }, + { "RestartSubscriptionBasedAutoresponders", + restartSubscriptionBasedAutoresponders } + }); + } + + public void Delete(string emailAddress) + { + NameValueCollection queryArguments = new NameValueCollection(); + queryArguments.Add("email", emailAddress); + HttpDelete(string.Format("/subscribers/{0}.json", ListID), queryArguments); + } + + public BulkImportResults Import(List subscribers, bool resubscribe) + { + return Import(subscribers, resubscribe, false); + } + + public BulkImportResults Import(List subscribers, + bool resubscribe, bool queueSubscriptionBasedAutoResponders) + { + return Import(subscribers, resubscribe, + queueSubscriptionBasedAutoResponders, false); + } + + public BulkImportResults Import(List subscribers, + bool resubscribe, bool queueSubscriptionBasedAutoResponders, + bool restartSubscriptionBasedAutoresponders) + { + List reworkedSubscribers = new List(); + foreach (SubscriberDetail subscriber in subscribers) + { + Dictionary subscriberWithoutDate = + new Dictionary() + { + { "EmailAddress", subscriber.EmailAddress }, + { "Name", subscriber.Name }, + { "CustomFields", subscriber.CustomFields } + }; + reworkedSubscribers.Add(subscriberWithoutDate); + } + + return HttpPost, BulkImportResults, + ErrorResult>( + string.Format("/subscribers/{0}/import.json", ListID), null, + new Dictionary() + { + { "Subscribers", reworkedSubscribers }, + { "Resubscribe", resubscribe }, + { "QueueSubscriptionBasedAutoResponders", + queueSubscriptionBasedAutoResponders }, + { "RestartSubscriptionBasedAutoresponders", + restartSubscriptionBasedAutoresponders } + }); + } + + public bool Unsubscribe(string emailAddress) + { + string result = HttpPost, string>( + string.Format("/subscribers/{0}/unsubscribe.json", ListID), null, + new Dictionary() + { + {"EmailAddress", emailAddress } + }); + + return result != null; + } + } +} diff --git a/createsend-netstandard/Template.cs b/createsend-netstandard/Template.cs new file mode 100644 index 0000000..8b99384 --- /dev/null +++ b/createsend-netstandard/Template.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; + +namespace createsend_dotnet +{ + public class Template : CreateSendBase + { + public string TemplateID { get; set; } + + public Template(AuthenticationDetails auth, string templateID) + : base(auth) + { + TemplateID = templateID; + } + + public static string Create(AuthenticationDetails auth, + string clientID, string name, string htmlPageUrl, string zipUrl) + { + return HttpHelper.Post, string>( + auth, string.Format("/templates/{0}.json", clientID), null, + new Dictionary() + { + { "Name", name }, + { "HtmlPageURL", htmlPageUrl }, + { "ZipFileUrl", zipUrl } + }); + } + + public void Update(string name, string htmlPageUrl, string zipUrl) + { + HttpPut, string>( + string.Format("/templates/{0}.json", TemplateID), null, + new Dictionary() + { + { "Name", name }, + { "HtmlPageURL", htmlPageUrl }, + { "ZipFileUrl", zipUrl } + }); + } + + public BasicTemplate Details() + { + return HttpGet( + string.Format("/templates/{0}.json", TemplateID), null); + } + + public void Delete() + { + HttpDelete( + string.Format("/templates/{0}.json", TemplateID), null); + } + } +} diff --git a/createsend-netstandard/Transactional/Attachment.cs b/createsend-netstandard/Transactional/Attachment.cs new file mode 100644 index 0000000..6def5f5 --- /dev/null +++ b/createsend-netstandard/Transactional/Attachment.cs @@ -0,0 +1,10 @@ + +namespace createsend_dotnet.Transactional +{ + public class Attachment + { + public string Name { get; set; } + public string Type { get; set; } + public byte[] Content { get; set; } + } +} diff --git a/createsend-netstandard/Transactional/AttachmentMetadata.cs b/createsend-netstandard/Transactional/AttachmentMetadata.cs new file mode 100644 index 0000000..ca5aec5 --- /dev/null +++ b/createsend-netstandard/Transactional/AttachmentMetadata.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace createsend_dotnet.Transactional +{ + public class AttachmentMetadata + { + public string Name { get; set; } + public string Type { get; set; } + } +} diff --git a/createsend-netstandard/Transactional/Authenticate.cs b/createsend-netstandard/Transactional/Authenticate.cs new file mode 100644 index 0000000..51e3c63 --- /dev/null +++ b/createsend-netstandard/Transactional/Authenticate.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Specialized; +using System.Globalization; + +namespace createsend_dotnet.Transactional +{ + public static class Authenticate + { + public static IAgencyTransactional ByAccountApiKey(string apiKey, ICreateSendOptions options = null) + { + if (apiKey == null) throw new ArgumentNullException("apiKey"); + + return new TransactionalContext(new AccountApiKey(apiKey), options); + } + + public static IAgencyTransactional ByAccountApiKey(string apiKey, string clientId, ICreateSendOptions options = null) + { + if (apiKey == null) throw new ArgumentNullException("apiKey"); + if (clientId == null) throw new ArgumentNullException("clientId"); + + return new TransactionalContext(new AccountApiKey(apiKey, clientId), options); + } + + public static ITransactional ByClientApiKey(string apiKey, ICreateSendOptions options = null) + { + if (apiKey == null) throw new ArgumentNullException("apiKey"); + + return new TransactionalContext(new ClientApiKey(apiKey), options); + } + + public static ITransactional ByOAuth(string accessToken, string refreshToken, string clientId, ICreateSendOptions options = null) + { + if (accessToken == null) throw new ArgumentNullException("accessToken"); + if (refreshToken == null) throw new ArgumentNullException("refreshToken"); + if (clientId == null) throw new ArgumentNullException("clientId"); + + return new TransactionalContext(new OAuthWithClientId(accessToken, refreshToken, clientId), options); + } + + internal static string PossiblyOptionalClientId(this CreateSendBase source, string clientId) + { + if (source == null) throw new ArgumentNullException("source"); + + var auth = source.AuthDetails as IProvideClientId; + + if (auth != null) + { + clientId = clientId ?? auth.ClientId; + + if (clientId == null) + { + throw new RequiredClientIdentierException(); + } + } + + return clientId; + } + + internal static NameValueCollection CreateQueryString(this CreateSendBase source, string clientId = null, NameValueCollection query = null) + { + if (source == null) throw new ArgumentNullException("source"); + + query = query ?? new NameValueCollection(); + clientId = source.PossiblyOptionalClientId(query.Get("clientid") ?? clientId); + if (clientId != null) + { + query["clientid"] = clientId; + } + + return query; + } + + internal static string Encode(this string value) + { + return value; + } + + internal static string Encode(this bool value) + { + return Encode(value.ToString().ToLowerInvariant()); + } + + internal static string Encode(this Enum value) + { + return value == null ? null : Encode(value.ToString().ToLowerInvariant()); + } + + internal static string Encode(this Guid value) + { + return Encode(value.ToString()); + } + + internal static string Encode(this Guid? value) + { + return value.HasValue ? Encode(value.Value) : null; + } + + internal static string Encode(this int value) + { + return Encode(value.ToString(CultureInfo.InvariantCulture)); + } + + internal static string Encode(this int? value) + { + return value.HasValue ? Encode(value.Value) : null; + } + + internal static string EncodeIso8601DateOnly(this DateTime date) + { + return Encode(date.ToString("yyyy-MM-dd")); + } + + internal static string EncodeIso8601DateOnly(this DateTime? date) + { + return date.HasValue ? EncodeIso8601DateOnly(date.Value) : null; + } + } +} diff --git a/createsend-netstandard/Transactional/ClassicEmail.cs b/createsend-netstandard/Transactional/ClassicEmail.cs new file mode 100644 index 0000000..4573e44 --- /dev/null +++ b/createsend-netstandard/Transactional/ClassicEmail.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Specialized; + +namespace createsend_dotnet.Transactional +{ + public interface IClassicEmail + { + RateLimited Send(EmailAddress from, string subject, string html, string text, EmailAddress replyTo = null, EmailAddress[] cc = null, EmailAddress[] bcc = null, Image[] images = null, Attachment[] attachments = null, bool trackOpens = true, bool trackClicks = true, bool inlineCss = true, string @group = null, string addRecipientsToListId = null, params EmailAddress[] to); + RateLimited Groups(); + } + + public interface IAgencyClassicEmail : IClassicEmail + { + RateLimited Send(string clientId, EmailAddress from, string subject, string html, string text, EmailAddress replyTo = null, EmailAddress[] cc = null, EmailAddress[] bcc = null, Image[] images = null, Attachment[] attachments = null, bool trackOpens = true, bool trackClicks = true, bool inlineCss = true, string @group = null, string addRecipientsToListId = null, params EmailAddress[] to); + RateLimited Groups(string clientId); + } + + internal class ClassicEmailContext : CreateSendBase, IClassicEmail, IAgencyClassicEmail + { + public ClassicEmailContext(AuthenticationDetails authenticationDetails, ICreateSendOptions options) + : base(authenticationDetails, options) + { + + } + + public RateLimited Send(EmailAddress from, string subject, string html, string text, EmailAddress replyTo = null, EmailAddress[] cc = null, EmailAddress[] bcc = null, Image[] images = null, Attachment[] attachments = null, bool trackOpens = true, bool trackClicks = true, bool inlineCss = true, string @group = null, string addRecipientsToListId = null, params EmailAddress[] to) + { + return Send(new ClassicEmail(from, replyTo, to, cc, bcc, subject, html, text, images, attachments, trackOpens, trackClicks, inlineCss, @group, addRecipientsToListId), this.CreateQueryString()); + } + + public RateLimited Send(string clientId, EmailAddress from, string subject, string html, string text, EmailAddress replyTo = null, EmailAddress[] cc = null, EmailAddress[] bcc = null, Image[] images = null, Attachment[] attachments = null, bool trackOpens = true, bool trackClicks = true, bool inlineCss = true, string @group = null, string addRecipientsToListId = null, params EmailAddress[] to) + { + if (clientId == null) throw new ArgumentNullException("clientId"); + + return Send(new ClassicEmail(from, replyTo, to, cc, bcc, subject, html, text, images, attachments, trackOpens, trackClicks, inlineCss, @group, addRecipientsToListId), this.CreateQueryString(clientId)); + } + + private RateLimited Send(ClassicEmail payload, NameValueCollection query) + { + return HttpPost>("/transactional/classicemail/send", query, payload); + } + + public RateLimited Groups() + { + return Groups(this.CreateQueryString()); + } + + public RateLimited Groups(string clientId) + { + if (clientId == null) throw new ArgumentNullException("clientId"); + + return Groups(this.CreateQueryString(clientId)); + } + + private RateLimited Groups(NameValueCollection query) + { + return HttpGet>("/transactional/classicemail/groups", query); + } + } + + internal class ClassicEmail + { + public EmailAddress From { get; set; } + public EmailAddress ReplyTo { get; set; } + public EmailAddress[] To { get; set; } + public EmailAddress[] CC { get; set; } + public EmailAddress[] BCC { get; set; } + public string Subject { get; set; } + public string Html { get; set; } + public string Text { get; set; } + public Image[] Images { get; set; } + public Attachment[] Attachments { get; set; } + public bool TrackOpens { get; set; } + public bool TrackClicks { get; set; } + public bool InlineCss { get; set; } + public string Group { get; set; } + public string AddRecipientsToListId { get; set; } + + public ClassicEmail(EmailAddress from, EmailAddress replyTo, EmailAddress[] to, EmailAddress[] cc, + EmailAddress[] bcc, string subject, string html, string text, Image[] images, Attachment[] attachments, + bool trackOpens, bool trackClicks, bool inlineCss, string @group, string addRecipientsToListId) + { + From = from; + ReplyTo = replyTo; + To = to; + CC = cc; + BCC = bcc; + Subject = subject; + Html = html; + Text = text; + Images = images; + Attachments = attachments; + TrackOpens = trackOpens; + TrackClicks = trackClicks; + InlineCss = inlineCss; + Group = @group; + AddRecipientsToListId = addRecipientsToListId; + } + } +} diff --git a/createsend-netstandard/Transactional/ClassicEmailDetail.cs b/createsend-netstandard/Transactional/ClassicEmailDetail.cs new file mode 100644 index 0000000..f295135 --- /dev/null +++ b/createsend-netstandard/Transactional/ClassicEmailDetail.cs @@ -0,0 +1,10 @@ +using System; + +namespace createsend_dotnet.Transactional +{ + public class ClassicEmailDetail + { + public string Group { get; set; } + public DateTimeOffset CreatedAt { get; set; } + } +} diff --git a/createsend-netstandard/Transactional/DisplayedTimeZone.cs b/createsend-netstandard/Transactional/DisplayedTimeZone.cs new file mode 100644 index 0000000..cae6fef --- /dev/null +++ b/createsend-netstandard/Transactional/DisplayedTimeZone.cs @@ -0,0 +1,9 @@ + +namespace createsend_dotnet.Transactional +{ + public enum DisplayedTimeZone + { + Utc, + Client, + } +} diff --git a/createsend-netstandard/Transactional/EmailAddress.cs b/createsend-netstandard/Transactional/EmailAddress.cs new file mode 100644 index 0000000..f4b49c1 --- /dev/null +++ b/createsend-netstandard/Transactional/EmailAddress.cs @@ -0,0 +1,46 @@ +using createsend_netstandard.Models; + +namespace createsend_dotnet.Transactional +{ + public class EmailAddress + { + private readonly MailAddress mail; + + public EmailAddress(string email, string name) + { + mail = new MailAddress(email, name); + } + + public EmailAddress(string email) : this(email, null) + { + } + + public string Email { get { return mail.Address; } } + public string Name { get { return mail.DisplayName; } } + + public static EmailAddress FromString(string email) + { + return email == null ? null : new EmailAddress(email); + } + + public static implicit operator EmailAddress(string email) + { + return FromString(email); + } + + public static implicit operator string(EmailAddress email) + { + return ToString(email); + } + + public static string ToString(EmailAddress email) + { + return email == null ? null : email.ToString(); + } + + public override string ToString() + { + return mail.ToString(); + } + } +} \ No newline at end of file diff --git a/createsend-netstandard/Transactional/EmailAddressConverter.cs b/createsend-netstandard/Transactional/EmailAddressConverter.cs new file mode 100644 index 0000000..23b9347 --- /dev/null +++ b/createsend-netstandard/Transactional/EmailAddressConverter.cs @@ -0,0 +1,35 @@ +using System; +using Newtonsoft.Json; + +namespace createsend_dotnet.Transactional +{ + internal class EmailAddressConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return objectType == typeof(EmailAddress); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.String) + { + return new EmailAddress((string)reader.Value); + } + return null; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value == null || value.GetType() != typeof(EmailAddress)) + { + writer.WriteNull(); + } + else + { + EmailAddress e = (EmailAddress)value; + writer.WriteValue(string.Format("{0} <{1}>", e.Name, e.Email)); + } + } + } +} \ No newline at end of file diff --git a/createsend-netstandard/Transactional/Geolocation.cs b/createsend-netstandard/Transactional/Geolocation.cs new file mode 100644 index 0000000..07a7087 --- /dev/null +++ b/createsend-netstandard/Transactional/Geolocation.cs @@ -0,0 +1,13 @@ + +namespace createsend_dotnet.Transactional +{ + public class Geolocation + { + public double Longitude { get; set; } + public double Latitude { get; set; } + public string City { get; set; } + public string Region { get; set; } + public string CountryCode { get; set; } + public string CountryName { get; set; } + } +} diff --git a/createsend-netstandard/Transactional/Image.cs b/createsend-netstandard/Transactional/Image.cs new file mode 100644 index 0000000..7f018e7 --- /dev/null +++ b/createsend-netstandard/Transactional/Image.cs @@ -0,0 +1,10 @@ + +namespace createsend_dotnet.Transactional +{ + public class Image + { + public string Name { get; set; } + public string Type { get; set; } + public byte[] Content { get; set; } + } +} diff --git a/createsend-netstandard/Transactional/MailClient.cs b/createsend-netstandard/Transactional/MailClient.cs new file mode 100644 index 0000000..2499546 --- /dev/null +++ b/createsend-netstandard/Transactional/MailClient.cs @@ -0,0 +1,9 @@ + +namespace createsend_dotnet.Transactional +{ + public class MailClient + { + public string Name { get; set; } + public string Version { get; set; } + } +} diff --git a/createsend-netstandard/Transactional/MessageBody.cs b/createsend-netstandard/Transactional/MessageBody.cs new file mode 100644 index 0000000..e26e943 --- /dev/null +++ b/createsend-netstandard/Transactional/MessageBody.cs @@ -0,0 +1,9 @@ + +namespace createsend_dotnet.Transactional +{ + public class MessageBody + { + public string Html { get; set; } + public string Text { get; set; } + } +} diff --git a/createsend-netstandard/Transactional/MessageBuilder.cs b/createsend-netstandard/Transactional/MessageBuilder.cs new file mode 100644 index 0000000..227c96b --- /dev/null +++ b/createsend-netstandard/Transactional/MessageBuilder.cs @@ -0,0 +1,274 @@ +using System; +using System.Collections.Generic; + +namespace createsend_dotnet.Transactional +{ + public interface IMessageBuilder + { + IMessageBuilder From(EmailAddress from); + IMessageBuilder ReplyTo(EmailAddress replyTo); + IMessageBuilder Subject(string subject); + IMessageBuilder To(EmailAddress to); + IMessageBuilder To(EmailAddress[] to); + IMessageBuilder CC(EmailAddress cc); + IMessageBuilder CC(EmailAddress[] cc); + IMessageBuilder BCC(EmailAddress bcc); + IMessageBuilder BCC(EmailAddress[] bcc); + IMessageBuilder Text(string text); + IMessageBuilder Html(string html); + IMessageBuilder Attachment(Attachment attachment); + IMessageBuilder Attachment(Attachment[] attachments); + IMessageBuilder Image(Image image); + IMessageBuilder Image(Image[] images); + IMessageBuilder Data(IDictionary data); + IMessageBuilder TrackOpens(bool trackOpens = true); + IMessageBuilder TrackClicks(bool trackClicks = true); + IMessageBuilder InlineCss(bool inlineCss = true); + IMessageBuilder Group(string @group); + IMessageBuilder AddRecipientsToList(bool addRecipientsToList); + IMessageBuilder AddRecipientsToListId(string addRecipientsToListId); + + RateLimited Send(); + RateLimited Send(Guid smartEmailId); + } + + public interface IAgencyMessageBuilder : IMessageBuilder + { + RateLimited Send(string clientId); + } + + internal class MessageBuilder : IMessageBuilder, IAgencyMessageBuilder + { + private readonly SmartEmailContext smart; + private readonly ClassicEmailContext classic; + private readonly List to; + private readonly List cc; + private readonly List bcc; + private readonly List attachments; + private readonly List images; + private IDictionary data; + private string subject; + private EmailAddress from; + private EmailAddress replyTo; + private string html; + private string text; + private bool trackOpens = true; + private bool trackClicks = true; + private bool inlineCss = true; + private string @group; + private bool addRecipientsToList = true; + private string listId; + + public MessageBuilder(SmartEmailContext smart, ClassicEmailContext classic) + { + this.smart = smart; + this.classic = classic; + + to = new List(); + cc = new List(); + bcc = new List(); + attachments = new List(); + images = new List(); + } + + public IMessageBuilder From(EmailAddress from) + { + if(from == null) throw new ArgumentNullException("from"); + + this.from = from; + return this; + } + + public IMessageBuilder ReplyTo(EmailAddress replyTo) + { + if(replyTo == null) throw new ArgumentNullException("replyTo"); + + this.replyTo = replyTo; + return this; + } + + public IMessageBuilder Subject(string subject) + { + if(subject == null) throw new ArgumentNullException("subject"); + + this.subject = subject; + return this; + } + + public IMessageBuilder To(EmailAddress to) + { + if(to == null) throw new ArgumentNullException("to"); + + this.to.Add(to); + return this; + } + + public IMessageBuilder To(EmailAddress[] to) + { + if(to == null) throw new ArgumentNullException("to"); + if(to.Length == 0) throw new ArgumentException("Cannot be empty", "to"); + + this.to.AddRange(to); + return this; + } + + public IMessageBuilder CC(EmailAddress cc) + { + if(cc == null) throw new ArgumentNullException("cc"); + + this.cc.Add(cc); + return this; + } + + public IMessageBuilder CC(EmailAddress[] cc) + { + if(cc == null) throw new ArgumentNullException("cc"); + if (cc.Length == 0) throw new ArgumentException("Cannot be empty", "cc"); + + this.cc.AddRange(cc); + return this; + } + + public IMessageBuilder BCC(EmailAddress bcc) + { + if(bcc == null) throw new ArgumentNullException("bcc"); + + this.bcc.Add(bcc); + return this; + } + + public IMessageBuilder BCC(EmailAddress[] bcc) + { + if (bcc == null) throw new ArgumentNullException("bcc"); + if (bcc.Length == 0) throw new ArgumentException("Cannot be empty", "bcc"); + this.bcc.AddRange(bcc); + return this; + } + + public IMessageBuilder Text(string text) + { + if(text == null) throw new ArgumentNullException("text"); + + this.text = text; + return this; + } + + public IMessageBuilder Html(string html) + { + if(html == null) throw new ArgumentNullException("html"); + + this.html = html; + return this; + } + + public IMessageBuilder Attachment(Attachment attachment) + { + if(attachment == null) throw new ArgumentNullException("attachment"); + + this.attachments.Add(attachment); + return this; + } + + public IMessageBuilder Attachment(Attachment[] attachments) + { + if(attachments == null) throw new ArgumentNullException("attachments"); + if (attachments.Length == 0) throw new ArgumentException("Cannot be empty", "attachments"); + + this.attachments.AddRange(attachments); + return this; + } + + public IMessageBuilder Image(Image image) + { + if(image == null) throw new ArgumentNullException("image"); + + this.images.Add(image); + return this; + } + + public IMessageBuilder Image(Image[] images) + { + if(images == null) throw new ArgumentNullException("images"); + if(images.Length == 0) throw new ArgumentException("Cannot be empty", "images"); + + this.images.AddRange(images); + return this; + } + + public IMessageBuilder Data(IDictionary data) + { + if(data == null) throw new ArgumentNullException("data"); + + this.data = data; + return this; + } + + public IMessageBuilder TrackOpens(bool trackOpens = true) + { + this.trackOpens = trackOpens; + + return this; + } + + public IMessageBuilder TrackClicks(bool trackClicks = true) + { + this.trackClicks = trackClicks; + + return this; + } + + public IMessageBuilder InlineCss(bool inlineCss = true) + { + this.inlineCss = inlineCss; + + return this; + } + + public IMessageBuilder Group(string @group) + { + if(@group == null) throw new ArgumentNullException("group"); + + this.@group = @group; + return this; + } + + public IMessageBuilder AddRecipientsToList(bool addRecipientsToList) + { + this.addRecipientsToList = addRecipientsToList; + + return this; + } + + public IMessageBuilder AddRecipientsToListId(string listId) + { + this.listId = listId; + + return this; + } + + public RateLimited Send() + { + return classic.Send(from, subject, html, text, replyTo, cc.ToArray(), bcc.ToArray(), images.ToArray(), + attachments.ToArray(), trackOpens, trackClicks, inlineCss, @group, listId, to.ToArray()); + } + + public RateLimited Send(string clientId) + { + return classic.Send(clientId, from, subject, html, text, replyTo, cc.ToArray(), bcc.ToArray(), images.ToArray(), + attachments.ToArray(), trackOpens, trackClicks, inlineCss, @group, listId, to.ToArray()); + } + + + public RateLimited Send(Guid smartEmailId) + { + return smart.Send( + smartEmailId, + cc.ToArray(), + bcc.ToArray(), + attachments.ToArray(), + data, + addRecipientsToList, + to.ToArray()); + } + } +} diff --git a/createsend-netstandard/Transactional/MessageContent.cs b/createsend-netstandard/Transactional/MessageContent.cs new file mode 100644 index 0000000..c970487 --- /dev/null +++ b/createsend-netstandard/Transactional/MessageContent.cs @@ -0,0 +1,18 @@ + +using System.Collections.Generic; + +namespace createsend_dotnet.Transactional +{ + public class MessageContent + { + public EmailAddress From { get; set; } + public EmailAddress ReplyTo { get; set; } + public string Subject { get; set; } + public EmailAddress[] To { get; set; } + public EmailAddress[] CC { get; set; } + public EmailAddress BCC { get; set; } + public MessageBody Body { get; set; } + public Dictionary Data { get; set; } + public AttachmentMetadata[] Attachments { get; set; } + } +} diff --git a/createsend-netstandard/Transactional/MessageDetail.cs b/createsend-netstandard/Transactional/MessageDetail.cs new file mode 100644 index 0000000..39ff511 --- /dev/null +++ b/createsend-netstandard/Transactional/MessageDetail.cs @@ -0,0 +1,24 @@ +using System; + +namespace createsend_dotnet.Transactional +{ + public class MessageDetail + { + public Guid MessageId { get; set; } + public string Status { get; set; } + public DateTimeOffset SentAt { get; set; } + + public bool CanBeResent { get; set; } + public EmailAddress Recipient { get; set; } + + public MessageContent Message { get; set; } + public string Group { get; set; } + public Guid? SmartEmailId { get; set; } + + public long TotalOpens { get; set; } + public long TotalClicks { get; set; } + + public SubscriberAction[] Opens { get; set; } + public SubscriberAction[] Clicks { get; set; } + } +} diff --git a/createsend-netstandard/Transactional/MessageListDetail.cs b/createsend-netstandard/Transactional/MessageListDetail.cs new file mode 100644 index 0000000..bff5a68 --- /dev/null +++ b/createsend-netstandard/Transactional/MessageListDetail.cs @@ -0,0 +1,18 @@ +using System; + +namespace createsend_dotnet.Transactional +{ + public class MessageListDetail + { + public Guid MessageId { get; set; } + public EmailAddress Recipient { get; set; } + public EmailAddress From { get; set; } + public string Subject { get; set; } + public string Status { get; set; } + public string Group { get; set; } + public Guid? SmartEmailId { get; set; } + public long TotalOpens { get; set; } + public long TotalClicks { get; set; } + public bool CanBeResent { get; set; } + } +} diff --git a/createsend-netstandard/Transactional/MessageListStatus.cs b/createsend-netstandard/Transactional/MessageListStatus.cs new file mode 100644 index 0000000..a102fb5 --- /dev/null +++ b/createsend-netstandard/Transactional/MessageListStatus.cs @@ -0,0 +1,11 @@ + +namespace createsend_dotnet.Transactional +{ + public enum MessageListStatus + { + All, + Delivered, + Bounced, + Spam, + } +} diff --git a/createsend-netstandard/Transactional/Messages.cs b/createsend-netstandard/Transactional/Messages.cs new file mode 100644 index 0000000..9d2e2fd --- /dev/null +++ b/createsend-netstandard/Transactional/Messages.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Specialized; + +namespace createsend_dotnet.Transactional +{ + public interface IMessages + { + RateLimited Resend(Guid messageId); + RateLimited Details(Guid messageId, bool statistics = false); + RateLimited List(Guid? sentAfterId = null, Guid? sentBeforeId = null, int count = 50, string @group = null, MessageListStatus status = MessageListStatus.All); + } + + public interface IAgencyMessages : IMessages + { + RateLimited List(string clientId, Guid? sentAfterId = null, Guid? sentBeforeId = null, int count = 50, string @group = null, MessageListStatus status = MessageListStatus.All); + } + + internal class MessagesContext : CreateSendBase, IMessages, IAgencyMessages + { + public MessagesContext(AuthenticationDetails authenticationDetails, ICreateSendOptions options) + : base(authenticationDetails, options) + { + + } + + public RateLimited Resend(Guid messageId) + { + if (messageId == Guid.Empty) throw new ArgumentException("Must not be empty", "messageId"); + + return Resend(messageId, new NameValueCollection()); + } + + private RateLimited Resend(Guid messageId, NameValueCollection query) + { + return HttpPost>(String.Format("/transactional/messages/{0}/resend", messageId), query, null); + } + + public RateLimited Details(Guid messageId, bool statistics = false) + { + return Details(messageId, CreateQueryString(statistics)); + } + + private RateLimited Details(Guid messageId, NameValueCollection query) + { + return HttpGet>(String.Format("/transactional/messages/{0}", messageId), query); + } + + public RateLimited List(Guid? sentAfterId = null, Guid? sentBeforeId = null, int count = 50, string @group = null, MessageListStatus status = MessageListStatus.All) + { + return ListMessages(CreateQueryString(@group, sentAfterId, sentBeforeId, count, status)); + } + + public RateLimited List(string clientId, Guid? sentAfterId = null, Guid? sentBeforeId = null, int count = 50, string @group = null, MessageListStatus status = MessageListStatus.All) + { + if (clientId == null) throw new ArgumentNullException("clientId"); + + return ListMessages(CreateQueryString(@group, sentAfterId, sentBeforeId, count, status, clientId)); + } + + private RateLimited ListMessages(NameValueCollection query) + { + return HttpGet>("/transactional/messages", query); + } + + private NameValueCollection CreateQueryString(bool statistics) + { + return new NameValueCollection + { + { "statistics", statistics.Encode() }, + }; + } + + private NameValueCollection CreateQueryString(string @group, Guid? sentAfterId, Guid? sentBeforeId, int count, MessageListStatus status, string clientId = null) + { + return this.CreateQueryString( + clientId, + query: new NameValueCollection + { + { "group", @group.Encode() }, + { "sentAfterId", sentAfterId.Encode() }, + { "sentBeforeId", sentBeforeId.Encode() }, + { "count", count.Encode() }, + { "status", status.Encode() }, + }); + } + } +} diff --git a/createsend-netstandard/Transactional/PropertyContent.cs b/createsend-netstandard/Transactional/PropertyContent.cs new file mode 100644 index 0000000..8b90d7a --- /dev/null +++ b/createsend-netstandard/Transactional/PropertyContent.cs @@ -0,0 +1,11 @@ + +namespace createsend_dotnet.Transactional +{ + public class PropertyContent + { + public string Html { get; set; } + public string Text { get; set; } + public string[] EmailVariables { get; set; } + public bool InlineCss { get; set; } + } +} diff --git a/createsend-netstandard/Transactional/RateLimitStatus.cs b/createsend-netstandard/Transactional/RateLimitStatus.cs new file mode 100644 index 0000000..9e9109a --- /dev/null +++ b/createsend-netstandard/Transactional/RateLimitStatus.cs @@ -0,0 +1,10 @@ + +namespace createsend_dotnet.Transactional +{ + public class RateLimitStatus + { + public uint Remaining { get; set; } + public uint Credit { get; set; } + public uint Reset { get; set; } + } +} diff --git a/createsend-netstandard/Transactional/RateLimited`1.cs b/createsend-netstandard/Transactional/RateLimited`1.cs new file mode 100644 index 0000000..61d6403 --- /dev/null +++ b/createsend-netstandard/Transactional/RateLimited`1.cs @@ -0,0 +1,15 @@ + +namespace createsend_dotnet.Transactional +{ + public sealed class RateLimited + { + public T Response { get; private set; } + public RateLimitStatus RateLimit { get; private set; } + + public RateLimited(T response, RateLimitStatus rateLimit) + { + Response = response; + RateLimit = rateLimit; + } + } +} diff --git a/createsend-netstandard/Transactional/RecipientStatus.cs b/createsend-netstandard/Transactional/RecipientStatus.cs new file mode 100644 index 0000000..5a074a2 --- /dev/null +++ b/createsend-netstandard/Transactional/RecipientStatus.cs @@ -0,0 +1,11 @@ +using System; + +namespace createsend_dotnet.Transactional +{ + public class RecipientStatus + { + public Guid MessageId { get; set; } + public EmailAddress Recipient { get; set; } + public string Status { get; set; } + } +} diff --git a/createsend-netstandard/Transactional/RequiredClientIdentifierException.cs b/createsend-netstandard/Transactional/RequiredClientIdentifierException.cs new file mode 100644 index 0000000..a2c61e6 --- /dev/null +++ b/createsend-netstandard/Transactional/RequiredClientIdentifierException.cs @@ -0,0 +1,31 @@ +using System; +using System.Runtime.Serialization; + +namespace createsend_dotnet.Transactional +{ + public class RequiredClientIdentierException : Exception + { + private const string defaultMessage = + "ClientId has not been provided. Note to agencies: If you are using an account API key, this is required as you need to specify the client"; + + public RequiredClientIdentierException() + : base(defaultMessage) + { + } + + public RequiredClientIdentierException(string message) + : base(message) + { + } + + public RequiredClientIdentierException(string message, Exception exception) + : base(message, exception) + { + } + + public RequiredClientIdentierException(Exception exception) + : base(defaultMessage, exception) + { + } + } +} \ No newline at end of file diff --git a/createsend-netstandard/Transactional/SmartEmail.cs b/createsend-netstandard/Transactional/SmartEmail.cs new file mode 100644 index 0000000..5fc886b --- /dev/null +++ b/createsend-netstandard/Transactional/SmartEmail.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; + +namespace createsend_dotnet.Transactional +{ + public interface ISmartEmail + { + RateLimited Send(Guid smartEmailId, EmailAddress[] cc = null, EmailAddress[] bcc = null, Attachment[] attachments = null, IDictionary data = null, bool addRecipientsToList = true, params EmailAddress[] to); + RateLimited Details(Guid smartEmailId); + RateLimited List(SmartEmailListStatus status = SmartEmailListStatus.All); + } + + public interface IAgencySmartEmail : ISmartEmail + { + RateLimited List(string clientId, SmartEmailListStatus status = SmartEmailListStatus.All); + } + + internal class SmartEmailContext : CreateSendBase, ISmartEmail, IAgencySmartEmail + { + public SmartEmailContext(AuthenticationDetails authenticationDetails, ICreateSendOptions options) + : base(authenticationDetails, options) + { + + } + + public RateLimited Send(Guid smartEmailId, EmailAddress[] cc = null, EmailAddress[] bcc = null, Attachment[] attachments = null, IDictionary data = null, bool addRecipientsToList = true, params EmailAddress[] to) + { + return Send(smartEmailId, new SmartEmail(to, cc, bcc, attachments, data, addRecipientsToList), new NameValueCollection()); + } + + private RateLimited Send(Guid smartEmailId, SmartEmail payload, NameValueCollection query) + { + return HttpPost>(String.Format("/transactional/smartemail/{0}/send", smartEmailId), query, payload); + } + + public RateLimited List(SmartEmailListStatus status = SmartEmailListStatus.All) + { + return List(CreateQueryString(status)); + } + + public RateLimited List(string clientId, SmartEmailListStatus status = SmartEmailListStatus.All) + { + if (clientId == null) throw new ArgumentNullException("clientId"); + + return List(CreateQueryString(status, clientId)); + } + + private RateLimited List(NameValueCollection query) + { + return HttpGet>("/transactional/smartemail", query); + } + + public RateLimited Details(Guid smartEmailId) + { + return Details(smartEmailId, new NameValueCollection()); + } + + private RateLimited Details(Guid smartEmailId, NameValueCollection query) + { + return HttpGet>(String.Format("/transactional/smartemail/{0}", smartEmailId), query); + } + + private NameValueCollection CreateQueryString(SmartEmailListStatus status, string clientId = null) + { + return this.CreateQueryString( + clientId, + query: new NameValueCollection + { + { "status", status.Encode() } + }); + } + } + + internal class SmartEmail + { + public EmailAddress[] To { get; private set; } + public EmailAddress[] CC { get; private set; } + public EmailAddress[] BCC { get; private set; } + public Attachment[] Attachments { get; private set; } + public IDictionary Data { get; private set; } + public bool AddRecipientsToList { get; private set; } + + public SmartEmail(EmailAddress[] to, EmailAddress[] cc, EmailAddress[] bcc, Attachment[] attachments, + IDictionary data, bool addRecipientsToList) + { + To = to; + CC = cc; + BCC = bcc; + Attachments = attachments; + Data = data; + AddRecipientsToList = addRecipientsToList; + } + } +} diff --git a/createsend-netstandard/Transactional/SmartEmailDetail.cs b/createsend-netstandard/Transactional/SmartEmailDetail.cs new file mode 100644 index 0000000..e98afee --- /dev/null +++ b/createsend-netstandard/Transactional/SmartEmailDetail.cs @@ -0,0 +1,13 @@ +using System; + +namespace createsend_dotnet.Transactional +{ + public class SmartEmailDetail + { + public Guid SmartEmailId { get; set; } + public string Name { get; set; } + public DateTimeOffset CreatedAt { get; set; } + public string Status { get; set; } + public SmartEmailProperties Properties { get; set; } + } +} diff --git a/createsend-netstandard/Transactional/SmartEmailListDetail.cs b/createsend-netstandard/Transactional/SmartEmailListDetail.cs new file mode 100644 index 0000000..1fa05e0 --- /dev/null +++ b/createsend-netstandard/Transactional/SmartEmailListDetail.cs @@ -0,0 +1,12 @@ +using System; + +namespace createsend_dotnet.Transactional +{ + public class SmartEmailListDetail + { + public Guid Id { get; set; } + public string Name { get; set; } + public DateTimeOffset CreatedAt { get; set; } + public string Status { get; set; } + } +} diff --git a/createsend-netstandard/Transactional/SmartEmailListStatus.cs b/createsend-netstandard/Transactional/SmartEmailListStatus.cs new file mode 100644 index 0000000..effd736 --- /dev/null +++ b/createsend-netstandard/Transactional/SmartEmailListStatus.cs @@ -0,0 +1,10 @@ + +namespace createsend_dotnet.Transactional +{ + public enum SmartEmailListStatus + { + All, + Active, + Draft, + } +} diff --git a/createsend-netstandard/Transactional/SmartEmailProperties.cs b/createsend-netstandard/Transactional/SmartEmailProperties.cs new file mode 100644 index 0000000..3ea048e --- /dev/null +++ b/createsend-netstandard/Transactional/SmartEmailProperties.cs @@ -0,0 +1,13 @@ + +namespace createsend_dotnet.Transactional +{ + public class SmartEmailProperties + { + public EmailAddress From { get; set; } + public EmailAddress ReplyTo { get; set; } + public string Subject { get; set; } + public PropertyContent Content { get; set; } + public string TextPreviewUrl { get; set; } + public string HtmlPreviewUrl { get; set; } + } +} diff --git a/createsend-netstandard/Transactional/Statistics.cs b/createsend-netstandard/Transactional/Statistics.cs new file mode 100644 index 0000000..5e0bd8f --- /dev/null +++ b/createsend-netstandard/Transactional/Statistics.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Specialized; + +namespace createsend_dotnet.Transactional +{ + public interface IStatistics + { + RateLimited Statistics(Guid? smartEmailId = null, string @group = null, DateTime? from = null, DateTime? to = null, DisplayedTimeZone timezone = DisplayedTimeZone.Client); + } + + public interface IAgencyStatistics : IStatistics + { + RateLimited Statistics(string clientId, Guid? smartEmailId = null, string @group = null, DateTime? from = null, DateTime? to = null, DisplayedTimeZone timezone = DisplayedTimeZone.Client); + } + + internal class StatisticsContext : CreateSendBase, IStatistics, IAgencyStatistics + { + public StatisticsContext(AuthenticationDetails authenticationDetails, ICreateSendOptions options) + : base(authenticationDetails, options) + { + + } + + public RateLimited Statistics(Guid? smartEmailId = null, string @group = null, DateTime? from = null, DateTime? to = null, DisplayedTimeZone timezone = DisplayedTimeZone.Client) + { + return Statistics(CreateQueryString(smartEmailId, @group, from, to, timezone)); + } + + public RateLimited Statistics(string clientId, Guid? smartEmailId = null, string @group = null, DateTime? from = null, DateTime? to = null, DisplayedTimeZone timezone = DisplayedTimeZone.Client) + { + if (clientId == null) throw new ArgumentNullException("clientId"); + + return Statistics(CreateQueryString(smartEmailId, @group, from, to, timezone, clientId)); + } + + private RateLimited Statistics(NameValueCollection query) + { + return HttpGet>("/transactional/statistics", query); + } + + private NameValueCollection CreateQueryString(Guid? smartEmailId, string @group, DateTime? from, DateTime? to, DisplayedTimeZone timezone, string clientId = null) + { + return this.CreateQueryString( + clientId, + query: new NameValueCollection + { + { "smartemailid", smartEmailId.Encode() }, + { "group", @group.Encode() }, + { "from", from.EncodeIso8601DateOnly() }, + { "to", to.EncodeIso8601DateOnly() }, + { "timezone", timezone.Encode() } + }); + } + } + + public class Statistics + { + public StatisticsQuery Query { get; set; } + public long Sent { get; set; } + public long Bounces { get; set; } + public long Delivered { get; set; } + public long Opened { get; set; } + public long Clicked { get; set; } + } +} diff --git a/createsend-netstandard/Transactional/StatisticsQuery.cs b/createsend-netstandard/Transactional/StatisticsQuery.cs new file mode 100644 index 0000000..ff1ff96 --- /dev/null +++ b/createsend-netstandard/Transactional/StatisticsQuery.cs @@ -0,0 +1,13 @@ +using System; + +namespace createsend_dotnet.Transactional +{ + public class StatisticsQuery + { + public Guid? SmartEmailId { get; set; } + public string Group { get; set; } + public DateTime From { get; set; } + public DateTime To { get; set; } + public string TimeZone { get; set; } + } +} diff --git a/createsend-netstandard/Transactional/SubscriberAction.cs b/createsend-netstandard/Transactional/SubscriberAction.cs new file mode 100644 index 0000000..2271c92 --- /dev/null +++ b/createsend-netstandard/Transactional/SubscriberAction.cs @@ -0,0 +1,13 @@ +using System; + +namespace createsend_dotnet.Transactional +{ + public class SubscriberAction + { + public EmailAddress EmailAddress { get; set; } + public DateTimeOffset Date { get; set; } + public string IpAddress { get; set; } + public Geolocation Geolocation { get; set; } + public MailClient MailClient { get; set; } + } +} diff --git a/createsend-netstandard/Transactional/Transactional.cs b/createsend-netstandard/Transactional/Transactional.cs new file mode 100644 index 0000000..35b8f93 --- /dev/null +++ b/createsend-netstandard/Transactional/Transactional.cs @@ -0,0 +1,86 @@ +using System; + +namespace createsend_dotnet.Transactional +{ + public interface ITransactional : IStatistics + { + IClassicEmail ClassicEmail { get; } + ISmartEmail SmartEmail { get; } + IMessageBuilder MessageBuilder(); + IMessages Messages { get; } + } + + public interface IAgencyTransactional : IAgencyStatistics + { + IAgencyClassicEmail ClassicEmail { get; } + IAgencySmartEmail SmartEmail { get; } + IAgencyMessageBuilder MessageBuilder(); + IAgencyMessages Messages { get; } + } + + internal class TransactionalContext : ITransactional, IAgencyTransactional + { + private readonly ClassicEmailContext classicEmail; + private readonly SmartEmailContext smartEmail; + private readonly MessagesContext messages; + private readonly StatisticsContext statistics; + + public TransactionalContext(AuthenticationDetails auth, ICreateSendOptions options) + { + classicEmail = new ClassicEmailContext(auth, options); + smartEmail = new SmartEmailContext(auth, options); + messages = new MessagesContext(auth, options); + statistics = new StatisticsContext(auth, options); + } + + IClassicEmail ITransactional.ClassicEmail + { + get { return classicEmail; } + } + + IAgencyClassicEmail IAgencyTransactional.ClassicEmail + { + get { return classicEmail; } + } + + ISmartEmail ITransactional.SmartEmail + { + get { return smartEmail; } + } + + IAgencySmartEmail IAgencyTransactional.SmartEmail + { + get { return smartEmail; } + } + + IMessages ITransactional.Messages + { + get { return messages; } + } + + IAgencyMessages IAgencyTransactional.Messages + { + get { return messages; } + } + + IMessageBuilder ITransactional.MessageBuilder() + { + return new MessageBuilder(smartEmail, classicEmail); + } + + IAgencyMessageBuilder IAgencyTransactional.MessageBuilder() + { + return new MessageBuilder(smartEmail, classicEmail); + } + + public RateLimited Statistics(Guid? smartEmailId = null, string @group = null, DateTime? from = null, DateTime? to = null, DisplayedTimeZone timezone = DisplayedTimeZone.Client) + { + return statistics.Statistics(smartEmailId, @group, from, to, timezone); + } + + public RateLimited Statistics(string clientId, Guid? smartEmailId = null, string @group = null, DateTime? from = null, DateTime? to = null, DisplayedTimeZone timezone = DisplayedTimeZone.Client) + { + return statistics.Statistics(clientId, smartEmailId, @group, from, to, timezone); + } + } +} diff --git a/createsend-netstandard/createsend-dotnet.nuspec b/createsend-netstandard/createsend-dotnet.nuspec new file mode 100644 index 0000000..1520002 --- /dev/null +++ b/createsend-netstandard/createsend-dotnet.nuspec @@ -0,0 +1,19 @@ + + + + $id$ + createsend-dotnet + $version$ + Campaign Monitor + A .NET library for the Campaign Monitor API with enhancements for Transactional + http://opensource.org/licenses/mit-license + http://campaignmonitor.github.io/createsend-dotnet/ + api campaign monitor .net transactional + + + + + + + + diff --git a/createsend-netstandard/createsend-netstandard.csproj b/createsend-netstandard/createsend-netstandard.csproj new file mode 100644 index 0000000..33bf22d --- /dev/null +++ b/createsend-netstandard/createsend-netstandard.csproj @@ -0,0 +1,17 @@ + + + + netstandard1.6 + + + + + + + + + + + + + \ No newline at end of file