Skip to content

Commit

Permalink
fix: update email channel
Browse files Browse the repository at this point in the history
  • Loading branch information
JeremyMelton committed Apr 11, 2024
1 parent 62c86b6 commit cbc07b9
Show file tree
Hide file tree
Showing 11 changed files with 460 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,19 @@
using System.Threading.Tasks;
using System.Net.Http;
using System.Text.Json;
using Transmitly.ChannelProvider.Infobip.Sms.SendSmsMessage;
using Transmitly.Infobip;
using System;
using Transmitly.Delivery;
using Transmitly.ChannelProvider.Infobip.Sms.SendSmsMessage;

namespace Transmitly.ChannelProvider.Infobip.Email
{

internal sealed class EmailChannelProviderClient(InfobipChannelProviderConfiguration configuration, HttpClient? httpClient) : ChannelProviderRestClient<IEmail>(httpClient)
internal sealed class EmailChannelProviderClient(InfobipChannelProviderConfiguration configuration) : ChannelProviderRestClient<IEmail>(null)
{
private const string SendEmailPath = "email/3/send";
private readonly InfobipChannelProviderConfiguration _configuration = configuration;

public EmailChannelProviderClient(InfobipChannelProviderConfiguration configuration) : this(configuration, null)
{
System.Text.Json.JsonSerializer.Serialize(new { });
}

protected override void ConfigureHttpClient(HttpClient client)
{
RestClientConfiguration.Configure(client, _configuration);
Expand All @@ -53,7 +48,7 @@ protected override void ConfigureHttpClient(HttpClient client)
var result = await restClient
.PostAsync(
SendEmailPath,
CreateMessageContent(recipientList, communication, communicationContext),
await CreateMessageContent(recipientList, communication, communicationContext),
cancellationToken
)
.ConfigureAwait(false);
Expand Down Expand Up @@ -89,30 +84,55 @@ protected override void ConfigureHttpClient(HttpClient client)
return results;
}

private static HttpContent CreateMessageContent(IAudienceAddress[] recipients, IEmail email, IDispatchCommunicationContext context)
private static async Task<HttpContent> CreateMessageContent(IAudienceAddress[] recipients, IEmail email, IDispatchCommunicationContext context)
{
MultipartFormDataContent form = [];
var emailProperties = new ExtendedEmailChannelProperties(email.ExtendedProperties);
bool hasTemplateId = emailProperties.TemplateId.HasValue && emailProperties.TemplateId.Value > 0;

var messageId = Guid.NewGuid().ToString("N");
TryAddRecipients(recipients, email, form);
AddStringContent(form, EmailField.TemplateId, emailProperties.TemplateId?.ToString());
AddStringContent(form, EmailField.From, email.From?.ToEmailAddress(), !hasTemplateId);
AddStringContent(form, EmailField.Subject, email.Subject, !hasTemplateId);
AddStringContent(form, EmailField.TextBody, email.TextBody);
AddStringContent(form, EmailField.HtmlBody, email.HtmlBody);
await TryAddAmpContent(email, context, form, emailProperties);
AddStringContent(form, EmailField.IntermediateReport, emailProperties.IntermediateReport?.ToString().ToLowerInvariant());
AddStringContent(form, EmailField.NotifyUrl, GetNotifyUrl(emailProperties.NotifyUrl, Guid.NewGuid().ToString("N"), context));
AddStringContent(form, EmailField.NotifyUrl, await GetNotifyUrl(messageId, emailProperties, email, context));
AddStringContent(form, EmailField.Track, emailProperties.Track.ToString().ToLowerInvariant());
AddStringContent(form, EmailField.TrackClicks, emailProperties.TrackClicks?.ToString().ToLowerInvariant());
AddStringContent(form, EmailField.trackOpens, emailProperties.TrackOpens?.ToString().ToLowerInvariant());
AddStringContent(form, EmailField.MessageId, messageId);
AddStringContent(form, EmailField.ApplicationId, emailProperties.ApplicationId);
AddStringContent(form, EmailField.EntityId, emailProperties.EntityId);

return form;
}
private static string? GetNotifyUrl(string? notifyUrl, string messageId, IDispatchCommunicationContext context)

private static async Task TryAddAmpContent(IEmail email, IDispatchCommunicationContext context, MultipartFormDataContent form, ExtendedEmailChannelProperties emailProperties)
{
if (string.IsNullOrWhiteSpace(notifyUrl))
return null;
if (emailProperties.AmpHtml == null)
return;
if (string.IsNullOrWhiteSpace(email.HtmlBody))
throw new InfobipException("HtmlBody is required when using AmpHtml");

return new Uri(notifyUrl).AddPipelineContext(messageId, context.PipelineName, Id.Channel.Email(), Id.ChannelProvider.Infobip()).ToString();
var ampContent = await context.TemplateEngine.RenderAsync(emailProperties.AmpHtml.GetTemplateRegistration(context.CultureInfo, true), context);
AddStringContent(form, EmailField.AmpHtml, ampContent, false);
}

private static async Task<string?> GetNotifyUrl(string messageId, ExtendedEmailChannelProperties emailProperties, IEmail email, IDispatchCommunicationContext context)
{
string? url;
var urlResolver = emailProperties.NotifyUrlResolver ?? email.DeliveryReportCallbackUrlResolver;
if (urlResolver != null)
url = await urlResolver(context).ConfigureAwait(false);
else
{
url = emailProperties.NotifyUrl ?? email.DeliveryReportCallbackUrl;
if (string.IsNullOrWhiteSpace(url))
return null;
}
return new Uri(url).AddPipelineContext(messageId, context.PipelineName, context.ChannelId, context.ChannelProviderId).ToString();
}

private static void AddStringContent(MultipartFormDataContent form, string key, string? value, bool optional = true)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) Code Impressions, LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License")
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using System;
using Transmitly.Delivery;

namespace Transmitly.ChannelProvider.Infobip.Email
{
sealed record EmailDeliveryReport : DeliveryReport
{
public EmailDeliveryReport(DeliveryReport original) : base(original)
{
}

public EmailDeliveryReport(string EventName, string? ChannelId, string? ChannelProviderId, string? PipelineName,
string? ResourceId, DispatchStatus DispatchStatus, object? ChannelCommunication, IContentModel? ContentModel, Exception? Exception)
: base(EventName, ChannelId, ChannelProviderId, PipelineName, ResourceId, DispatchStatus, ChannelCommunication, ContentModel, Exception)
{
var infobipException = this.Infobip().Voice.Error;
if (infobipException != null && Exception == null)
base.Exception = new InfobipException(infobipException);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright (c) Code Impressions, LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License")
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks;
using Transmitly.Delivery;

namespace Transmitly.ChannelProvider.Infobip.Email
{
sealed class EmailDeliveryStatusReportAdaptor : IChannelProviderDeliveryReportRequestAdaptor
{
public Task<IReadOnlyCollection<DeliveryReport>?> AdaptAsync(IRequestAdaptorContext adaptorContext)
{

if (!ShouldAdapt(adaptorContext))
return Task.FromResult<IReadOnlyCollection<DeliveryReport>?>(null);

var statuses = JsonSerializer.Deserialize<EmailStatusReports>(adaptorContext.Content!);

if (statuses?.Results == null)
return Task.FromResult<IReadOnlyCollection<DeliveryReport>?>(null);

var ret = new List<DeliveryReport>(statuses.Results.Count);
foreach (var emailReport in statuses.Results)
{
var report = new EmailDeliveryReport(
DeliveryReport.Event.StatusChanged(),
Id.Channel.Email(),
Id.ChannelProvider.Infobip(),
adaptorContext.PipelineName,
emailReport.MessageId,
Util.ToDispatchStatus(emailReport.Status?.GroupId),
null,
null,
null
).ApplyExtendedProperties(emailReport);

ret.Add(report);
}

return Task.FromResult<IReadOnlyCollection<DeliveryReport>?>(ret);
}

private static bool ShouldAdapt(IRequestAdaptorContext adaptorContext)
{
if (string.IsNullOrWhiteSpace(adaptorContext.Content))
return false;
return
(adaptorContext.GetValue(DeliveryUtil.ChannelIdKey)?.Equals(Id.Channel.Email(), StringComparison.InvariantCultureIgnoreCase) ?? false) &&
(adaptorContext.GetValue(DeliveryUtil.ChannelProviderIdKey)?.Equals(Id.ChannelProvider.Infobip(), StringComparison.InvariantCultureIgnoreCase) ?? false);


}
}
}
11 changes: 10 additions & 1 deletion src/Transmitly.ChannelProvider.Infobip/Email/EmailField.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ internal static class EmailField
public const string NotifyUrl = "notifyUrl";
public const string Track = "track";
public const string EntityId = "entityId";
public const string ApplicationId = "applciationId";
public const string ApplicationId = "applicationId";
public const string AmpHtml = "ampHtml";
public const string TrackClicks = "trackClicks";
public const string trackOpens = "trackOpens";
public const string TrackingUrl = "trackingUrl";
public const string BulkId = "bulkId";
public const string MessageId = "messageId";
public const string ReplyTo = "replyTo";
public const string preserveRecipients = "preserveRecipients";
public const string sendAt = "sendAt";
}
}
25 changes: 25 additions & 0 deletions src/Transmitly.ChannelProvider.Infobip/Email/EmailPrice.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) Code Impressions, LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License")
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using System.Text.Json.Serialization;
namespace Transmitly.ChannelProvider.Infobip.Email
{
public sealed class EmailPrice
{
[JsonPropertyName("pricePerMessage")]
public double? PricePerMessage { get; set; }
[JsonPropertyName("currency")]
public string? Currency { get; set; }
}
}
57 changes: 57 additions & 0 deletions src/Transmitly.ChannelProvider.Infobip/Email/EmailStatusReport.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (c) Code Impressions, LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License")
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using System;
using System.Text.Json.Serialization;

namespace Transmitly.ChannelProvider.Infobip.Email
{
sealed class EmailStatusReport
{
[JsonPropertyName("bulkId")]
public string? BulkId { get; set; }

[JsonPropertyName("messageId")]
public string? MessageId { get; set; }

[JsonPropertyName("to")]
public string? To { get; set; }

[JsonPropertyName("sentAt")]
[JsonConverter(typeof(InfobipDateTimeOffsetConverter))]
public DateTimeOffset? SentAt { get; set; }

[JsonPropertyName("doneAt")]
[JsonConverter(typeof(InfobipDateTimeOffsetConverter))]
public DateTimeOffset? DoneAt { get; set; }

[JsonPropertyName("smsCount")]
public int? SmsCount { get; set; }

[JsonPropertyName("callbackData")]
public string? CallbackData { get; set; }

[JsonPropertyName("price")]
public EmailPrice? Price { get; set; }

[JsonPropertyName("status")]
public CallbackStatus? Status { get; set; }

[JsonPropertyName("error")]
public ErrorStatus? Error { get; set; }

[JsonPropertyName("browserLink")]
public string? BrowserLink { get; set; }
}
}
24 changes: 24 additions & 0 deletions src/Transmitly.ChannelProvider.Infobip/Email/EmailStatusReports.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) Code Impressions, LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License")
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Transmitly.ChannelProvider.Infobip.Email
{
sealed class EmailStatusReports
{
[JsonPropertyName("results")]
public List<EmailStatusReport>? Results { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using System.Threading.Tasks;
using System;
using Transmitly.Template.Configuration;

namespace Transmitly.ChannelProvider.Infobip.Email
Expand All @@ -28,7 +30,7 @@ internal ExtendedEmailChannelProperties(IExtendedProperties properties)
AmpHtml = new ContentTemplateConfiguration();
}

internal ExtendedEmailChannelProperties(IEmailChannel channel):this(Guard.AgainstNull(channel).ExtendedProperties)
internal ExtendedEmailChannelProperties(IEmailChannel channel) : this(Guard.AgainstNull(channel).ExtendedProperties)
{
}

Expand Down Expand Up @@ -99,5 +101,32 @@ public IContentTemplateConfiguration AmpHtml
get => _extendedProperties.GetValue<string?>(ProviderKey, nameof(NotifyUrl));
set => _extendedProperties.AddOrUpdate(ProviderKey, nameof(NotifyUrl), value);
}

/// <summary>
/// The resolver to get the URL on your callback server on which the Delivery report will be sent.
/// </summary>
public Func<IDispatchCommunicationContext, Task<string?>>? NotifyUrlResolver
{
get => _extendedProperties.GetValue<Func<IDispatchCommunicationContext, Task<string?>>>(ProviderKey, nameof(NotifyUrlResolver));
set => _extendedProperties.AddOrUpdate(ProviderKey, nameof(NotifyUrlResolver), value);
}

/// <summary>
/// Required for application use in a send request for outbound traffic. Returned in notification events.
/// </summary>
public string? ApplicationId
{
get => _extendedProperties.GetValue<string?>(ProviderKey, nameof(ApplicationId));
set => _extendedProperties.AddOrUpdate(ProviderKey, nameof(ApplicationId), value);
}

/// <summary>
/// Required for entity use in a send request for outbound traffic. Returned in notification events.
/// </summary>
public string? EntityId
{
get => _extendedProperties.GetValue<string?>(ProviderKey, nameof(EntityId));
set => _extendedProperties.AddOrUpdate(ProviderKey, nameof(EntityId), value);
}
}
}

0 comments on commit cbc07b9

Please sign in to comment.