I have a usecase where I have 1000 emails with the bodies prepared (they are ready to send as is), a single sent from email address, 1000 recipients. I am using SendGrid API v3 in C#. I am trying to figure out how to bulk send them to the SendGrid API. This is my code:
private async Task SendBatchEmails(DataRowCollection emailDataRows)
{
var WriteToDatabaseCollection = new Dictionary<Guid, string>();
var emailObjectCollection = new List<SendGridMessage>();
foreach (DataRow emailDataRow in emailDataRows)
{
var emailObject = new SendGridMessage();
var to = (new EmailAddress(emailDataRow["RecipientEmailAddress"] + "", emailDataRow["RecipientName"] + ""));
var from = new EmailAddress(emailDataRow["SenderEmailAddress"] + "", emailDataRow["SenderName"] + "");
var subject = emailDataRow["Subject"] + "";
var text = emailDataRow["MessageBody"] + "";
var html = $"<strong>{emailDataRow["MessageBody"] + "" }</strong>";
var msg = MailHelper.CreateSingleEmail(from, to, subject, text, html);
emailObjectCollection.Add(msg);
}
await emailClient.SendBatchEmailsEmailAsync(emailObjectCollection);
dataContext.UpdateEmailResult(WriteToDatabaseCollection);
}
public async Task SendBatchEmailsEmailAsync(List<SendGridMessage> messages)
{
return await client.????(messages);
}
client is a SendGridClient, and the only option I have is: SendEmailAsync(msg)
How do I send a batch fo sendgrid messages?
Twilio SendGrid developer evangelist here.
There isn't a batch email send for emails with different bodies. Though you can send the same body to multiple addresses.
To send your 1000 emails you need to loop through your list of messages and call the API once per message.
If any of the emails you want to send has same body as others (you can't bulk send emails with different content as mentioned in the other answer), you could group them by body and then send in chunks. SendGridClient takes max 1000 at a time, so you'd also need to check for that. With something like this:
public class Email
{
string Content { get; }
string Address { get; }
}
Your code to group, chunk and send your emails in batches (where emails is a collection of Email objects) would look something like this:
var client = new SendGridClient("123456APIKEY");
var senderEmail = "someEmail";
var groupedByContent = emails.GroupBy(x => x.Content, x => x.Address);
foreach(var group in groupedByContent)
foreach(var recipientsBatch in group.Chunk(1000))
{
var message = new SendGridMessage();
// This extra index is needed to create separate personalizations, if you don't want recipients to see each others email
for (var i = 0; i < recipientsBatch.Length; i++)
message.AddTo(new EmailAddress { Email = recipientsBatch[i] }, i);
message.PlainTextContent = group.Key;
message.SetFrom(senderEmail);
var response = await client.SendEmailAsync(message);
// response processing code ...
}
Related
I am working on task in ASP.NET Core 5 (C#) which requires to send an email using Graph API, I have referred to following article and did the configuration on the Azure trial account and was able to send the emails.
Sending e-mails with Microsoft Graph using .NET
This is the send email code:
//send email
var client = await GetAuthenticatedGraphClient();
await client.Users[senderObjectId]
.SendMail(graphMailMessage, true)
.Request()
.PostAsync();
senderObjectId - Object Id coming from config
We deployed the same code on the client's Azure account we needed the User Object Id for the service account that we are going to use as a sender's email id. However, the client came back saying that the account is not part of the Azure AD and its a service account. Is there a way of sending emails without using the user object id.
Here's the method which takes parameters for mail sending. Also, it separates (comma) the mail and sends it to multiple users
public string SendEmail(string fromAddress, string toAddress, string CcAddress, string subject, string message, string tenanatID , string clientID , string clientSecret)
{
try
{
var credentials = new ClientSecretCredential(
tenanatID, clientID, clientSecret,
new TokenCredentialOptions { AuthorityHost = AzureAuthorityHosts.AzurePublicCloud });
GraphServiceClient graphServiceClient = new GraphServiceClient(credentials);
string[] toMail = toAddress.Split(',');
List<Recipient> toRecipients = new List<Recipient>();
int i = 0;
for (i = 0; i < toMail.Count(); i++)
{
Recipient toRecipient = new Recipient();
EmailAddress toEmailAddress = new EmailAddress();
toEmailAddress.Address = toMail[i];
toRecipient.EmailAddress = toEmailAddress;
toRecipients.Add(toRecipient);
}
List<Recipient> ccRecipients = new List<Recipient>();
if (!string.IsNullOrEmpty(CcAddress))
{
string[] ccMail = CcAddress.Split(',');
int j = 0;
for (j = 0; j < ccMail.Count(); j++)
{
Recipient ccRecipient = new Recipient();
EmailAddress ccEmailAddress = new EmailAddress();
ccEmailAddress.Address = ccMail[j];
ccRecipient.EmailAddress = ccEmailAddress;
ccRecipients.Add(ccRecipient);
}
}
var mailMessage = new Message
{
Subject = subject,
Body = new ItemBody
{
ContentType = BodyType.Html,
Content = message
},
ToRecipients = toRecipients,
CcRecipients = ccRecipients
};
// Send mail as the given user.
graphServiceClient
.Users[fromAddress]
.SendMail(mailMessage, true)
.Request()
.PostAsync().Wait();
return "Email successfully sent.";
}
catch (Exception ex)
{
return "Send Email Failed.\r\n" + ex.Message;
}
}
If we have the UniqueId to the mail we wish to move the attachment to via the usage of an ImapClient, how exactly can we achieve this?
Thank you!
UniqueId? AddAttachmentToMessage (ImapClient client, ImapFolder folder, UniqueId uid, MimeEntity attachment)
{
var message = folder.GetMessage (uid);
var body = message.Body;
Multipart multipart;
if (message.Body is Multipart && message.Body.ContentType.IsMimeType ("multipart", "mixed")) {
multipart = (Multipart) message.Body;
} else {
multipart = new Multipart ("mixed");
multipart.Add (message.Body);
message.Body = multipart;
}
multipart.Add (attachment);
var newUid = folder.Append (message);
folder.AddFlags (uid, MessageFlags.Deleted, true);
if (client.Capabilities.HasFlag (ImapCapabilities.UidPlus))
folder.Expunge (new UniqueId[] { uid });
return newUid;
}
If the server doesn't support UIDPLUS and you need the newUid value, then you can probably do something like this:
if (!client.Capabilities.HasFlag (ImapCapabilities.UidPlus)) {
var initialUids = folder.Search (SearchQuery.All);
folder.Append (message);
var updatedUids = folder.Search (SearchQuery.All);
// find the new uids
var newUids = new UniqueIdSet (SortOrder.Ascending);
for (int i = updatedUids.Count - 1; i >= 0; i--) {
if (!initialUids.Contains (updatedUids[i]))
newUids.Add (updatedUids[i]);
}
// get envelope info for each of the new messages
var newItems = folder.Fetch (newUids, MessageSummaryItems.UniqueId | MessageSummaryItems.Envelope);
foreach (var item in newItems) {
var msgid = MimeUtils.ParseMessageId (item.Envelope.MessageId);
if (message.MessageId.Equals (msgid))
return item.UniuqeId;
// Note: if you want to be more pedantic, you can compare the From/To/Cc/ReplyTo and Subject fields as well.
}
}
I have 3000 emails in my gmail account. I want to create an aggregated list of all the senders so that I can more effectively clean up my inbox. I dont need to download the message bodys or the attachments.
I used this sample to get me started (https://developers.google.com/gmail/api/quickstart/dotnet) althought now I cant figure out how to return more than 100 message ids when i execute this code:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Gmail.v1;
using Google.Apis.Gmail.v1.Data;
using Google.Apis.Requests;
using Google.Apis.Services;
using Google.Apis.Util;
using Google.Apis.Util.Store;
namespace GmailQuickstart
{
class Program
{
static string[] Scopes = { GmailService.Scope.GmailReadonly };
static string ApplicationName = "Gmail API .NET Quickstart";
static void Main(string[] args)
{
UserCredential credential;
using (var stream = new FileStream("credentials.json", FileMode.Open, FileAccess.Read))
{
string credPath = "token.json";
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
Scopes,
"user",
CancellationToken.None,
new FileDataStore(credPath, true)).Result;
Console.WriteLine("Credential file saved to: " + credPath);
}
// Create Gmail API service.
var service = new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
////get all of the message ids for the messages in the inbox
var messageRequest = service.Users.Messages.List("me");
messageRequest.LabelIds = "INBOX";
var messageList = new List<Message>();
ListMessagesResponse messageResponse1 = new ListMessagesResponse();
var k = 0;
do
{
messageResponse1 = messageRequest.Execute();
messageList.AddRange(messageResponse1.Messages);
var output = $"Request {k} - Message Count: {messageList.Count()} Page Token: {messageRequest.PageToken} - Next Page Token: {messageResponse1.NextPageToken}";
Console.WriteLine(output);
System.IO.File.AppendAllText(#"C:\000\log.txt", output);
messageRequest.PageToken = messageResponse1.NextPageToken;
k++;
//this switch allowed me to walk through getting multiple pages of emails without having to get them all
//if (k == 5)
//{
// break;
//}
} while (!String.IsNullOrEmpty(messageRequest.PageToken));
//once i created the list of all the message ids i serialized the list to JSON and wrote it to a file
//so I could test the next portions without having to make the calls against the above each time
var serializedMessageIdList = Newtonsoft.Json.JsonConvert.SerializeObject(messageList);
System.IO.File.WriteAllText(#"C:\000\MessageIds.json", serializedMessageIdList);
//read in the serialized list and rehydrate it to test the next portion
var mIdList = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Message>>(System.IO.File.ReadAllText(#"C:\000\MessageIds.json"));
//this method takes those message ids and gets the message object from the api for each of them
//1000 is the maximum number of requests google allows in a batch request
var messages = BatchDownloadEmails(service, mIdList.Select(m => m.Id), 1000);
//again i'm serializing the message list and writing them to a file
var serializedMessageList = Newtonsoft.Json.JsonConvert.SerializeObject(messages);
System.IO.File.WriteAllText(#"C:\000\Messages.json", serializedMessageList);
//and then reading them in and rehydrating the list to test the next portion
var mList = Newtonsoft.Json.JsonConvert.DeserializeObject<IList<Message>>(System.IO.File.ReadAllText(#"C:\000\Messages.json"));
//then i loop through each message and pull the values out of the payload header i'm looking for
var emailList = new List<EmailItem>();
foreach (var message in mList)
{
if (message != null)
{
var from = message.Payload.Headers.SingleOrDefault(h => h.Name == "From")?.Value;
var date = message.Payload.Headers.SingleOrDefault(h => h.Name == "Date")?.Value;
var subject = message.Payload.Headers.SingleOrDefault(h => h.Name == "Subject")?.Value;
emailList.Add(new EmailItem() { From = from, Subject = subject, Date = date });
}
}
//i serialized this list as well
var serializedEmailItemList = Newtonsoft.Json.JsonConvert.SerializeObject(emailList);
System.IO.File.WriteAllText(#"C:\000\EmailItems.json", serializedEmailItemList);
//rehydrate for testing
var eiList = Newtonsoft.Json.JsonConvert.DeserializeObject<List<EmailItem>>(System.IO.File.ReadAllText(#"C:\000\EmailItems.json"));
//here is where i do the actual aggregation to determine which senders i have the most email from
var senderSummary = eiList.GroupBy(g => g.From).Select(g => new { Sender = g.Key, Count = g.Count() }).OrderByDescending(g => g.Count);
//serialize and output the results
var serializedSummaryList = Newtonsoft.Json.JsonConvert.SerializeObject(senderSummary);
System.IO.File.WriteAllText(#"C:\000\SenderSummary.json", serializedSummaryList);
}
public static IList<Message> BatchDownloadEmails(GmailService service, IEnumerable<string> messageIds, int chunkSize)
{
// Create a batch request.
var messages = new List<Message>();
//because the google batch request will only allow 1000 requests per batch the list needs to be split
//based on chunk size
var lists = messageIds.ChunkBy(chunkSize);
//double batchRequests = (2500 + 999) / 1000;
//for each list create a request with teh message id and add it to the batch request queue
for (int i = 0; i < lists.Count(); i++)
{
var list = lists.ElementAt(i);
Console.WriteLine($"list: {i}...");
var request = new BatchRequest(service);
foreach (var messageId in list)
{
//Console.WriteLine($"message id: {messageId}...");
var messageBodyRequest = service.Users.Messages.Get("me", messageId);
//messageBodyRequest.Format = UsersResource.MessagesResource.GetRequest.FormatEnum.Metadata;
request.Queue<Message>(messageBodyRequest,
(content, error, index, message) =>
{
messages.Add(content);
});
}
Console.WriteLine("");
Console.WriteLine("ExecuteAsync");
//execute all the requests in the queue
request.ExecuteAsync().Wait();
System.Threading.Thread.Sleep(5000);
}
return messages;
}
}
public class EmailItem
{
public string From { get; set; }
public string Subject { get; set; }
public string Date { get; set; }
}
public static class IEnumerableExtensions
{
public static IEnumerable<IEnumerable<T>> ChunkBy<T>(this IEnumerable<T> source, int chunkSize)
{
return source
.Select((x, i) => new { Index = i, Value = x })
.GroupBy(x => x.Index / chunkSize)
.Select(x => x.Select(v => v.Value));
}
}
}
The research I've done says I need to use a batch request and based on the information I've found Im not able to adapt it to what I'm trying to accomplish. My understanding is that I would use the batch request to get all of the message ids and then 3000 individual calls to get the actual from, subject, and date received from each email in my inbox??
You can use paging to get a full list.
Pass the page token from the previous page to get the next call to Users.Messages.List (don't pass into the first call to get things started). Detect the end when the result contains no messages.
This allows you to get all the messages in the mailbox.
NB. I suggest you make the code async: if there are more than a few messages to read, it can take an appreciable time to get them all.
You can also use PageStreamer to get the remainder of the results.
var pageStreamer = new PageStreamer<Google.Apis.Gmail.v1.Data.Message, UsersResource.MessagesResource.ListRequest, ListMessagesResponse, string>(
(request, token) => request.PageToken = token,
response => response.NextPageToken,
response => response.Messages);
var req = service.Users.Messages.List("me");
req.MaxResults = 1000;
foreach (var result in pageStreamer.Fetch(req))
{
Console.WriteLine(result.Id);
}
This code will continue to run as long as there are additional results to request. Batching isnt really going to help you here as there is no way to know what the next page token will be.
I am trying to track down a performance issue that is not allowing me to send more than 3 or so message with the twilio c# api. In short, we have a windows service that responds to bulk 'JOIN' messages. See the following code:
int i = 0;
DateTime d = DateTime.Now;
TimeSpan ts = new TimeSpan(0,0,1);
foreach (CustomerTextMessage t in o)
{
i++;
ThreadInput ti = new ThreadInput { MessageIds = i, Body = t.Body, ThreadCreated = DateTime.Now, FromNumber = t.FromNumber };
ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessResponseList), (object)ti);
}
The ProcessResponseList method does a quick look up on the customer, then responds using this method:
public static void SendResponse(string number, string body)
{
string AccountSid = ConfigurationManager.AppSettings["AccountSid"];
string AuthToken = ConfigurationManager.AppSettings["AccountToken"];
var twilio = new TwilioRestClient(AccountSid, AuthToken);
string fromNumber = "";
if (ConfigurationManager.AppSettings["StageExecution"] == "true")
{
BusinessLayer.SLOSBL.Logger logger = new BusinessLayer.SLOSBL.Logger("DiscoverTextMessagingService", "DiscoverTextMessagingService");
AuthToken = ConfigurationManager.AppSettings["StageApplicationToken"];
var longCodes = twilio.ListIncomingPhoneNumbers(null, "StagePhoneNumber", null, null);
fromNumber = longCodes.IncomingPhoneNumbers[1].PhoneNumber;
logger.Notify("Sending text message to: " + number + " from Twilio number: " + fromNumber + ". The body of the message reads: " + body);
}
else
{
var shortCodes = twilio.ListShortCodes(null, null);
fromNumber = "+" + shortCodes.ShortCodes[0].ShortCode;
}
//var message = twilio.SendMessage(fromNumber, number, body, new string[0], null, AuthToken );
DateTime d = DateTime.Now;
twilio.SendMessage(fromNumber, number, body, new string[0], null, AuthToken, null, m => { MessageAsyncCallBack(m, d); });
}
What I am seeing is that it is able to send these message about 3 every second. I am really looking to increase that closer to the 30 every second that a short code is possible but I am not able to even get them to the twilio queue fast enough. So my question, is there anything obvious that I should be doing? Should I be reusing the TwilioRestClient variable for all message? Is there a limit to the number of connections someplace that I am missing? Thanks in advance.
I am trying to figure out how to add variables to existing template (example: Web Link Or Name dynamically) which has been created in sendgrid template engine, I am unsure how to do this using the SendGrid C# .NET libraries. I am wondering if anyone could help me.
// Create the email object first, then add the properties.
SendGridMessage myMessage = new SendGridMessage();
myMessage.AddTo("test#test.com");
myMessage.From = new MailAddress("test#test.com", "Mr test");
myMessage.Subject = " ";
var timestamp = DateTime.Now.ToString("HH:mm:ss tt");
myMessage.Html = "<p></p> ";
myMessage.EnableTemplate("<p> <% body %> Hello</p>");
myMessage.EnableTemplateEngine("9386b483-8ad4-48c2-9ee3-afc7618eb56a");
var identifiers = new Dictionary<String, String>();
identifiers["USER_FULLNAME"] = "Jimbo Jones";
identifiers["VERIFICATION_URL"] = "www.google.com";
myMessage.AddUniqueArgs(identifiers);
// Create credentials, specifying your user name and password.
var credentials = new NetworkCredential("username", "password");
// Create an Web transport for sending email.
var transportWeb = new Web(credentials);
// Send the email.
transportWeb.Deliver(myMessage);
My Email
Hello -REP-
<%body%>
Fotter
My C# Code
myMessage.AddSubstitution("-REP-", substitutionValues);
Works PERFECT!!!
I found the solution:
replacementKey = "*|VERIFICATION_URL|*";
substitutionValues = new List<string> { VERIFICATION_URL };
myMessage.AddSubstitution(replacementKey, substitutionValues);
I've used the following approach. Note you have to provide the mail.Text and mail.Html values - I use the empty string and <p></p> tag as seen in the example. Your SendGrid template also still must contain the default <%body%> and <%subject%> tokens, although they will be replaced with the actual subject and body value specified in the mail.Text and mail.Html properties.
public void Send(string from, string to, string subject, IDictionary<string, string> replacementTokens, string templateId)
{
var mail = new SendGridMessage
{
From = new MailAddress(from)
};
mail.AddTo(to);
mail.Subject = subject;
// NOTE: Text/Html and EnableTemplate HTML are not really used if a TemplateId is specified
mail.Text = string.Empty;
mail.Html = "<p></p>";
mail.EnableTemplate("<%body%>");
mail.EnableTemplateEngine(templateId);
// Add each replacement token
foreach (var key in replacementTokens.Keys)
{
mail.AddSubstitution(
key,
new List<string>
{
replacementTokens[key]
});
}
var transportWeb = new Web("THE_AUTH_KEY");
var result = transportWeb.DeliverAsync(mail);
}
It can then be called like this:
Send(
"noreply#example.com",
"TO_ADDRESS",
"THE SUBJECT",
new Dictionary<string, string> {
{ "#Param1!", "Parameter 1" },
{ "#Param2!", "Parameter 2" } },
"TEMPLATE_GUID");
After did lots of RND. My below code is working fine & Tested as well.
SendGridMessage myMessage = new SendGridMessage();
myMessage.AddTo(email);
myMessage.AddBcc("MyEmail#gmail.com");
myMessage.AddBcc("EmailSender_CC#outlook.com");
myMessage.From = new MailAddress("SenderEmail#outlook.com", "DriverPickup");
//myMessage.Subject = "Email Template Test 15.";
myMessage.Headers.Add("X-SMTPAPI", GetMessageHeaderForWelcome("MyEmail#Gmail.com", callBackUrl));
myMessage.Html = string.Format(" ");
// Create an Web transport for sending email.
var transportWeb = new Web(SendGridApiKey);
// Send the email, which returns an awaitable task.
transportWeb.DeliverAsync(myMessage);
I have created Separate method for getting JSON header
private static string GetMessageHeaderForWelcome(string email, string callBackUrl)
{
var header = new Header();
//header.AddSubstitution("{{FirstName}}", new List<string> { "Dilip" });
//header.AddSubstitution("{{LastName}}", new List<string> { "Nikam" });
header.AddSubstitution("{{EmailID}}", new List<string> { email });
header.AddSubstitution("-VERIFICATIONURL-", new List<string>() { callBackUrl });
//header.AddSubstitution("*|VERIFICATIONURL|*", new List<string> { callBackUrl });
//header.AddSubstitution("{{VERIFICATIONURL}}", new List<string> { callBackUrl });
header.AddFilterSetting("templates", new List<string> { "enabled" }, "1");
header.AddFilterSetting("templates", new List<string> { "template_id" }, WelcomeSendGridTemplateID);
return header.JsonString();
}
Below code I have used in my HTML Sendgrid template.
<div>Your {{EmailID}} register. Please <a class="btn-primary" href="-VERIFICATIONURL-">Confirm email address</a></div>
In case if any query please let me know.
For inline HTML replace you need to use -YourKeyForReplace- & for text replace you need to use {{UserKeyForReplace}}