-
-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improvement proposal for OrchardCoreContrib.Email.SendGrid
: add support for SendGrid Dynamic Templates
via SendGrid Client Library
#135
Comments
OrchardCoreContrib.Email.SendGrid
: add support for SendGrid Dynamic Templates
via SendGrid .NET LibraryOrchardCoreContrib.Email.SendGrid
: add support for SendGrid Dynamic Templates
via SendGrid Client Library
This could be a useful feature for email in general, do you want to send a PR for it? The template could be customizable: Liquid, Text .. etc |
@hishamco I can implement the SendGrid one, adding a method in the SendGridService.cs class. But I need a new interface to use in DI, because Of course, in this case, the template is provided by SendGrid. But yes, I agree, having the possibility to create a template and manage a list of them could be a nice feature for the Email module in general. Thank you |
Let's start on https://github.com/OrchardCoreContrib/OrchardCoreContrib repo and add templating infrastructure to the |
@MarGraz are you still interested to contribute on this one? |
@hishamco Yes, I have created a method to use the SendGrid Dynamic Template, and I've built our own NuGet package based on the Contrib library. In our version I retrieve configuration parameters, such as the SendGrid I believe it's necessary to transfer these settings into the backend. Currently, I don't have the time to do this, but I might be able to fork your project and add the new template method. Let me know. Thank you |
I have been waiting for your contribution since last time :) anyhow feel free to submit a PR when you have time |
@hishamco I think I will add the new method in the SmtpService class, could be ok? Or do you prefer a new Service class? I think I can work on it during the week. Thank you |
While the templating is something that could be used across any email we could introduce another type that will be injected later. Let me propose something or you could share your proposal before you dive into the could to save the time |
After thinking I suggest to add templating APIs with some providers: Text Template, Razor Template, HandleBars ... ect, then the email module could utilize those APIs So, let me start bulding the infrastructure APIs then you can build on top of it. BTW I presume you are using templates for email body only, right? |
Hi @hishamco, Yes, right now I'm creating the template in SendGrid. I’m using some pre-made HTML templates adding them "handlebars". On the Orchard Core side, I have a dynamic template to share those "Personalizations". Below is what I've done so far, with the code included. Note that this is just a demo, so I need to refactoring it 😅 I think a useful feature would be to create items for Let me know how you usually approach refactoring and if you think a dedicated class is needed. I can then start working on it in a new branch. I will need some help with the backoffice part, as I'm fairly new to modifying the DisplayDriver and similar components in the BO. Here the code. /// <summary>
/// Represents a service for sending emails using SendGrid emailing service.
/// </summary>
public class SendGridService : ISendGridService
{
private static readonly char[] EmailsSeparator = new char[] { ',', ';', ' ' };
private static readonly Regex HtmlTagRegex = new Regex(@"<[^>]*>", RegexOptions.Compiled);
private readonly SendGridSettings _sendGridSetting;
private readonly IStringLocalizer S;
/// <summary>
/// Initializes a new instance of <see cref="SendGridService"/>.
/// </summary>
/// <param name="sendGridSetting">The <see cref="IOptions<GmailSettings>"/>.</param>
/// <param name="stringLocalizer">The <see cref="IStringLocalizer<GmailService>"/>.</param>
public SendGridService(
IOptions<SendGridSettings> sendGridSetting,
IStringLocalizer<SendGridService> stringLocalizer
)
{
_sendGridSetting = sendGridSetting.Value;
S = stringLocalizer;
}
/// <inheritdoc/>
public async Task<SmtpResult> SendAsync(MailMessage message)
{
if (_sendGridSetting?.DefaultSender == null)
{
return SmtpResult.Failed(S["SendGrid settings must be configured before an email can be sent."]);
}
try
{
message.From = string.IsNullOrWhiteSpace(message.From)
? _sendGridSetting.DefaultSender
: message.From;
var sendGridMessage = FromMailMessage(message);
var client = new SendGridClient(_sendGridSetting.ApiKey);
await client.SendEmailAsync(sendGridMessage);
return SmtpResult.Success;
}
catch (Exception ex)
{
return SmtpResult.Failed(S["An error occurred while sending an email: '{0}'", ex.Message]);
}
}
/// <summary>
/// Send an Email using the SendGrid DynamicTemplateData.
/// </summary>
/// <param name="message">The MailMessage.</param>
/// <param name="dynamicTemplateData">A custom model that will be passed inside the SendGrid "Personalizations" object, used to pass a value to the handlebars (placeholders) in SendGrid.</param>
/// <returns></returns>
public async Task<SmtpResult> SendUsingSendGridDynamicTemplateAsync(MailMessage message, DynamicTemplateData dynamicTemplateData)
{
if (_sendGridSetting?.DefaultSender == null)
{
return SmtpResult.Failed(S["SendGrid settings must be configured before an email can be sent."]);
}
if (dynamicTemplateData == null || dynamicTemplateData.SendGridDynamicTemplateId == null)
{
return SmtpResult.Failed(S["Dynamic template ID must be provided for SendGrid dynamic template emails."]);
}
try
{
// Create the SendGrid client
var client = new SendGridClient(_sendGridSetting.ApiKey);
// Prepare the message for SendGrid
var sendGridMessage = FromMailMessage(message, dynamicTemplateData);
// Send the email
var response = await client.SendEmailAsync(sendGridMessage);
if (response.StatusCode == HttpStatusCode.OK || response.StatusCode == HttpStatusCode.Accepted)
{
return SmtpResult.Success;
}
var responseBody = await response.Body.ReadAsStringAsync();
return SmtpResult.Failed(S[$"An error occurred while sending an email: '{responseBody}'"]);
}
catch (Exception ex)
{
return SmtpResult.Failed(S[$"An error occurred while sending an email: '{ex.Message}'"]);
}
}
/// <summary>
/// Used for a normal email, that is not using the SendGrid Dynamic Template.
/// </summary>
/// <param name="message">The MailMessage.</param>
/// <returns></returns>
private SendGridMessage FromMailMessage(MailMessage message)
{
var senderAddress = string.IsNullOrWhiteSpace(message.Sender)
? _sendGridSetting.DefaultSender
: message.From;
var sendGridMessage = new SendGridMessage
{
From = new EmailAddress(senderAddress)
};
if (!string.IsNullOrWhiteSpace(message.To))
{
foreach (var address in message.To.Split(EmailsSeparator, StringSplitOptions.RemoveEmptyEntries))
{
sendGridMessage.AddTo(new EmailAddress(address));
}
}
// If no template, just send a plain text or HTML email
if (message.IsHtmlBody)
{
sendGridMessage.HtmlContent = message.Body;
sendGridMessage.PlainTextContent = HtmlTagRegex.Replace(message.Body, string.Empty);
}
else
{
sendGridMessage.PlainTextContent = message.Body;
}
return sendGridMessage;
}
/// <summary>
/// Used for the email is using a SendGrid Dynamic Template
/// </summary>
/// <param name="message">The MailMessage.</param>
/// <param name="dynamicTemplateData">A custom model that will be passed inside the SendGrid "Personalizations" object, used to pass a value to the handlebars (placeholders) in SendGrid.</param>
/// <returns></returns>
private SendGridMessage FromMailMessage(MailMessage message, DynamicTemplateData dynamicTemplateData)
{
var senderAddress = string.IsNullOrWhiteSpace(message.Sender)
? _sendGridSetting.DefaultSender
: message.From;
var sendGridMessage = new SendGridMessage();
sendGridMessage.SetFrom(new EmailAddress(senderAddress));
//sendGridMessage.SetGlobalSubject(message.Subject); // Those method are not working, so I passed the subject into the DynamicTemplateData, it was the only working solution
//sendGridMessage.SetSubject(message.Subject);
if (!string.IsNullOrWhiteSpace(message.To))
{
foreach (var address in message.To.Split(EmailsSeparator, StringSplitOptions.RemoveEmptyEntries))
{
sendGridMessage.AddTo(new EmailAddress(address));
}
}
// Handle dynamic template data
if (!string.IsNullOrWhiteSpace(dynamicTemplateData.SendGridDynamicTemplateId))
{
// Set the template ID
sendGridMessage.TemplateId = dynamicTemplateData.SendGridDynamicTemplateId;
// Prepare a dictionary to hold template data dynamically
var templateData = new Dictionary<string, object>();
// Add EmailParts to the template data dynamically
if (dynamicTemplateData.EmailParts != null)
{
foreach (var prop in dynamicTemplateData.EmailParts.GetType().GetProperties())
{
templateData.Add(prop.Name, prop.GetValue(dynamicTemplateData.EmailParts));
}
}
// Add Placeholders to the template data dynamically
if (dynamicTemplateData.Placeholders != null)
{
foreach (var placeholder in dynamicTemplateData.Placeholders)
{
templateData.Add(placeholder.Key, placeholder.Value);
}
}
// Set the template data in the SendGrid message
sendGridMessage.SetTemplateData(templateData);
}
else
{
// If no template, just send a plain text or HTML email
if (message.IsHtmlBody)
{
sendGridMessage.HtmlContent = message.Body;
sendGridMessage.PlainTextContent = HtmlTagRegex.Replace(message.Body, string.Empty);
}
else
{
if (result.Errors != null && result.Errors.Any())
{
Console.WriteLine("Failed to send email:");
foreach (var error in result.Errors)
{
Console.WriteLine($"- {error}");
}
}
}
}
return sendGridMessage;
}
} This is my custom public class DynamicTemplateData
{
public string SendGridDynamicTemplateId { get; set; }
public EmailParts EmailParts { get; set; }
public List<EmailPlaceholder> Placeholders { get; set; }
}
public class EmailParts
{
public string subject { get; set; } // ATTENTION: do not change it in uppercase, otherwise it will not work with the placeholder {{subject}} in the SendGrid template. Is not possible to use [JsonProperty] or [JsonPropertyName], they are not working because it seems that SendGrid uses their own parser.
public string RecipientName { get; set; }
}
public class EmailPlaceholder
{
public string Key { get; set; }
public object Value { get; set; }
} To test it quickly, I created a class Program
{
private static void ConfigureServices(ServiceCollection services)
{
// Add logging services
services.AddLogging(configure => configure.AddConsole());
// Add localization services
services.AddLocalization();
// Add SendGridService to DI with necessary configuration
services.AddTransient<ISendGridService, SendGridService>();
// Mocking SendGridSettings with necessary API key and default sender
services.Configure<SendGridSettings>(options =>
{
options.ApiKey = "*******************"; // ----- Replace with your actual SendGrid API key
options.DefaultSender = "youremailsender@gmail.com"; // ----- Replace with your sender email
});
}
static async Task Main(string[] args)
{
Console.WriteLine("Testing SendGrid Email Sending...");
// Set up dependency injection (if needed)
ServiceCollection services = new ServiceCollection();
ConfigureServices(services);
ServiceProvider serviceProvider = services.BuildServiceProvider();
// Get the SendGrid service from DI
ISendGridService sendGridService = serviceProvider.GetRequiredService<ISendGridService>();
// Prepare a test email message
MailMessage message = new MailMessage
{
To = "recipient-email@gmail.com", // ----- Replace with your recipient email
IsHtmlBody = true
};
// Those are the placeholders I have inside my SendGrid template,
// I can use it writing, for example: {{emailbody.AcceptStoreUri}}
Dictionary<string, string> emailBodyItems = new Dictionary<string, string>
{
{ "UsernameStore", "Store Pippo" },
{ "EmailStore", "OurStore@email.com" },
{ "AcceptStoreUri", "https://accept.uri" },
{ "RefuseStoreUri", "https://refuse.uri" }
};
DynamicTemplateData dynamicTemplateData = new DynamicTemplateData
{
SendGridDynamicTemplateId = "d-*******************************ab3", // ----- Replace with your SendGrid Template Id
EmailParts = new EmailParts
{
subject = $"This is a test email",
RecipientName = "Admin"
},
Placeholders = new List<EmailPlaceholder>
{
new EmailPlaceholder { Key = "emailbody", Value = emailBodyItems }
}
};
// Send email using dynamic template
SmtpResult result = await sendGridService.SendUsingSendGridDynamicTemplateAsync(message, dynamicTemplateData);
if (result.Succeeded)
{
Console.WriteLine("Email sent successfully!");
}
else
{
Console.WriteLine($"Failed to send email: {result.Errors}");
}
}
} Thank you |
Thanks, @MarGraz, I think we need to start building templating infrastructure APIs, and then we could add a feature Let me start after Orchard Core |
@hishamco no problem. When you start a new branch, let me know how I can help 😊 |
Hi,
I think it would be great if the OrchardCoreContrib.Email.SendGrid module could implement the SendGrid client library to send emails using pre-created
Dynamic Templates
.SendGrid allows you to create email templates with placeholders in their backoffice, using a WYSIWYG editor (official guide here). You can then use the client library to select the template and pass dynamic text to the APIs. Here is an example of using Dynamic Templates.
Below is the code snippet from this example:
I think that should be sufficient to add another method in the SendGridService.cs class, that allows the use of a
Dynamic Template
.What do you think about it? 😊
Thank you
The text was updated successfully, but these errors were encountered: