How to download attachment from email sent to google group mail address - c#

Let's say my email a#company.com is in the google group which has email address group#company.com. We have a g suite service account which has enabled Google Apps Domain-wide Delegation.
We have emails send from b#companyb.com to our group email group#company.com, have subject Report from company b and attach the report in the email.
The issue is that the gmail api is able to list all messages but not able to list the attachments in each email.
Is there any way to do it?
Here's my code:
// here when I create the client, I use my email address `a#company.com`
using(var client = CreateClient())
{
UsersResource.MessagesResource.ListRequest request = client.Users.Messages.List("me");
request.Q = "from:b#compayb.com AND subject:Report from company b AND has:attachment"
// List messages.
var messageIds = request.Execute().Messages?.Select(m => m.Id) ?? new List<string>();
foreach(var mId in messageIds)
{
// https://developers.google.com/gmail/api/v1/reference/users/messages/attachments/get
Message message = client.Users.Messages.Get("me", messageId).Execute();
IList<MessagePart> parts = message.Payload.Parts;
foreach (MessagePart part in parts)
{
if (!String.IsNullOrEmpty(part.Filename))
{
String attId = part.Body.AttachmentId;
MessagePartBody attachPart = client.Users.Messages.Attachments.Get("me", messageId, attId).Execute();
// Converting from RFC 4648 base64 to base64url encoding
// see http://en.wikipedia.org/wiki/Base64#Implementations_and_history
String attachData = attachPart.Data.Replace('-', '+');
attachData = attachData.Replace('_', '/');
byte[] data = Convert.FromBase64String(attachData);
var file = new FileInfo(part.Filename);
File.WriteAllBytes(file.FullName, data);
}
}
}
}
If I forward the mail manually to the same address (so the receiver will be me), the code downloads the attachment.
I'd appreciate if you can help.

I've found the attachments are in the child MessagePart. So I wrote the recursive method to loop through all Parts to get all attachments.
// List<FileInfo> Files = new List<FileInfo>();
// client is created outside this method
private void GetAttachmentsFromParts(IList<MessagePart> parts, string messageId)
{
if (parts == null) return;
foreach (MessagePart part in parts)
{
if (!String.IsNullOrEmpty(part.Filename))
{
String attId = part.Body?.AttachmentId ?? null;
if(String.IsNullOrWhiteSpace(attId)) continue;
MessagePartBody attachPart = GmailServiceClient.Users.Messages.Attachments.Get("me", messageId, attId).Execute();
// Converting from RFC 4648 base64 to base64url encoding
// see http://en.wikipedia.org/wiki/Base64#Implementations_and_history
String attachData = attachPart.Data.Replace('-', '+');
attachData = attachData.Replace('_', '/');
byte[] data = Convert.FromBase64String(attachData);
var file = new FileInfo(part.Filename);
Files.Add(file);
File.WriteAllBytes(file.FullName, data);
}
if((part.Parts?.Count ?? 0) > 0)
GetAttachmentsFromParts(part.Parts, messageId);
}
}
All attachments will be stored in the List<FileInfo> Files

Related

Exception during delete file attached to a sent email

After successfully sending email with attachment I have to delete files I've sent as attachement.
Files are in use so I have an exception.
I've used the code of the documentation. I'm using a method to create and send the email so everything is disposed automatically after calling it.
MimeMessage eMail = new MimeMessage();
eMail.From.Add (new MailboxAddress(fromDescription, fromAddress));
foreach (string to in toAddress)
eMail.To.Add(new MailboxAddress(to));
if (ccAddress != null)
foreach (string cc in ccAddress)
eMail.Cc.Add(new MailboxAddress(cc));
if (ccnAddress != null)
foreach (string ccn in ccnAddress)
eMail.Bcc.Add(new MailboxAddress(ccn));
eMail.Subject = subject;
var Body = new TextPart("plain")
{
Text = body
};
// now create the multipart/mixed container to hold the message text and the attachment
var multipart = new Multipart("mixed");
multipart.Add(Body);
if (attachments != null)
{
foreach (string attachmentPath in attachments)
{
// create an attachment for the file located at path
var attachment = new MimePart(MimeTypes.GetMimeType(attachmentPath))
{
Content = new MimeContent(File.OpenRead(attachmentPath), ContentEncoding.Default),
ContentDisposition = new ContentDisposition(ContentDisposition.Attachment),
ContentTransferEncoding = ContentEncoding.Base64,
FileName = Path.GetFileName(attachmentPath)
};
multipart.Add(attachment);
}
}
// now set the multipart/mixed as the message body
eMail.Body = multipart;
using (var client = new SmtpClient())
{
// For demo-purposes, accept all SSL certificates (in case the server supports STARTTLS)
//client.ServerCertificateValidationCallback = (s, c, h, e) => true;
client.Connect(SmtpHost, SmtpPort, SecureSocketOptions.SslOnConnect);
// Note: since we don't have an OAuth2 token, disable
// the XOAUTH2 authentication mechanism.
//client.AuthenticationMechanisms.Remove("XOAUTH2");
// Note: only needed if the SMTP server requires authentication
client.Authenticate(SmtpUser, SmtpPassword);
client.Send(eMail);
client.Disconnect(true);
}
What's wrong? Anyoone could help me?
Thank you
You need to dispose the stream you opened for the attachment:
File.OpenRead(attachmentPath)
You could do something like this:
var streams = new List<Stream> ();
if (attachments != null) {
foreach (string attachmentPath in attachments) {
// create an attachment for the file located at path
var stream = File.OpenRead(attachmentPath);
var attachment = new MimePart(MimeTypes.GetMimeType(attachmentPath)) {
Content = new MimeContent(stream, ContentEncoding.Default),
ContentDisposition = new ContentDisposition(ContentDisposition.Attachment),
ContentTransferEncoding = ContentEncoding.Base64,
FileName = Path.GetFileName(attachmentPath)
};
multipart.Add(attachment);
streams.Add (stream);
}
}
And then, after sending the message, do this:
foreach (var stream in streams)
stream.Dispose ();

Google API - Gmail. Downloading attachments

When calling this method please help by advising what values to pass through such as GmailService? Im guessing userID is the Gmail account and the messageID( i want to download all of them) .
How can i change this to download all the attachments in the inbox.
Thank you in advance and I hope someone can help me.
Method im using is below.
public static void GetAttachments(GmailService service, String userId, String messageId, String outputDir)
{
try
{
Message message = service.Users.Messages.Get(userId, messageId).Execute();
IList<MessagePart> parts = message.Payload.Parts;
foreach (MessagePart part in parts)
{
if (!String.IsNullOrEmpty(part.Filename))
{
String attId = part.Body.AttachmentId;
MessagePartBody attachPart = service.Users.Messages.Attachments.Get(userId, messageId, attId).Execute();
// Converting from RFC 4648 base64 to base64url encoding
// see http://en.wikipedia.org/wiki/Base64#Implementations_and_history
String attachData = attachPart.Data.Replace('-', '+');
attachData = attachData.Replace('_', '/');
byte[] data = Convert.FromBase64String(attachData);
File.WriteAllBytes(Path.Combine(outputDir, part.Filename), data);
}
}
}
catch (Exception e)
{
Console.WriteLine("An error occurred: " + e.Message);
}
}
I think the answer to your question can be solved using Get in PostMan
In your header in PostMan, use key as Authorization and pass your token generated to it as value
or goto Authorization and pick bearer token and pass your token.
Note that messageId is the Id in string of the message you are trying to fetch and {userId} =me or user according to google and I believe you can fetch Attachment Id by the method you use above.
https://www.googleapis.com/gmail/v1/users/me/messages/messageId/attachments/id
public async Task<TResult> GetGmailInboxAttachmentById<TResult>(string messageId, string token, string id, string attachId)
{
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var url = "https://www.googleapis.com/gmail/v1/users/me/messages/" + messageId + "/" + id + "/attachments" + "/" + attachId;
HttpResponseMessage response = await httpClient.GetAsync(url);
return await response.Content.ReadAsAsync<TResult>();
}
}
foreach (var part in inbox.Payload.Parts)
{
if (!String.IsNullOrEmpty(part.Filename))
{
var attachId = part.Body.AttachmentId;
var attach = _gmailService.GetGmailInboxAttachmentById<MessagePartBody>(id, token, part.PartId, attachId).Result;
// Converting from RFC 4648 base64 to base64url encoding
// see http://en.wikipedia.org/wiki/Base64#Implementations_and_history
string attachData = attach.Data.Replace('_', '+');
attachData = attachData.Replace('_', '/');
byte[] data = Convert.FromBase64String(attachData);
string file = Convert.ToBase64String(data);
GetAttach.Add(file);
}
}
Hope this solve it for you because I solved it with this method
I know, it's could be an off-topic, but using PostMan in case when programmer use Google API is little but contraproductive :) APIs also have attachments.Get Method, and you will not need to call HTTP request and allow non OAuth 2.0 authentication especially when you're using multi-phase authentication.
Here is example:
https://developers.google.com/gmail/api/v1/reference/users/messages/attachments/get
foreach (var part in m.Payload.Parts)
{
if (!string.IsNullOrEmpty(part.Filename))
{
var attachId = part.Body.AttachmentId;
MessagePartBody attachPart = service.Users.Messages.Attachments.Get(userId,
message.Id,
attachId).Execute();
byte[] data = GetBytesFromPart(attachPart.Data);
File.WriteAllBytes(Path.Combine(#"c:\teste\",
$"{DateTime.Now:yyyyMMddHHmmss}-{part.Filename}"), data);
}
}
private static string DecodedString(string messagePart)
{
try
{
var data = GetBytesFromPart(messagePart);
string decodedString = Encoding.UTF8.GetString(data);
return decodedString;
}
catch (System.Exception e)
{
// ignored
return string.Empty;
}
}
private static byte[] GetBytesFromPart(string messagePart)
{
var attachData = messagePart.Replace('-', '+');
attachData = attachData.Replace('_', '/');
byte[] data = Convert.FromBase64String(attachData);
return data;
}

Call WebAPI method from MVC method passing Mail Message

I have a method in my MVC Controller which I am trying to call an API from in order to send an email (The Mail Message is generated in the MVC Method)
Create Mail message is as follows and this works fine.
public static MailMessage CreateMailMessage(string from,
string to,
string cc,
string bcc,
string subject,
string body,
List<Attachment> attachments,
string differentServer,
bool eatError)
{
MailMessage mm = new MailMessage();
mm.From = new MailAddress(from);
mm.Subject = subject;
mm.Body = body;
mm.IsBodyHtml = true;
//send to multiple addresses separated by semi-colon or comma
if (!string.IsNullOrEmpty(to))
{
var toAddresses = to.Split(new char[] { ';', ',' });
foreach (string toAddress in toAddresses)
{
if (!string.IsNullOrWhiteSpace(toAddress))
mm.To.Add(toAddress);
}
}
if (!string.IsNullOrEmpty(cc))
{
mm.CC.Add(cc);
}
if (!string.IsNullOrEmpty(bcc))
{
mm.Bcc.Add(bcc);
}
if (attachments != null)
{
foreach (Attachment attachment in attachments)
{
mm.Attachments.Add(attachment);
}
}
return mm;
}
However in order to send the email I need to write and call a WebAPI method - sending the email is fine - just not sure how to post the mail message and some other properties to a WebAPI Method?
So My WebAPI method at the minute is like:
/// <summary>
/// Method to email the Report
/// </summary>
/// <returns></returns>
[HttpPost]
[Route("api/Document/EmailReport/")]
public HttpResponseMessage EmailFarmFeatures([FromBody]MailMessage email)
{
return Request.CreateResponse(HttpStatusCode.OK);
}
I am attempting to call this WebAPI from MVC Method as below:
private void EmailReport(string reportName, byte[] bytes)
{
ContentType ct = new ContentType(MediaTypeNames.Application.Octet);
var attachments = new List<Attachment>();
var attach = new Attachment(new MemoryStream(bytes), ct);
attach.ContentDisposition.FileName = reportName;
attachments.Add(attach);
string strFrom = ConfigurationManager.AppSettings["FromEmail"];
string strTo = ConfigurationManager.AppSettings["ToEmail"];
string subject = string.Format("Customer Report - {0}", customerId);
string body = string.Format("Report for Customer {0} attached.", customerId);
string mailServer = ConfigurationManager.AppSettings["SmtpServer"];
MailMessage message = EmailHelper.CreateMailMessage(strFrom, strTo, "", "", subject, body, attachments, mailServer, false);
using (var client = new HttpClient())
{
var requestBody = JsonConvert.SerializeObject(message);
var postRequest = new StringContent(requestBody, Encoding.UTF8, "application/json");
var response = client.PostAsync("http://localhost/myWS/api/Document/EmailReport/", postRequest).GetAwaiter().GetResult();
if (response.StatusCode != HttpStatusCode.OK)
{
throw new Exception("Error occured emailing report");
}
}
}
I am currently getting an error on this line:
var requestBody = JsonConvert.SerializeObject(message);
[InvalidOperationException: Timeouts are not supported on this stream.]
System.IO.Stream.get_ReadTimeout() +57
GetReadTimeout(Object ) +81
Newtonsoft.Json.Serialization.DynamicValueProvider.GetValue(Object target) +114
[JsonSerializationException: Error getting value from 'ReadTimeout' on 'System.IO.MemoryStream'.]
Newtonsoft.Json.Serialization.DynamicValueProvider.GetValue(Object target) +274
I believe it is something to do with attemptiong to seriliaze the memory stream attachment though I am not sure what the fix is to make sure this attachment gets included on the POST Request to the API Method
MailMessage isn't Serializable Just create a Separate regular object which represents a mail message.
Have a look at this Thread I guess it's pretty much what you need:
(https://discuss.hangfire.io/t/help-sending-email-mailmessage-deserialize-problem/354/5)[https://discuss.hangfire.io/t/help-sending-email-mailmessage-deserialize-problem/354/5]

SendGrid email using MailHelper

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

Reading emails from Gmail in C#

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.

Categories

Resources