I am trying to read emails from Gmail. I have tried every API / open source project I can find, and can not get any of them working.
Does anyone have a sample of working code that will allow me to authenticate and download emails from a Gmail account?
Final working version posted below: https://stackoverflow.com/a/19570553/550198
Using the library from: https://github.com/pmengal/MailSystem.NET
Here is my complete code sample:
Email Repository
using System.Collections.Generic;
using System.Linq;
using ActiveUp.Net.Mail;
namespace GmailReadImapEmail
{
public class MailRepository
{
private Imap4Client client;
public MailRepository(string mailServer, int port, bool ssl, string login, string password)
{
if (ssl)
Client.ConnectSsl(mailServer, port);
else
Client.Connect(mailServer, port);
Client.Login(login, password);
}
public IEnumerable<Message> GetAllMails(string mailBox)
{
return GetMails(mailBox, "ALL").Cast<Message>();
}
public IEnumerable<Message> GetUnreadMails(string mailBox)
{
return GetMails(mailBox, "UNSEEN").Cast<Message>();
}
protected Imap4Client Client
{
get { return client ?? (client = new Imap4Client()); }
}
private MessageCollection GetMails(string mailBox, string searchPhrase)
{
Mailbox mails = Client.SelectMailbox(mailBox);
MessageCollection messages = mails.SearchParse(searchPhrase);
return messages;
}
}
}
Usage
[TestMethod]
public void ReadImap()
{
var mailRepository = new MailRepository(
"imap.gmail.com",
993,
true,
"yourEmailAddress#gmail.com",
"yourPassword"
);
var emailList = mailRepository.GetAllMails("inbox");
foreach (Message email in emailList)
{
Console.WriteLine("<p>{0}: {1}</p><p>{2}</p>", email.From, email.Subject, email.BodyHtml.Text);
if (email.Attachments.Count > 0)
{
foreach (MimePart attachment in email.Attachments)
{
Console.WriteLine("<p>Attachment: {0} {1}</p>", attachment.ContentName, attachment.ContentType.MimeType);
}
}
}
}
Another example, this time using MailKit
public class MailRepository : IMailRepository
{
private readonly string mailServer, login, password;
private readonly int port;
private readonly bool ssl;
public MailRepository(string mailServer, int port, bool ssl, string login, string password)
{
this.mailServer = mailServer;
this.port = port;
this.ssl = ssl;
this.login = login;
this.password = password;
}
public IEnumerable<string> GetUnreadMails()
{
var messages = new List<string>();
using (var client = new ImapClient())
{
client.Connect(mailServer, port, ssl);
// Note: since we don't have an OAuth2 token, disable
// the XOAUTH2 authentication mechanism.
client.AuthenticationMechanisms.Remove("XOAUTH2");
client.Authenticate(login, password);
// The Inbox folder is always available on all IMAP servers...
var inbox = client.Inbox;
inbox.Open(FolderAccess.ReadOnly);
var results = inbox.Search(SearchOptions.All, SearchQuery.Not(SearchQuery.Seen));
foreach (var uniqueId in results.UniqueIds)
{
var message = inbox.GetMessage(uniqueId);
messages.Add(message.HtmlBody);
//Mark message as read
//inbox.AddFlags(uniqueId, MessageFlags.Seen, true);
}
client.Disconnect(true);
}
return messages;
}
public IEnumerable<string> GetAllMails()
{
var messages = new List<string>();
using (var client = new ImapClient())
{
client.Connect(mailServer, port, ssl);
// Note: since we don't have an OAuth2 token, disable
// the XOAUTH2 authentication mechanism.
client.AuthenticationMechanisms.Remove("XOAUTH2");
client.Authenticate(login, password);
// The Inbox folder is always available on all IMAP servers...
var inbox = client.Inbox;
inbox.Open(FolderAccess.ReadOnly);
var results = inbox.Search(SearchOptions.All, SearchQuery.NotSeen);
foreach (var uniqueId in results.UniqueIds)
{
var message = inbox.GetMessage(uniqueId);
messages.Add(message.HtmlBody);
//Mark message as read
//inbox.AddFlags(uniqueId, MessageFlags.Seen, true);
}
client.Disconnect(true);
}
return messages;
}
}
Usage
[Test]
public void GetAllEmails()
{
var mailRepository = new MailRepository("imap.gmail.com", 993, true, "YOUREMAILHERE#gmail.com", "YOURPASSWORDHERE");
var allEmails = mailRepository.GetAllMails();
foreach(var email in allEmails)
{
Console.WriteLine(email);
}
Assert.IsTrue(allEmails.ToList().Any());
}
You don't need any extra 3rd Party Libraries if a summary of the 20 most recent emails is sufficient for you. You can read the data from API that Gmail has provided here: https://mail.google.com/mail/feed/atom
The response in XML format can be handled by the code below:
try {
const string emailAddress = "YourEmail";
// App Password, not password
// See: https://support.google.com/accounts/answer/185833?hl=en
const string appPassword = "YourAppPassword";
string response;
string title;
string summary;
XmlDocument xmlDocument = new XmlDocument();
HttpClient httpClient = new HttpClient();
// Logging in Gmail server to get data
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{emailAddress}:{appPassword}")));
// Reading data and converting to string
response = await httpClient.GetStringAsync(#"https://mail.google.com/mail/feed/atom");
// Remove XML namespace to simplify parsing/selecting nodes
response = response.Replace(#"<feed version=""0.3"" xmlns=""http://purl.org/atom/ns#"">", #"<feed>");
// Loading into an XML so we can get information easily
xmlDocument.LoadXml(response);
// Amount of emails
string nr = xmlDocument.SelectSingleNode(#"/feed/fullcount").InnerText;
// Reading the title and the summary for every email
foreach (XmlNode node in xmlDocument.SelectNodes(#"/feed/entry")) {
title = node.SelectSingleNode("title").InnerText;
summary = node.SelectSingleNode("summary").InnerText;
Console.WriteLine($"> {title}");
Console.WriteLine($"{summary}");
Console.WriteLine();
}
} catch (Exception ex) {
MessageBox.Show($"Error retrieving mails: {ex.Message}");
}
Have you tried POP3 Email Client with full MIME Support ?
If you don't it's a very good example for you. As an alternativ;
OpenPop.NET
.NET class library in C# for communicating with POP3 servers. Easy to
use but yet powerful. Includes a robust MIME parser backed by several
hundred test cases. For more information, visit our project homepage.
Lumisoft
You can also try Mail.dll IMAP client.
It supports all Gmail IMAP protocol extensions:
Thread ID,
Message ID,
Labels,
Localized folder names,
Google search syntax
OAuth authentication.
Please note that Mail.dll is a commercial product, I've developed.
Related
How to send existing email from inbox to other email address using graph API c#
I have tried sending the existing email but Im not able to send it.
It is throwing error -Code: generalException Message: Unexpected exception returned from the service.
Below is the code snippet:
internal static async Task<System.Collections.Generic.IEnumerable<Message>> Sendemail(List<MM_Mailbox_Forward> toAddress, string smptpadd)
{
string from ="xyz#gmail.com"``your text``
var Toptenmessages1 = graphClient.Users["xyz#gmail.com"].MailFolders["Inbox"].Messages.Request().Top(1);
var task = System.Threading.Tasks.Task.Run(async () => await Toptenmessages1.GetAsync());
var authResult2 = task.Result;
foreach (var message in authResult2)
{
try
{
var messages = new Message
{
Subject = message.Subject,
Body = message.Body,
ToRecipients = new List<Recipient>()
{
new Recipient
{
EmailAddress = new EmailAddress
{
Address = "abc#gmail.com"
}
}
},
};
await graphClient
.Users[from]
.SendMail(messages, true)
.Request()
.PostAsync();
}
}
catch (Exception ex)
{
Console.WriteLine("error:" + ex);
}
Make sure you are not using a shared email box, you can't (by default) send email with a shared email box.
I need to send a simple email, but using authentication. Using AppPasswords is not an option.
My difficulty is to retrieve the response after sending the authorization and at what point should I generate the email. I know that to send the email I must use the password and authentication user identity.
Here's what I've done.
Credentials are read from a file
ClientSecrets varClientSecrets = new ClientSecrets();
UserCredential credential;
using (var stream =
new FileStream(pathFile, FileMode.Open, FileAccess.Read,FileShare.Read))
{
varClientSecrets = GoogleClientSecrets.FromStream(stream).Secrets;
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
varClientSecrets,
new[] { "email", "profile", "https://mail.google.com/" },
"use",
CancellationToken.None,
new FileDataStore(credPath, true)
).Result;
strCLIENT_ID = varClientSecrets.ClientId;
strCLIENT_SECRET = varClientSecrets.ClientSecret;
}
// Create Gmail API service.
var service = new GmailService(new BaseClientService.Initializer
{
HttpClientInitializer = credential,
ApplicationName = "XXX"
});
Get credential data
// Define parameters of request.
UsersResource.LabelsResource.ListRequest request = service.Users.Labels.List("me");
ConsultaWeb CW = new ConsultaWeb();
IRestResponse irrRetornoServidor = null;
strSCOPE = credential.Token.Scope;
strREDIRECT_URI = "https://accounts.google.com/o/oauth2/v2/auth";
strTokenURL = "https://www.googleapis.com/oauth2/v4/token";
/// model: {Authorization: token_type base64(userEmail:userPassword)}
strHeader = "Authorization: " + credential.Token.TokenType + " "
+ Base64.EncodeToBase64(userEmail + ":" + userPassword);
strURL = "https://accounts.google.com/o/oauth2/v2/auth"
+ "?scope=" + strSCOPE
+ "&access_type=offline"
+ "&include_granted_scopes=true"
+ "&response_type=code"
+ "&state=state_parameter_passthrough_value"
+ "&redirect_uri=" + strREDIRECT_URI
+ "&client_id=" + strCLIENT_ID;
strServidorRetorno = CW.RequisicaoWebREST(strURL, GET, JSON
, out irrRetornoServidor, "", "", "", "", strHeader);
// Return from OAuth 2.0 server
// OAuth 2.0 server responds to your application's access request
// using the URL specified in the request.
////If the user approves the access request, the response
//will contain an authorization code. If the user does not approve the request,
//the response will contain an error message.
//The authorization code or error message that is returned to the web server
//appears on the query string, as shown below:
strAuthCode = "";
Get response from server
if ((short)irrRetornoServidor.StatusCode == (short)RegrasGlobais.HTTPStatusCode.OK)
{
if (irrRetornoServidor.ResponseUri.AbsolutePath.Contains("error"))
{
// An error response:
// https://oauth2.example.com/auth?error=access_denied
return -1;
}
else if (irrRetornoServidor.ResponseUri.AbsolutePath.Contains("ServiceLogin"))
{
//An authorization code response
// https://oauth2.example.com/auth?code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7
// strAuthCode = "P7q7W91a-oMsCeLvIaQm6bTrgtp7";
// how to get the autorization response, if this open in the browser
strAuthCode = irrRetornoServidor.ResponseUri.Query.ToString();
}
}
else
{
return -1;
}
send the email
// Gmail API server address
//MailMessage msg = new MailMessage();
// build mail
thks
my problem was and how to do the authentication before sending the email. Now I got it. Thanks.
Im a little confused as to what it is you are trying to do. You say you want to send an email using the SMTP server yet you are connecting to the gmail api. Why not just send your emails via the gmail api then?
Assuming you have just gone in the wrong direction. You should know that the SmtpClient can handle the client from the google .Net client library directly. Just let it get its access token when needed.
await client.AuthenticateAsync (oauth2, CancellationToken.None);
If all you want to do is send an email from the smtp server. Try the following sample.
Full sample
using Google.Apis.Auth.OAuth2;
using Google.Apis.Util.Store;
using MailKit.Net.Smtp;
using MailKit.Security;
using MimeKit;
var to = "test#Gmail.com";
var from = "test#gmail.com";
var path = #"C:\YouTube\dev\credentials.json";
var scopes = new[] { "https://mail.google.com/" };
var credential = GoogleWebAuthorizationBroker.AuthorizeAsync(GoogleClientSecrets.FromFile(path).Secrets,
scopes,
"GmalSmtpUser",
CancellationToken.None,
new FileDataStore(Directory.GetCurrentDirectory(), true)).Result;
var message = new EmailMessage()
{
From = from,
To = to,
MessageText = "This is a test message using https://developers.google.com/gmail/imap/xoauth2-protocol",
Subject = "Testing GmailSMTP with XOauth2"
};
try
{
using (var client = new SmtpClient())
{
client.Connect("smtp.gmail.com", 465, true);
var oauth2 = new SaslMechanismOAuth2 (message.From, credential.Token.AccessToken);
await client.AuthenticateAsync (oauth2, CancellationToken.None);
client.Send(message.GetMessage());
client.Disconnect(true);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
public class EmailMessage
{
public string To { get; set; }
public string From { get; set; }
public string Subject { get; set; }
public string MessageText { get; set; }
public MimeMessage GetMessage()
{
var body = MessageText;
var message = new MimeMessage();
message.From.Add(new MailboxAddress("From a user", From));
message.To.Add(new MailboxAddress("To a user", To));
message.Subject = Subject;
message.Body = new TextPart("plain") { Text = body };
return message;
}
}
I need to remove a saved wifi profilefrom code, so that the SoftAP is enabled again. According to the ms docs, there is no way to remove a profile, only disconnect. Is this not possible?
Ms docs for wifi
https://learn.microsoft.com/en-us/uwp/api/windows.devices.wifi.wifiadapter
Device Portal API
https://learn.microsoft.com/de-ch/windows/mixed-reality/device-portal-api-reference#wifi-management
Here is my working code for disconnecting from a wifi using device portal API
// API creds
string username = "Administrator";
string password = "p#ssw0rd
// API request URIs
string apiUri = "http://192.168.1.15:8080/api/wifi/network";
// WiFi details
string wifiInterface = string.Empty;
string wifiProfile = string.Empty;
// WiFi access
WiFiAccessStatus wifiAccess = await WiFiAdapter.RequestAccessAsync();
if (wifiAccess == WiFiAccessStatus.Allowed)
{
// Get WiFi adapter
IReadOnlyList<WiFiAdapter> wifiAdapterResult = await WiFiAdapter.FindAllAdaptersAsync();
WiFiAdapter wifiAdapter = wifiAdapterResult[0];
// Get conn profile / details
ConnectionProfile profile = await wifiAdapter.NetworkAdapter.GetConnectedProfileAsync();
wifiInterface = profile.NetworkAdapter.NetworkAdapterId.ToString();
wifiProfile = profile.ProfileName;
}
// API creds
PasswordCredential credentials = new PasswordCredential("login", username, password);
// HttpClient filter
HttpBaseProtocolFilter filter = new HttpBaseProtocolFilter();
filter.CookieUsageBehavior = HttpCookieUsageBehavior.NoCookies;
filter.CacheControl.ReadBehavior = HttpCacheReadBehavior.MostRecent;
filter.CacheControl.WriteBehavior = HttpCacheWriteBehavior.NoCache;
filter.ServerCredential = credentials;
// HttpClient
HttpClient client = new HttpClient(filter);
apiUri = apiUri + "?interface=" + wifiInterface + "&op=disconnect" + "&createprofile=no";
// Request
HttpRequestMessage request = new HttpRequestMessage();
request.Method = new HttpMethod("POST");
request.RequestUri = new Uri(apiUri);
// Send request
try
{
// Response
HttpResponseMessage response = await client.SendRequestAsync(request);
// Again
if (response.Content.ToString().Contains("Authorization Required"))
{
response = await client.SendRequestAsync(request);
}
}
catch
{
// Dispose
client.Dispose();
filter.Dispose();
}
But for deleting a wifi profile, i get 404 not found back from the API. According to the API docs linked above, the request should be ok. Here is my code for deleting a wifi profile
// API creds
string username = "Administrator";
string password = "p#ssw0rd
// API request URIs
string apiUri = "http://192.168.1.15:8080/api/wifi/network";
// WiFi details
string wifiInterface = string.Empty;
string wifiProfile = string.Empty;
// WiFi access
WiFiAccessStatus wifiAccess = await WiFiAdapter.RequestAccessAsync();
if (wifiAccess == WiFiAccessStatus.Allowed)
{
// Get WiFi adapter
IReadOnlyList<WiFiAdapter> wifiAdapterResult = await WiFiAdapter.FindAllAdaptersAsync();
WiFiAdapter wifiAdapter = wifiAdapterResult[0];
// Get conn profile / details
ConnectionProfile profile = await wifiAdapter.NetworkAdapter.GetConnectedProfileAsync();
wifiInterface = profile.NetworkAdapter.NetworkAdapterId.ToString();
wifiProfile = profile.ProfileName;
}
// API creds
PasswordCredential credentials = new PasswordCredential("login", username, password);
// HttpClient filter
HttpBaseProtocolFilter filter = new HttpBaseProtocolFilter();
filter.CookieUsageBehavior = HttpCookieUsageBehavior.NoCookies;
filter.CacheControl.ReadBehavior = HttpCacheReadBehavior.MostRecent;
filter.CacheControl.WriteBehavior = HttpCacheWriteBehavior.NoCache;
filter.ServerCredential = credentials;
// HttpClient
HttpClient client = new HttpClient(filter);
apiUri = apiUri + "?interface=" + wifiInterface + "&profile=" + wifiProfile;
// Request
HttpRequestMessage request = new HttpRequestMessage();
request.Method = new HttpMethod("DELETE")
request.RequestUri = new Uri(apiUri);
// Send request
try
{
// Response
HttpResponseMessage response = await client.SendRequestAsync(request);
// Again
if (response.Content.ToString().Contains("Authorization Required"))
{
response = await client.SendRequestAsync(request);
}
}
catch
{
// Dispose
client.Dispose();
filter.Dispose();
}
Edit//
To close this problem, since build 17763, there is a new method for deleting WiFi profiles directly from code available
bool canDelete = wifiProfile.CanDelete;
if (canDelete)
{
ConnectionProfileDeleteStatus deleteStatus = await wifiProfile.TryDeleteAsync();
}
You may be able to call netsh from your program.
netsh wlan delete <profile name> should get you there.
After trying for hours, finally found a solution! For those who are interested, you have to call the "run command" API, which allows you to run certain windows commands
string deleteCommand = "netsh wlan delete profile name=*";
string cmdApi = string.Format("http://192.168.1.15:8080/api/iot/processmanagement/runcommand?command={0}&runasdefaultaccount={1}", GetBase64String(deleteCommand), GetBase64String("no"));
The really important thing to note here is that you have to encode the command as a base64 string, otherwise it won't work!
private string GetBase64String(string stringToConvert)
{
return Convert.ToBase64String(Encoding.UTF8.GetBytes(stringToConvert));
}
With this code, it is finally possible for me to delete either certain wifi profiles, or in the example case above, every saved profile.
Thanks a lot Andy for finding this. I was doing it with command line before but this does work. I added some supporting code around it to help others where I had some issues such as get, delete or post. If it works then I restart IoT to be back in Onboarding mode. Maybe someone will find this helpful.
Logging into the portal as admin may or may not be required but that is how I do it. The if (interfaceGUID != null) was assigned by a previous Api request and can be removed for testing.
private string password = "yourpassword";
private string localhost = "127.0.0.1";
private async Task DeleteProfile()
{
try
{
using (HttpClient client = new HttpClient())
{
if (interfaceGUID != null)
{
string deleteCommand = "netsh wlan delete profile name=*";
using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, string.Format("http://{0}:8080/api/iot/processmanagement/runcommand?command={1}&runasdefaultaccount={2}", localhost, Convert.ToBase64String(Encoding.UTF8.GetBytes(deleteCommand)), Convert.ToBase64String(Encoding.UTF8.GetBytes("no")))))
{
request.Headers.Authorization = CreateBasicCredentials("Administrator");
using (HttpResponseMessage response = await client.SendAsync(request))
{
if (response.IsSuccessStatusCode == true)
{
ShutdownManager.BeginShutdown(Windows.System.ShutdownKind.Restart, TimeSpan.FromSeconds(1));
}
else
{
Debug.WriteLine("Could not delete the profiles. " + response.ReasonPhrase.ToString());
}
}
}
}
client.Dispose();
}
}
catch (Exception ex)
{
Debug.WriteLine("Could not delete the profiles. " + ex.InnerException.ToString());
}
}
private AuthenticationHeaderValue CreateBasicCredentials(string userName)
{
string toEncode = userName + ":" + password;
Encoding encoding = Encoding.GetEncoding("iso-8859-1");
byte[] toBase64 = encoding.GetBytes(toEncode);
string parameter = Convert.ToBase64String(toBase64);
return new AuthenticationHeaderValue("Basic", parameter);
}
Been working on Windows device portal API recently and came across this post. The reason your code got 404 response is because in the API URI, the &profile= expects a Base64 value instead of a text string that you're using. Once you encode the profile name to Base64, it should work.
I believe this isn't explicitly stated in MS's device portal documentation, as I only discovered this by using web browser debugger to inspect Windows Device Portal web page when deleting a WIFI profile.
To close this problem, since build 17763, there is a new method for deleting WiFi profiles directly from code available
bool canDelete = wifiProfile.CanDelete;
if (canDelete)
{
ConnectionProfileDeleteStatus deleteStatus = await wifiProfile.TryDeleteAsync();
}
I want to send email using c# async/await and the program deletes the attached file from computer depending on the flag "deleteFile". Whatever the value of deleteFile flag the email is delivered successfully but when I set deleteFile to true I am getting the following exception. I have to send two separate emails and I need to delete the file only after second mail.
An unhandled exception of type 'System.IO.IOException' occurred in
mscorlib.dll
Additional information: The process cannot access the file
'C:\Uploads\TestFile.txt' because it is being used by another process.
Could you please help me to resolve the issue?
My console application code is:
using System;
namespace SendMailAsyncDemo
{
class Program
{
private static string filePath = #"C:\Uploads\";
static void Main(string[] args)
{
Sender mailSender = new Sender();
mailSender.SendEmail("myemail#gmail.com", "Async mail with attachment", "Async mail with attachment body goes here ...", filePath + "TestFile.txt", false);
mailSender.SendEmail("anotheremail#gmail.com.com", "Async mail with attachment", "Async mail with attachment body goes here ...", filePath + "TestFile.txt", true);
Console.WriteLine("Email sent successfully!!!");
Console.ReadLine();
}
}
}
I have a Sender class to send email:
using System;
using System.IO;
using System.Net.Mail;
using System.Text;
using System.Threading.Tasks;
namespace SendMailAsyncDemo
{
public class Sender
{
public void SendEmail(string toEmail, string title, string body, string attachmentPath, bool deleteFile = false)
{
Task.Factory.StartNew(() =>
{
SendEmailAsync(toEmail, title, body, attachmentPath, deleteFile);
});
}
private async void SendEmailAsync(string toEmail, string title, string body, string attachmentPath, bool deleteFile)
{
Attachment attachment = null;
try
{
// class to hold all values from the section system.net/mailSettings/smtp in app.config
MailConfiguration smtpSection = new MailConfiguration();
using (MailMessage mailMsg = new MailMessage("<" + smtpSection.FromAddress + ">", toEmail))
{
mailMsg.IsBodyHtml = true;
mailMsg.Subject = title;
mailMsg.SubjectEncoding = Encoding.UTF8;
mailMsg.Body = body;
mailMsg.BodyEncoding = Encoding.UTF8;
if (!string.IsNullOrWhiteSpace(attachmentPath) && File.Exists(attachmentPath))
{
attachment = new Attachment(attachmentPath);
mailMsg.Attachments.Add(attachment);
}
using (SmtpClient smtpClient = new SmtpClient())
{
smtpClient.Timeout = 1000000;
smtpClient.UseDefaultCredentials = false;
if (deleteFile)
{
smtpClient.SendCompleted += (s, e) =>
{
attachment.Dispose();
File.Delete(attachmentPath);
};
}
await smtpClient.SendMailAsync(mailMsg);
}
}
}
catch (Exception ex)
{
Console.WriteLine("SendEmail exception: " + ex);
}
finally
{
Console.WriteLine("SendEmail done");
}
}
}
}
As discussed in the comments, there's no simple way to fix this while maintaining this same program structure. In particular, since you've currently got two ongoing email send calls with no knowledge of each other, there's no way to determine when it's safe to perform the delete. The second Send could finish first.
Changes I would make - I'd delete SendEmail and make SendEmailAsync public and Task returning. I'd also remove the concept of deleting after:
public async Task SendEmailAsync(string toEmail, string title, string body, string attachmentPath)
{
try
{
// class to hold all values from the section system.net/mailSettings/smtp in app.config
MailConfiguration smtpSection = new MailConfiguration();
using (MailMessage mailMsg = new MailMessage("<" + smtpSection.FromAddress + ">", toEmail))
{
mailMsg.IsBodyHtml = true;
mailMsg.Subject = title;
mailMsg.SubjectEncoding = Encoding.UTF8;
mailMsg.Body = body;
mailMsg.BodyEncoding = Encoding.UTF8;
if (!string.IsNullOrWhiteSpace(attachmentPath) && File.Exists(attachmentPath))
{
Attachment attachment = new Attachment(attachmentPath);
mailMsg.Attachments.Add(attachment);
}
using (SmtpClient smtpClient = new SmtpClient())
{
smtpClient.Timeout = 1000000;
smtpClient.UseDefaultCredentials = false;
await smtpClient.SendMailAsync(mailMsg);
}
}
}
catch (Exception ex)
{
Console.WriteLine("SendEmail exception: " + ex);
}
finally
{
Console.WriteLine("SendEmail done");
}
}
I'd then change Main as follows:
static void Main(string[] args)
{
Sender mailSender = new Sender();
var send1 = mailSender.SendEmailAsync("myemail#gmail.com", "Async mail with attachment", "Async mail with attachment body goes here ...", filePath + "TestFile.txt");
var send2 = mailSender.SendEmailAsync("anotheremail#gmail.com.com", "Async mail with attachment", "Async mail with attachment body goes here ...", filePath + "TestFile.txt");
Task.WaitAll(send1,send2);
File.Delete(filePath + "TestFile.txt");
Console.WriteLine("Email sent successfully!!!");
Console.ReadLine();
}
If this wasn't Main/if you're using bleeding-edge C#, I'd make it async also and use a couple of awaits at the current point I've got the WaitAll. It's the earliest piece of code common to both sends and so the only piece of code that can actually determine when it's safe to perform the delete.
I am using SendGrid mailhelper (as part of C# SDK) to send email. I need to send to multiple users, and hence I am using Personalization.
I get an error : Bad Request
This is my code:
static async Task Execute(string sub, string body, List<Recipient> recipients)
{
string apiKey = Environment.GetEnvironmentVariable("SendGrid_ApiKey", EnvironmentVariableTarget.User);
dynamic sg = new SendGridAPIClient(apiKey);
SendGrid.Helpers.Mail.Email from = new SendGrid.Helpers.Mail.Email("test1#gmail.com");
string subject = sub;
Personalization personalization = new Personalization();
SendGrid.Helpers.Mail.Email emails = new SendGrid.Helpers.Mail.Email();
var i = 0;
foreach (var recp in recipients)
{
emails.Address = recp.Email;
emails.Name = recp.FirstName + " " + recp.LastName;
personalization.AddTo(emails);
i++;
}
SendGrid.Helpers.Mail.Email to = new SendGrid.Helpers.Mail.Email("test1#gmail.com");
Content content = new Content("text/plain", body);
Mail mail = new Mail(from, subject, to, content);
mail.AddPersonalization(personalization);
dynamic response = await sg.client.mail.send.post(requestBody: mail.Get());
}
I appreciate if someone could advise me what am I doing incorrect.
Sendgrid API responds with bad request when there are more than 1 email address that is the same in the Personalization object. Make sure all the emails are unique