I have been making my own mail client recently and added a receive option, I used mimekit and mailkit as plugins and was able to download most of my mails with a reader that is supposed to show content(right now it shows subject, to, from, date)
The way I downloaded the subject, to, ... is msg.envelope.subject, msg.envelope.to
But I cannot download the body like this :( when doing either msg.body, msg.textbody, msg.bodyparts, ... they all result in NOTHING, the place where it should be is just empty, I can't get it downloaded :(
Can anyone help me?
There are 2 ways to get the message body:
1. Download the Whole Message
This method is probably the easiest way.
To do this, all you need to do is call:
var message = folder.GetMessage (uid);
or
var message = folder.GetMessage (index);
I would recommend always using the UniqueId of the message. Since you are already using the Fetch method, all you need to do to make sure that you have the UniqueId of the message is to include the MessageSummaryItems.UniqueId in your fetch request:
var messages = folder.Fetch (0, -1, MessageSummaryItems.UniqueId |
MessageSummaryItems.Envelope | ...);
Once you have the message, you can do whatever you want with it.
For rendering the message, I would recommend taking a look at the MessageReader sample included in the MimeKit GitHub repository.
It will show you how to properly render a MimeMessage.
2. Download Only What You Need
This method is a bit harder but can be more efficient as far as network bandwidth usage goes.
The first thing you need to do is to make sure to include the MessageSummaryItems.BodyStructure bit flag in the Fetch method. For example:
var messages = folder.Fetch (0, -1, MessageSummaryItems.Envelope |
MessageSummaryItems.BodyStructure);
(You'll probably want other fields, but that's just an example to show you how to bitwise-or flags together to request multiple message summary items).
By requesting the BodyStructure of the messages, you'll be able to make use of the msg.Body property.
Each msg.Body will be a BodyPart object which is an abstract class. The 2 main subclasses are BodyPartMultipart and BodyPartBasic. You can use the as cast or the is keyword to figure out which one it is:
var multipart = msg.Body as BodyPartMultipart;
if (multipart != null) {
// the top-level body part is a multi-part
} else {
// the body is a basic singleton part
}
This is how you would iterate over the subparts of a BodyPartMultipart:
foreach (var part in multipart.BodyParts) {
// each part will either be a BodyPartMultipart
// or a BodyPartBasic, just like before...
}
There are also 2 subclasses of BodyPartBasic which are: BodyPartText and BodyPartMessage. A BodyPartText is a textual-based MIME part (meaning it has a MIME-type of text/*) whereas a BodyPartMessage is an embedded message (and will have a MIME-type of message/rfc822).
Since MIME is recursive, you'll need to implement a recursive function to walk the MIME tree structure to find whatever MIME part you are looking for.
For your convenience, the TextBody and HtmlBody properties on the IMessageSummary interface will locate and return the text/plain and text/html body parts, respectively.
It should be noted, however, that these properties only work in cases where the structure of the message follows the standard convention (notice I said convention, there is no formal standard dictating the location of the message text within a MIME hierarchy).
It should also be noted that if your mail client will be rendering the HTML body, the HTML body part may be part of a group of related MIME parts (i.e. a child of a multipart/related), but the HtmlBody property will not be able to return that and so implementing your own recursive logic will be a better option.
For an example of how to do this, check out the ImapClientDemo sample in the MailKit GitHub repository. The logic currently resides in the MainWindow.cs code.
Related
I have a C# REST API with an upload endpoint that has the sole purpose to process a binary file and add its metadata (as an Attachment model) to a List<Attachment> property of a different entity.
When I call the endpoint from my web application in a sequential manner like below (pseudo code), the endpoint does as intended and processes each binary file and adds a single Attachment to the provided entity.
const attachments = [Attachment, Attachment, Attachment];
for(const attachment of attachments) {
await this.api.upload(attachment);
}
But when I try to upload the attachments in a parallel manner like below (pseudo code), each binary file gets processed properly, but only one Attachment metadata object gets added to the entity.
const attachments = [Attachment, Attachment, Attachment];
const requests = attachments.map((a) => this.api.upload(a));
await Promise.all(requests);
The endpoint basically does the following (simplified):
var attachment = new Attachment()
{
// Metadata is collected from the binary (FormFile)
};
using (var session = Store.OpenAsyncSession())
{
var entity = await session.LoadAsync<Entity>(entityId);
entity.Attachments.Add(attachment);
await session.StoreAsync(entity);
await session.SaveChangesAsync();
};
I suspect that the problem is that the endpoint is called at the same time. Both request open (at the same time) a database session and query the entity into memory. They each add the Attachment to the entity and update it in the database. The saved attachment you see in the database is from the request that finishes last, e.g. the request that takes the longest.
I've tried to recreate the issue by creating this example. When you open the link, the example runs right away. You can see the created entities on this database server.
Open the Hogwarts database and after that open the contact Harry Potter and you see two attachments added. When you open the contact Hermione Granger you only see the one attachment added (the Second.txt), although it should also have both attachments.
What is the best approach to solve this issue? I prefer not having to send the files as a batch to the endpoint. Appreciate any help!
PS: You might need to run the example manually by clicking on Run. If the database doesn't exist on the server (as the server gets emptied automatically) you can create it manually with the Hogwarts name. And because it looks like a race condition, sometimes both Attachment items are added properly. So you might need to run the example a few times.
That is a a fairly classic example of a race condition in writing to the database, you are correct.
The sequence of event is:
Req 1 load doc Attachments = []
Req 1 load doc Attachments = []
Req 1 Attachments.Push()
Req 2 Attachments.Push()
Req 1 SaveChanges()
Req 2 SaveChanges()
The change in 5 overwrites the change in 4, so you are losing data.
There are two ways to handle this scenario. You can enable optimistic concurrency for this particular scenario, see the documentation on the topic:
https://ravendb.net/docs/article-page/4.2/csharp/client-api/session/configuration/how-to-enable-optimistic-concurrency#enabling-for-a-specific-session
Basically, you can do session.Advanced.UseOptimisticConcurrency = true; to cause the transaction to fail if the document was updated behind the scenes.
You can then retry the transaction to make it work (make sure to create a new session).
Alternatively, you can use the patching API, which will allow you to add an item to the document concurrently safely.
Here is the relevant documentation:
https://ravendb.net/docs/article-page/4.2/csharp/client-api/operations/patching/single-document#add-item-to-array
Note that there is a consideration here, you shouldn't care what the order of the operations are (because they can happen in any order).
If there is a business usecase behind the order, you probably cannot use the patch API easily and need to go with the full transaction route.
I've been trying to find an answer to this within MSDN documentation and various other resources, but am unable to find something that works.
Here is some C# code I am using:
private ExtendedPropertyDefinition SurpressAutoResponse = new ExtendedPropertyDefinition(
DefaultExtendedPropertySet.InternetHeaders,
"X-Auto-Response-Suppress",
MapiPropertyType.String); // Also tried with StringArray and Integer
private ExtendedPropertyDefinition OtherID = new ExtendedPropertyDefinition(
DefaultExtendedPropertySet.InternetHeaders,
"X-Custom-ID-Property-Example",
MapiPropertyType.String);
{ some other code that's unimportant in between }
var mm = new EmailMessage(Global.Exchange);
mm.ToRecipients.Add("me#me.com"); // example address, of course
mm.Subject = Subject.Replace('\r', ' ').Replace('\n', ' ');
mm.SetExtendedProperty(SurpressAutoResponse, "OOF, NDR"); // Also tried {"OOF", "NDR"} and -1
mm.SetExtendedProperty(OtherId, "12345-1");
mm.Body = "Hello World";
mm.Send();
When I inspect the headers for the incoming email, I see that my "OtherId" is correctly set, but the X-Auto-Response-Suppress is not set. Any ideas how I should be getting exchange to suppress these out of office and delivery failure reports?
Notes:
I am targeting an Exchange 2010_SP2 server, which should support this
References:
https://learn.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxcmail/ced68690-498a-4567-9d14-5c01f974d8b1
https://learn.microsoft.com/en-us/previous-versions/office/developer/exchange-server-2010/dd633654(v=exchg.80)
UPDATE
I decided to try testing the behavior of the email and set an auto-reply/OOF on my email account. Even though the properties of the email do not include the X-Auto-Response-Suppress header, I noticed that it indeed prevented a reply. My presumption is that the header is read on the Exchange server, which also probably processes the auto-responses instead of the client. Since the client doesn't act upon the header itself, Exchange saves some data by removing it from the original email before it's transferred. Can anyone who knows please confirm this is correct?
I have had issues using that header before as the MSDN is very vague on what all it actually does. And it is mostly only utilized by exchange servers and OOF purposes. Instead there are other headers that work better. Here is an article explaining why I think it would serve you well to use other headers. https://www.jitbit.com/maxblog/18-detecting-outlook-autoreplyout-of-office-emails-and-x-auto-response-suppress-header/
If you are only wanting to catch OOF then you can change the header to:
X-Auto-Response-Suppress:OOF
But I don't see that as a good example. Here is another thread on why this isn't always the best header to use: Detecting Outlook autoreply/out-of-office emails
I'm trying to implement punchout catalogs on our eComm site. Honestly, the documentation for cXML is a mess and all the code examples are in javascript and/or VB.Net (I use C# and would rather not have to try and translate). Does anyone out there have examples or samples of how to receive the PunchOutSetupRequest XML and then send out the PunchOutSetupResponse XML using C#? I've been unable to find anything on the interwebs (I've been looking for two days now)...
I'm hoping I can just do this inside an ActionResult (vs. a 'launch page' as suggested).
I'm a complete noob at punchouts and could really use some help here. The bosses are being pretty pushy, so any assistance would be greatly appreciated. Suggestions as to how to make this work would also be much appreciated.
I apologize to all for the vagueness of the question (request).
This isn't trivial, but this should get you started.
You'll need 3 generic handlers (.ashx): Setup, Start, and Order....
Setup and Order will receive HTTP Post with content-type of "text/xml". Look at HttpRequest.InputStream if needed to get the XML into a string. From there, look at LINQ-to-XML to dig out the data you want. Your HTTP Response to both of these will also be content-type "text/xml" and UTF8 encoded, returning the CXML as documented...use LINQ-to-XML to produce that.
The Setup handler will need to validate credentials and return a URL with a unique QueryString token pointing to the Start handler. Do not expect session persistence between Setup and Start, because they're not from the same caller. This handler will need to create an application object for the token and associated data you extracted from the cXML.
The Start handler will be called as a simple GET, and will need to match the token in the QueryString to the appropriate application object, copy that data to the session, and then do a response.redirect to whatever page in your site you want the buyer to land on.
Once they populate their cart with some things, and are ready to check out, you'll take them to a page that has an embedded form (not to be confused with an ASP.Net form that posts back to your server) and a submit button (again, not an ASP.Net button). From your Setup handler, you captured a URL to point this form's Post, and within the form you'll have a hidden input tag with the UTF8 encoded CXML Punchout Order injected as the value produced with LINQ-to-XML. Highly recommend Base64 encoding that value to avoid ASP.Net messing with the tags it contains during rendering, and naming the hidden input "cxml-base64" per the documentation. The result is the form is client-side POSTed to your customer's server instead of yours, and their server will extract the CXML Punchout Order and that ends your visitor's session.
The Order handler will receive a CXML OrderRequest and just like Setup, you'll dump that to a string and then use LINQ-to-XML to parse it and act upon it. Again you'll get credentials to verify, possibly a credit card to process, and the order items, ship-to, etc. Note that the OrderRequest may not contain all the items that were in the Punchout Order, because the system on your customer's side may remove items or even change item quantities before submitting the final OrderRequest to you. The OrderRequest could come back to you after the Punchout Order is posted to them in a matter of minutes, days, weeks, or never...don't bother storing the cart data in hopes of matching it to the order later.
Last note...the buyer may be experiencing your site in an iframe embedded in their web-based procurement UI, so design accordingly.
If you need more info, reply to this and I'll get back.
Update...Additional considerations:
Discuss with the buyer how they want fault handling to flow, particularly with orders, because you have a choice. 1) exhaustively evaluate everything in the CXML you receive and return response codes other than 200 if anything is wrong, or 2) always return a 200 Success and deal with any issues out of band or by generating a ConfirmationRequest that rejects the order. My experience is that a mix of the two works best. Certainly you should throw a non-200 if the credentials fail, but you may not want (or be able) to run a credit card or validate stock availability inline. Your buyer's system may not be able to cope with dozens of possible faults, and/or may not show your fault messages to the user for them to make corrections. I've seen systems that will flat-out discard any non-200 response code and just blindly retry the submission repeatedly on an interval for hours or days until it gives up on a sanity check, while others will handle response codes within certain ranges differently than others, for example a 4xx invokes a retry, while a 5xx is treated as fatal. Remember that Setup and Order are not coming directly from the user...their procurement system is generating those internally.
Update...answering the comment about how to test things...
You'd use the same method as you will for generating outbound ConfirmationRequest, ShipNoticeRequest, and InvoiceDetailRequest, all of which generally are produced on your side after receiving an OrderRequest from your customer's procurement system.
Start with Linq-To-XML for an example of crafting your outgoing cXML (Creating XML Trees section). Combine that example with this bit of code:
StringBuilder output = new StringBuilder();
XmlWriterSettings objXmlWriterSettings = new XmlWriterSettings();
objXmlWriterSettings.Indent = true;
objXmlWriterSettings.NewLineChars = Environment.NewLine;
objXmlWriterSettings.NewLineHandling = NewLineHandling.Replace;
objXmlWriterSettings.NewLineOnAttributes = false;
objXmlWriterSettings.Encoding = new UTF8Encoding();
using (XmlWriter objXmlWriter = XmlWriter.Create(output, objXmlWriterSettings)) {
XElement root = new XElement("Root",
new XElement("Child", "child content")
);
root.Save(objXmlWriter);
}
Console.WriteLine(output.ToString());
So at this point the StringBuilder (output) has your whole cXML, and you need to POST it someplace. Your Web Application project, started with F5 and a default.aspx page will be listening on localhost and some port (you'll see that in the URL it opens). Separately, perhaps using VS Express for Desktop, you have the above code in a console app that you can run to do the Post using something like this:
Net.HttpWebRequest objRequest = Net.WebRequest.Create("http://localhost:12345/handler.ashx");
objRequest.Method = "POST";
objRequest.UserAgent = "Some User Agent";
objRequest.ContentLength = output.Length;
objRequest.ContentType = "text/xml";
IO.StreamWriter objStreamWriter = new IO.StreamWriter(objRequest.GetRequestStream, System.Text.Encoding.ASCII);
objStreamWriter.Write(output);
objStreamWriter.Flush();
objStreamWriter.Close();
Net.WebResponse objWebResponse = objRequest.GetResponse();
XmlReaderSettings objXmlReaderSettings = new XmlReaderSettings();
objXmlReaderSettings.DtdProcessing = DtdProcessing.Ignore;
XmlReader objXmlReader = XmlReader.Create(objWebResponse.GetResponseStream, objXmlReaderSettings);
// Pipes the stream to a higher level stream reader with the required encoding format.
IO.MemoryStream objMemoryStream2 = new IO.MemoryStream();
XmlWriter objXmlWriter2 = XmlWriter.Create(objMemoryStream2, objXmlWriterSettings);
objXmlWriter2.WriteNode(objXmlReader, true);
objXmlWriter2.Flush();
objXmlWriter2.Close();
objWebResponse.Close();
// Reset current position to the beginning so we can read all below.
objMemoryStream2.Position = 0;
StreamReader objStreamReader = new StreamReader(objMemoryStream2, Encoding.UTF8);
Console.WriteLine(objStreamReader.ReadToEnd());
objStreamReader.Close();
Since your handler should be producing cXML you'll see that spat out in the console. If it pukes, you'll get a big blob of debug mess in the console, which of course will help you fix whatever is broken.
pardon the verbosity in the variable names, done to try to make things clear.
I've written a test application in C# that creates a draft message using the new Gmail API. It works fine when the message has no attachment.
I'm moving from the IMAP API and have used the MailBee.NET components with that API. The MailBee.NET components includes a class that produces an RFC 2822 message, so I've re-used this and have Base64-encoded the message and have assigned to the "Raw" property as described here:
https://developers.google.com/gmail/api/guides/drafts
MailMessage msg = new MailMessage();
msg.Subject = "test!";
msg.BodyPlainText = "Test content";
msg.Attachments.Add(#"D:\Trace.log", "Trace.log", Guid.NewGuid().ToString(), null, null, NewAttachmentOptions.Inline, MailTransferEncoding.Base64);
Message m = new Message();
m.Raw = Convert.ToBase64String(msg.GetMessageRawData());
Draft d = new Draft();
d.Message = m;
d = gs.Users.Drafts.Create(d, "me").Execute();
It works fine when no attachment is added, but fails with a 500 response when one is added:
{
"error": {
"errors": [
{
"domain": "global",
"reason": "backendError",
"message": "Backend Error"
}
],
"code": 500,
"message": "Backend Error"
}
}
Could somebody please provide an example of how to do this using the .NET API? The example on the API page is very barebones and doesn't really give much in the way of useful information and the documentation isn't great. It would probably be best to use the Message / MessagePart / MessagePartBody classes included with the .NET Client, however I can't find any clear guidance or examples on their use so don't know where to begin.
Questions:
1) Can anybody provide some example code of creating a draft message with an attachment using the classes within the .NET Client?
2) Is it possible to attach more than one file? The documentation refers to a single file throughout and the Multipart guidance refers to exactly two parts: metadata and attachment.
Providing a sample "raw" field that you're uploading would definitely be helpful to debug (either base64 encoded or just directly).
However this sounds related to:
GMail API : unable to add an attachment in a draft
also about this:
m.Raw = Convert.ToBase64String(msg.GetMessageRawData());
you want to make sure you're using "web safe" (aka "url safe") base64 encoding alphabet from https://www.rfc-editor.org/rfc/rfc4648#section-5
as it says in the docs at the URL you mentioned:
"""
Your application can create drafts using the drafts.create method. The general process is to:
Create a MIME message that complies with RFC 2822.
Convert the message to a URL-safe base64-encoded string.
Create a draft, setting the value of the drafts.message.raw field to the encoded string.
"""
Google APIs use the
Much like for the poster of the other question GmailGuy referred to, this has magically started working overnight. So it must've been a Gmail-side problem after all.
Regarding:
m.Raw = Convert.ToBase64String(msg.GetMessageRawData());
Thanks for the heads-up on this; I had actually encoded it previously but while trying 20 different things to get things working I removed it and forgot to add it back in!
Also, to confirm: yes, you're able to add more than one attachment when you use the raw message approach.
I am sending a new logon and password to a user, however when I do on a test version of our site on the internet the Spam score is 4.6 by spam assassin. Which means it gets trapped.
The Email is HTML (so the marketing dept have their nice fonts and colours) with a linked image.
The MailMessage() object does not appear to give me a lot of control over the output format of the message.
What measures could I take to lower the spam score?
I am sending using this:
/* send an email */
MailMessage msg = new MailMessage();
msg.IsBodyHtml = true;
//msg.BodyEncoding = Encoding.UTF8;
msg.To.Add(new MailAddress(sToEmail));
msg.From = new MailAddress(sFromEmail);
msg.Subject = sEmailSubject;
msg.Body = sEmailTemplate;
try
{
client.Send(msg);
}
The spam score is this:
X-Spam-Score: 4.6 (++++)
X-Spam-Report: Spam detection software report (4.6 points):
pts rule name description
---- ---------------------- --------------------------------------------------
1.8 HTML_IMAGE_ONLY_20 BODY: HTML: images with 1600-2000 bytes of words
0.0 HTML_MESSAGE BODY: HTML included in message
1.7 MIME_HTML_ONLY BODY: Message only has text/html MIME parts
1.1 HTML_MIME_NO_HTML_TAG HTML-only message, but there is no HTML tag
0.1 RDNS_NONE Delivered to trusted network by a host with no rDNS
Two solutions:
Add more content, so that the <img> is not the main part of the email - loads more content in clean text without tags. (I know it looks lame, but copyright notices, unsubscribe instructions and registration rules make a really good text padding) Add a text-only version in a new mime part. Send a properly constructed HTML which actually contains the <html> tag.
Smack marketing people with a clue-by-four many times and send text emails in text only - as $DEITY intended.
Using the AlternateView class, you can specify a text/plain body and provide an alternate html body for the marketing boys. Even if the text part only says that you should have an html enable reader, the spam filter will drop 1.8 points.
Then if you start the HTML message with a proper tag (just take a full html page), you will drop 2.8 poins.
You can also include LinkedResources so you can send the image without showing attachments, much nicer.
It's already telling you what to do, but I'll spell it out for you:
Include more text or less images.
Nothing you can do here if you want HTML. It's not weighted on the default SpamAssassin install anyway, though.
Add in a text version of the content in addition to the HTML version.
Add in the missing <html> tag
Set up reverse DNS for your outgoing mail server's IP.
Steps 3 and 4 are probably the most important to do. 1 is out of your control (marketing is in control of that). 5 would help, but it's rated fairly low.
It seems like it doesn't matter how you send the message that effects the spam score, but what the message contains.
Try different versions of the message contents and see what else can change.
1.8 points seems to be from the images. Take out the images.
How are you creating the HTML? I would look at all those factors before I would look at changing how the message is sent, because that is not a factor in spam.
1.1 HTML_MIME_NO_HTML_TAG HTML-only message, but there is no HTML tag
Well for one, you need an HTML tag around your HTML message. If you make your HTML validate, it seems like it'd lower the score a bit. That'd knock you down to 3.5 points.
Don't forget a nice friendly name in your from address too, that's making our emails get caught up in filters.
email from:
J Random Hacker <jrandomhacker#example.com>
is better than jrandomhacker#example.com
If you've simply got an html link to a picture, then it looks like spam, and people who's email clients block images by default (most online ones do) won't be able to see your message.
Rather than have one big image, try breaking it up and use html tables to lay it out. Also, make sure you set the alt attribute on the img tags.
The other thing, apart from the spam assasin score, to look at, is making sure you've set up Sender Policy Framework for the domain from which you're sending the emails. Some online email providers do not score spam on content at all, rather they use SPF and user's "reporting spam", so make sure you get this set up and working correctly before you do large broadcasts.
You might also choose to use a service such as the excellent campaign monitor instead of writing your own broadcast client. They can guide you through the process of setting up the DNS entries required for SPF, and also provide tracking of people opening the email and following links within the email.
You are answering your own question. By not sending "images with 1600-2000 bytes of words", "HTML included in message", "Message only has text/html MIME parts" or "HTML-only message, but there is no HTML tag" you will substract spam points from the formula and hence the result will be lower.
An (inferior) alternative is to ask the user to whitelist you.
I don't know much about Spam Assasin, but I've used Return Path in the past. They give a rather comprehensive view of the aspects of an email that make it look like spam.
I don't work for Return Path, btw :)
Nothing you can do here if you want HTML. It's not weighted on the default SpamAssassin install anyway, though.
Add in a text version of the content in addition to the HTML version.
Add in the missing tag
alter sol to above : Do HTML encode base64 method , not expose html headers in content can significant reduce level of spam score via filters. :)