Open rpanjwani opened 8 years ago
I have added this to our backlog for consideration, in the mean time I suggest you take a look at this: https://sendgrid.com/docs/Classroom/Send/v3_Mail_Send/how_to_migrate_from_v2_to_v3_mail_send.html
thanks @thinkingserious , but it doesn't really help me in converting .net MailMessage object to SendGrid Mail object. I still have to go through and read the .net documentation on each portion of the email such as content disposition, content type, etc. etc. and convert and map each of the properties to that of SendGrid. Just opens up simple emailing to a lot of bugs in the future.
@rpanjwani,
Have you tried this? https://github.com/andrewdavey/postal/issues/130#issuecomment-163363032
I think this is using SendGrid as an smtp relay, whereas I am trying to use the web api v3. I was under the impression that azure doesn't allow smtp directly - you have to setup a relay server. I can certainly give it a go - that way I don't have to mess with the API at all.
@rpanjwani,
Please let us know how it goes. Thanks!
You have probably solved this by now, but in case you haven't or someone else wanders in here, then this might at least help get it started:
using System;
using System.IO;
using System.Net.Mail;
namespace SendGrid.Helpers.Mail
{
public static partial class MailMessageExtensions
{
public static Email GetSendGridAddress(this MailAddress address)
{
// SendGrid Server-Side API is currently bugged, and messes up when the name has a comma or a semicolon in it
return String.IsNullOrWhiteSpace(address.DisplayName) ?
new Email(address.Address) :
new Email(address.Address, address.DisplayName.Replace(",", "").Replace(";", ""));
}
public static Attachment GetSendGridAttachment(this System.Net.Mail.Attachment attachment)
{
using (var stream = new MemoryStream())
{
try
{
attachment.ContentStream.CopyTo(stream);
return new Attachment()
{
Disposition = "attachment",
Type = attachment.ContentType.MediaType,
Filename = attachment.Name,
ContentId = attachment.ContentId,
Content = Convert.ToBase64String(stream.ToArray())
};
}
finally
{
stream.Close();
}
}
}
public static Mail GetSendGridMessage(this MailMessage message)
{
var msg = new Mail();
msg.From = message.From.GetSendGridAddress();
if (message.ReplyToList.Count > 0)
{
msg.ReplyTo = message.ReplyToList[0].GetSendGridAddress();
}
var p = new Personalization();
foreach (var a in message.To)
{
p.AddTo(a.GetSendGridAddress());
}
foreach (var a in message.CC)
{
p.AddCc(a.GetSendGridAddress());
}
foreach (var a in message.Bcc)
{
p.AddBcc(a.GetSendGridAddress());
}
msg.AddPersonalization(p);
if (!String.IsNullOrWhiteSpace(message.Subject))
{
msg.Subject = message.Subject;
}
if (!String.IsNullOrWhiteSpace(message.Body))
{
if (message.IsBodyHtml)
{
var c = new Content();
c.Type = "text/html";
if (!message.Body.StartsWith("<html"))
{
c.Value = "<html><body>" + message.Body + "</body></html>";
}
else
{
c.Value = message.Body;
}
msg.AddContent(c);
}
else
{
msg.AddContent(new Content("text/plain", message.Body));
}
}
foreach (var attachment in message.Attachments)
{
msg.AddAttachment(attachment.GetSendGridAttachment());
}
return msg;
}
}
}
now you can get it simply by doing:
var sgMail = mailMessage.GetSendGridMessage();
This is awesome @APM3!
Could you please email us at dx@sendgrid.com with your T-shirt size and mailing address?
Cool extension. Please also add a loop for your replacements from MailMessage .. a.ka substitution tags in Sendgrid
var output = new StringBuilder(EmailBody);
foreach (Dictionary<string, string> _replacement in replacements)
{
foreach (KeyValuePair<string, string> kvp in _replacement)
{
output.Replace(kvp.Key, kvp.Value);
}
}
EmailBody = output.ToString();
Thanks for the suggestion @kvishalv!
I have updated and cleaned up the above extension method. To reflect the latest API (9.6.0). I should add I haven't test this yet but should do before close of business today and will amend this post with any fixes.
public static partial class MailMessageExtensions
{
public static EmailAddress GetSendGridAddress(this MailAddress address)
{
return String.IsNullOrWhiteSpace(address.DisplayName) ?
new EmailAddress(address.Address) :
new EmailAddress(address.Address, address.DisplayName.Replace(",", "").Replace(";", ""));
}
public static SendGrid.Helpers.Mail.Attachment GetSendGridAttachment(this System.Net.Mail.Attachment attachment)
{
using (var stream = new MemoryStream())
{
attachment.ContentStream.CopyTo(stream);
return new SendGrid.Helpers.Mail.Attachment()
{
Disposition = "attachment",
Type = attachment.ContentType.MediaType,
Filename = attachment.Name,
ContentId = attachment.ContentId,
Content = Convert.ToBase64String(stream.ToArray())
};
}
}
public static SendGridMessage GetSendGridMessage(this MailMessage message)
{
var sendgridMessage = new SendGridMessage();
sendgridMessage.From = GetSendGridAddress(message.From);
if (message.ReplyToList.Any())
{
sendgridMessage.ReplyTo = message.ReplyToList.First().GetSendGridAddress();
}
if(message.To.Any())
{
var tos = message.To.Select(x => x.GetSendGridAddress()).ToList();
sendgridMessage.AddTos(tos);
}
if (message.CC.Any())
{
var cc = message.CC.Select(x => x.GetSendGridAddress()).ToList();
sendgridMessage.AddCcs(cc);
}
if(message.Bcc.Any())
{
var bcc = message.Bcc.Select(x => x.GetSendGridAddress()).ToList();
sendgridMessage.AddBccs(bcc);
}
if (!string.IsNullOrWhiteSpace(message.Subject))
{
sendgridMessage.Subject = message.Subject;
}
if (!string.IsNullOrWhiteSpace(message.Body))
{
var content = message.Body;
if (message.IsBodyHtml)
{
if (content.StartsWith("<html"))
{
content = message.Body;
}
else
{
content = $"<html><body>{message.Body}</body></html>";
}
sendgridMessage.AddContent("text/html", content);
}
else
{
sendgridMessage.AddContent("text/plain", content);
}
}
if(message.Attachments.Any())
{
sendgridMessage.Attachments = new System.Collections.Generic.List<SendGrid.Helpers.Mail.Attachment>();
sendgridMessage.Attachments.AddRange(message.Attachments.Select(x => GetSendGridAttachment(x)));
}
return sendgridMessage;
}
}
Furthermore if you believe this would make a useful feature. I would be open to making this extension into a PR with associated tests etc.
I think this would be a great addition as a helper! Thank you!
Upon investigation the System.Net.Mail assembly wasn't inlcuded in the .NET standard v1.3. However it appears that it has been ported to v2.0. Once the v2.0 standard is released I will provide a pull request.
Thanks for the update @da1rren, that sounds good.
Thank you to @APM3 and @da1rren that helped me with a speedy migration from SMTP to API.
I needed to add support for headers in the conversion (especially the X-SMTPAPI header which the webapi doesn't extract from the message so needs special treatment) so i thought i'd contribute my additions, and threw in priority support as well (not that i think we ever really used in in our system, but the calling class supported it so why not).
I converted from vb.net to c# to match the posted code so please forgive any noncompilabilities/faux pas
foreach (void hdr_loopVariable in message.Headers.AllKeys) {
hdr = hdr_loopVariable;
if (hdr == "X-SMTPAPI") {
//deserailize X-SMTPAPI JSON from format {"unique_args":{"Arg1":"Value1"}}
dynamic xSmtpApiDict = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, Dictionary<string, string>>>(message.Headers.Item(hdr));
Dictionary<string, string> uniqueArgs =null;
if (xSmtpApiDict.TryGetValue("unique_args", uniqueArgs)) {
foreach (KeyValuePair<string, string> de in uniqueArgs) {
sendgridMessage.AddCustomArg(de.Key, de.Value);
}
}
} else {
//add normal header
sendgridMessage.AddHeader(hdr, message.Headers.Item(hdr));
}
}
if (message.Priority != Net.Mail.MailPriority.Normal) {
string xpri = "3";
string imp = "Normal";
switch (message.Priority) {
case Net.Mail.MailPriority.High:
xpri = "1";
imp = "High";
break;
case Net.Mail.MailPriority.Low:
xpri = "5";
imp = "Low";
break;
default:
//use defaults
break;
}
sendgridMessage.AddHeader("X-Priority", xpri);
sendgridMessage.AddHeader("X-MSMail-Priority", imp);
sendgridMessage.AddHeader("Importance", imp);
//"X-Priority" (values: 1 to 5- from the highest[1] to lowest[5]),
//"X-MSMail-Priority" (values: High, Normal, Or Low),
//"Importance" (values: High, Normal, Or Low).
}
Not the greatest, I'm sure i could probably pre-process the string to just get one dictionary instead of the nested dictionaries, but i can't know what everyone put in the X-SMTPAPI header, and at this stage i only cared about keeping unique_args to support the webhooks.
Hope that helps someone.
Hi @TrendyTim,
Would you mind adding your example and perhaps a synthesis of the solutions in this thread to our TROUBLESHOOTING.md for hacktoberfest?
With Best Regards,
Elmer
So nothing like this has been added to >9.6.0 yet?
Not yet @electricessence.
Were you considering a PR? :)
@thinkingserious, I'm just surprised it hasn't happened yet. I could easily add what @da1rren posted earlier.
So just for giggles, I forked and tried to update the code. Issues I ran into: StyleCop doesn't seem to properly respect the copyright setting? It's strange. And just a note, the copyright in the stylecop,json needs fixing. Then, because of how the dependencies are arranged, I admittedly am not versed enough using the VS Community 2017 to add dependencies for both 4.5.2 and .NET Standard 1.3. :(
Here's what I was working on to add SendGrid as an "EmailProcessor" injectable interface/code. https://github.com/electricessence/NetMail It has the code from above.
Hi @electricessence,
I will take care of the dependencies and StyleCop issues. Thanks!
Hello. I see this is still a "work in progress" since March 2nd. Do I need to create a custom implementation of this in my project or could we expect it to arrive soon in a near-coming version of SendGrid?
Thank you
Hello @jmevel,
So far, there has not been a PR submitted, but I marked it as work in progress because two people on this thread offered to submit a PR.
Beyond that, it may be a while before we implement this internally as it's not too high on our backlog right now.
My suggestion, if this is time sensitive, is for you to create a custom implementation, or if you are up to it, create a PR here.
With Best Regards,
Elmer
Hello,
Thanks for your reply @thinkingserious. My task on writing an email component at my job has been postponed for the moment.
When time comes I will create a custom implementation then and I'll see if this could lead me to a PR in SendGrid repo or not...
Regards
Thanks @jmevel!
The problem with creating a pull request is simply that .net core 1.0 does not contain System.Net.Mail.MailMessage. Which is the minimum version of .net core the SendGrid client currently supports.
I don't know of a nice way of including the helper method. Until send grid drops support for .net core 1.0 & 1.1. Aside from some nasty if def stuff.
Hi @da1rren,
Thanks for bringing your expertise to the conversation! Perhaps, we just have to get nasty in this case, as I don't see us dropping support for 1.0 & 1.1 :)
With Best Regards,
Elmer
I have a plan. Will fork and take a look at the weekend. It might not be nearly as bad as I would think.
Hi, I found some code in pull request #746 and was able to use that to make migrating from SMTP to SendGrid API much easier. It works, however, if you use AlternateViews in the old MailMessage object (to send both HTML and plain version in the same e-mail), it is not migrated to the SendGridMessage object. I had to modify the ToSendGridMessage method as follows:
/// <summary>
/// Converts a System.Net.Mail.MailMessage to a SendGrid message.
/// </summary>
/// <param name="message">The MailMessage to be converted</param>
/// <returns>Returns a <see cref="SendGridMessage"/> with the properties from the MailMessage</returns>
public static SendGridMessage ToSendGridMessage(this MailMessage message)
{
var sendGridMessage = new SendGridMessage();
sendGridMessage.From = ToSendGridAddress(message.From);
if (message.ReplyToList.Any())
{
if (message.ReplyToList.Count > 1)
{
throw new ArgumentException("Sendgrid only supports one reply to address.");
}
sendGridMessage.ReplyTo = message.ReplyToList.Single().ToSendGridAddress();
}
if (message.To.Any())
{
var tos = message.To.Select(ToSendGridAddress).ToList();
sendGridMessage.AddTos(tos);
}
if (message.CC.Any())
{
var cc = message.CC.Select(ToSendGridAddress).ToList();
sendGridMessage.AddCcs(cc);
}
if (message.Bcc.Any())
{
var bcc = message.Bcc.Select(ToSendGridAddress).ToList();
sendGridMessage.AddBccs(bcc);
}
if (!string.IsNullOrWhiteSpace(message.Subject))
{
sendGridMessage.Subject = message.Subject;
}
if (message.Headers.Count > 0)
{
var headers = message.Headers.AllKeys.ToDictionary(x => x, x => message.Headers[x]);
sendGridMessage.AddHeaders(headers);
}
if (!string.IsNullOrWhiteSpace(message.Body))
{
var htmlAlternateView = message.AlternateViews.FirstOrDefault(av => av.ContentType.MediaType == "text/html");
// if we have HTML alternate view, use it as body
if (htmlAlternateView != null)
{
using (StreamReader reader = new StreamReader(htmlAlternateView.ContentStream))
{
htmlAlternateView.ContentStream.Seek(0, SeekOrigin.Begin);
sendGridMessage.AddContent("text/html", reader.ReadToEnd());
}
}
var content = message.Body;
// if body is html, only add it if alternate view is not present (and was not already added as body)
if (message.IsBodyHtml && htmlAlternateView == null)
{
content = content.Contains("<html")
? message.Body
: $"<html><body>{message.Body}</body></html>";
sendGridMessage.AddContent("text/html", content);
}
else if (!message.IsBodyHtml)
{
sendGridMessage.AddContent("text/plain", content);
}
}
if (message.Attachments.Any())
{
sendGridMessage.Attachments = new List<SendGrid.Helpers.Mail.Attachment>();
sendGridMessage.Attachments.AddRange(message.Attachments.Select(ToSendGridAttachment));
}
return sendGridMessage;
}
It simply uses AlternateView of type text/html if it finds it (and adds it into the Contents of resulting SendGridMessage. If there is no AlternateView, then the body is represented as HTML or Plain depending on the IsBodyHtml flag of original MailMessage. This works for us, hope it will be helpful for someone else too. For complete code (other methods unchanged) refer to the above mentioned pull request.
Just came here to say that this was a huge help, thanks.
Issue Summary
It's just ridiculous that I have to manually map my existing MailMessage to SendGrid's Mail. I am currently using Postal which generates a MailMessage from an MVC view, but now I can't easily convert my MailMessage object to SendGrid.Mail. I have to manually go through each property and I have no idea how it will work.
Steps to Reproduce
No steps, it's pretty obvious.
Technical details: