I am having the hardest time figuring out how to create a pdf and attach it to an automated email. I thought it wouldn't be that hard but have quickly found out that it is much more complex than I imagined. Anyways, what I am doing is I have an order form, and when the customer fills out an order and submits it, I want it to generate a PDF for that order and attach it to an automated confirmation email. I am currently using Rotativa to generate the pdf and if I just ran the action result for generating it, it works perfectly fine which looks something like this:
public ActionResult ProcessOrder(int? id)
{
string orderNum = "FulfillmentOrder" + orderDetail.OrderID + ".pdf";
var pdf = new ActionAsPdf("OrderReportPDF", new { id = orderDetail.OrderID }) { FileName = orderNum };
return pdf;
}
When I added the function to send an automated email after the order form was submitted, that works perfectly fine with just the email by itself. But I can't seem to figure out how to generate the pdf and attach it to the email. The report view that gets the data from the order form is called "OrderReportPDF". My order form is called "Checkout", but the action result I use for this is called "Process Order". I've taken out the code in this function that is for the order form as it is not applicable. The section of my code for sending the email after the form is submitted is:
public ActionResult ProcessOrder(int? id)
{
//Send a confirmation email
var msgTitle = "Order Confirmation #" + orderDetail.OrderID;
var recipient = JohnDoe;
var fileAttach = //This is where I can't figure out how to attach//
var fileList = new string[] { fileAttach };
var errorMessage = "";
var OrderConfirmation = "Your order has been placed and is pending review." +
" Attached is a copy of your order.";
try
{
// Initialize WebMail helper
WebMail.SmtpServer = "abc.net";
WebMail.SmtpPort = 555;
WebMail.UserName = recipient;
WebMail.Password = "12345";
WebMail.From = "orders#samplecode.com";
// Send email
WebMail.Send(to: "orders#samplecode.com",
subject: msgTitle,
body: OrderConfirmation,
filesToAttach: fileList
);
}
catch (Exception ex)
{
errorMessage = ex.Message;
}
//5. Remove item cart session
Session.Remove(strCart);
return View("OrderSuccess");
}
I have been able to find very few articles on this issue using Rotativa and the ones I have found don't work for what I'm doing. Maybe Rotativa won't work for this, I'm hoping it does because I've designed my pdf report it generates from it, and not sure if doing it another way will screw up my formatting of it or not. As you can see at the bottom of the code, I return the view to an "OrderSuccess" page. When I try to implement the first code into the second code, it won't let me "return pdf" to execute the ActionAsPdf that generates the pdf when I do the "return View("OrderSuccess")". It only lets me do one or the other. If anybody knows how I can accomplish this, I need some help. I appreciate any suggestions or feedback. I'm pretty new to this so please be patient with me if I don't understand something.
Here is the updated code that fixed my problem and created the pdf, then attached it to an automated email once the order form was submitted:
public ActionResult ProcessOrder(int? id)
{
//1. Generate pdf file of order placed
string orderNum = "FulfillmentOrder" + orderDetail.OrderID + ".pdf";
var actionResult = new Rotativa.ActionAsPdf("OrderReportPDF", new { id = orderDetail.OrderID }) { FileName = orderNum };
var PdfAsBytes = actionResult.BuildFile(this.ControllerContext);
//2. Send confirmation email
var msgTitle = "Order Confirmation #" + orderDetail.OrderID;
var OrderConfirmation = "Your order has been placed and is pending review.<br />" +
" Attached is a copy of your order." +
using (MailMessage mail = new MailMessage())
{
mail.From = new MailAddress("orders#samplecode.com");
mail.To.Add("orders#samplecode.com");
mail.Subject = msgTitle;
mail.Body = OrderConfirmation;
mail.IsBodyHtml = true;
//STREAM THE CONVERTED BYTES AS ATTACHMENT HERE
mail.Attachments.Add(new Attachment(new MemoryStream(PdfAsBytes), orderNum));
using (SmtpClient smtp = new SmtpClient("abc.net", 555))
{
smtp.Credentials = new NetworkCredential("orders#samplecode.com", "password!");
smtp.EnableSsl = true;
smtp.Send(mail);
}
}
//3. Remove item cart session
Session.Remove(strCart);
return View("OrderSuccess");
}
Thank you again to the people that helped and got me pointed in the right direction! Also, a quick shout out to #Drewskis who posted this answer in convert Rotativa.ViewAsPdf to System.Mail.Attachment where I was able to use it to solve my issue!
So i need to get informations such as :
Sender mail address (to later get his Domain profile informations)
Mail subject (which should be the "filepath" minus the "msg" extension anyway).
And then, replying to it just like i would push "ReplyAll" button in Outlook. So the reply needs to get the usual Headers such as "From : ....", "To : ...", "Cc : ..." and so on.
All i need is to change its subject, and delete the address depending on the "FromAddress" of the user that will push the button
I've read a bit here and there, and people are talking about a MailItem, but there's no informations about HOW to get this item or how to build it from a .msg file.
What i have to do comes after a specific user action.
The user is supposed to Drag&Drop the mail into a panel, from there i get its local path.
Thanks for your time !
Edit#1
I managed to find out to get informations and to set a .msg file to a MailItem :
Outlook.Application appOutlook = new Outlook.Application();
var email = (Outlook.MailItem)appOutlook.Session.OpenSharedItem(filepath);
string getCC = "";
string getFrom = ""; // From is never null
string getTo = "";
string getSubject = "";
bool lengthCC = email.CC.HasValue();
bool lengthTo = email.To.HasValue();
bool lengthSubject = email.Subject.HasValue();
if (lengthCC)
{
getCC = email.CC.ToString();
}
// and so on...
//
// Display it in MessageBox to confirm test succeeded :
MessageBox.Show("CC : " + getCC +
"\nFrom : " + getFrom +
"\nTo : " + getTo +
"\nSubject : " + getSubject);
email.Close(Outlook.OlInspectorClose.olDiscard);
Now i just need to build the ReplyAll Body and add headers manually myself i guess...
Edit#2
No need to rewrite Headers apparently, doing so :
Outlook._MailItem reply = email.ReplyAll();
reply.To = getFrom;
reply.CC = getCC;
reply.Body = "SomeReplyMessage" + reply.Body;
reply.Send();
Marshal.ReleaseComObject(appOutlook);
Marshal.ReleaseComObject(email);
Marshal.ReleaseComObject(reply);
But it erased the separator above the original message, i'll find a way to re-add it !!!
Edit#3
And there it is, the so-called "separator" wasn't displaying, because i wasn't re-stating an HTML Body !
So, to keep it you can do this :
reply.BodyFormat = Outlook.OlBodyFormat.olFormatHTML;
string ReplyMessageBody = String.Format("AddSome<br>HTMLCode<br>ThereAndHere<br>ButFinishWith : BodyTag</body>");
reply.HTMLBody = ReplyMessageBody + reply.HTMLBody;
Or simpler if you don't need your reply to be an HTML one :
reply.BodyFormat = Outlook.OlBodyFormat.olFormatHTML;
reply.HTMLBody = "AddSomeReplyMessage" + reply.HTMLBody;
Outlook does not work directly wit MSG files - when you call CreateFromTemplate or even OpenSharedItem, Outlook creates a new item in its default store and imports the MSG or OFT file. It does not expose anything that would you let you figure out that the message came from a file; the item is indistinguishable from an item created directly using CreateItem or MAPIFolder.Items.Add.
See edits on original question for answer !
I am editing an email and send it to the recipient. But i also want to save the original mail in the sent folder. But if i move the mailobject to the folder the mail is still editable.
This is how i move the mail:
private void CopyMailToSent(Outlook.MailItem originalMail)
{
var folder = originalMail.SaveSentMessageFolder;
originalMail.Move(folder);
}
Can i set the mailobject to readonly or faking the send?
Firstly, Outlook Object Model would not let you set the MailItem.Sent property at all. On the MAPI level, the MSGFLAG_UNSENT bit in the PR_MESSAGE_FLAGS property can only be set before the message is saved for the very first time.
The only OOM workaround I am aware of is to create a post item (it is created in the sent state), set its message class to "IPM.Note", save it, release it, reopen by the entry id (it will be now MailItem in the sent state), reset the icon using PropertyAccessor, set some sender properties (OOM won't let you set all of them).
If using Redemption (I am its author) is an option, it will let you set the Sent property as well as the sender related properties, plus add recipients without having to resolve them.
Set MySession = CreateObject("Redemption.RDOSession")
MySession.MAPIOBJECT = Application.Session.MAPIOBJECT
Set folder = MySession.GetDefaultFolder(olFolderSentMail)
Set msg = folder.Items.Add("IPM.Note")
msg.Sent = True
msg.Recipients.AddEx "Joe The User", "joe#domain.demo", "SMTP", olTo
msg.Sender = MySession.CurrentUser
msg.SentOnBehalfOf = MySession.CurrentUser
msg.subject = "Test sent message"
msg.Body = "test body"
msg.UnRead = false
msg.SentOn = Now
msg.ReceivedTime = Now
msg.Save
I couldn't solve the problem but i did workaround which works fine for me.
I hope it's ok to post this here even it's not right the solution. If not, sorry i will delete it.
My workaround is saving the orignal mail as ".msg" file and then add it to the mail in the sent folder.
Then it looks like this:
This is the code:
private void SendMail(Outlook.Mailitem mail)
{
mail.SaveAs(tempDirectory + #"originalMail.msg");
var folder = mail.SaveSentMessageFolder;
ChangeMailSubject(mail);
ChangeMailText(mail);
mail.Send();
folder.Items.ItemAdd += new Outlook.ItemsEvents_ItemAddEventHandler((sender) => AttachOriginalMail(sender);
}
private void AttachOriginalMail(object sender)
{
var mail = (Outlook.MailItem) sender;
mail.Attachments.Add(tempDirectory + #"originalMail.msg");
mail.Save();
}
If create the body property as
System.Net.Mail.MailMessage message = new System.Net.Mail.MailMessage();
message.Body ="First Line \n second line";
I also tried
message.Body ="First Line" + system.environment + "second line";
Both of these were ignored when I received the message (using outlook).
Any ideas on how to get mutliple lines? I am trying to avoid html encoding so that the email will play nicer with spam filters.
thanks
As per the comment by drris, if IsBodyHtml is set to true then a standard newline could potentially be ignored by design, I know you mention avoiding HTML but try using <br /> instead, even if just to see if this 'solves' the problem - then you can rule out by what you know:
var message = new System.Net.Mail.MailMessage();
message.Body = "First Line <br /> second line";
You may also just try setting IsBodyHtml to false and determining if newlines work in that instance, although, unless you set it to true explicitly I'm pretty sure it defaults to false anyway.
Also as a side note, avoiding HTML in emails is not necessarily any aid in getting the message through spam filters, AFAIK - if anything, the most you do by this is ensure cross-mail-client compatibility in terms of layout. To 'play nice' with spam filters, a number of other things ought to be taken into account; even so much as the subject and content of the mail, who the mail is sent from and where and do they match et cetera. An email simply won't be discriminated against because it is marked up with HTML.
In case you dont need the message body in html, turn it off:
message.IsBodyHtml = false;
then use e.g:
message.Body = "First line" + Environment.NewLine +
"Second line";
but if you need to have it in html for some reason, use the html-tag:
message.Body = "First line <br /> Second line";
Beginning each new line with two white spaces will avoid the auto-remove perpetrated by Outlook.
var lineString = " line 1\r\n";
linestring += " line 2";
Will correctly display:
line 1
line 2
It's a little clumsy feeling to use, but it does the job without a lot of extra effort being spent on it.
I usually like a StringBuilder when I'm working with MailMessage. Adding new lines is easy (via the AppendLine method), and you can simply set the Message's Body equal to StringBuilder.ToString() (... for the instance of StringBuilder).
StringBuilder result = new StringBuilder("my content here...");
result.AppendLine(); // break line
You need to enable IsBodyHTML
message.IsBodyHtml = true; //This will enable using HTML elements in email body
message.Body ="First Line <br /> second line";
The key to this is when you said
using Outlook.
I have had the same problem with perfectly formatted text body e-mails. It's Outlook that make trash out of it. Occasionally it is kind enough to tell you that "extra line breaks were removed". Usually it just does what it wants and makes you look stupid.
So I put in a terse body and put my nice formatted text in an attachement. You can either do that or format the body in HTML.
Try this
IsBodyHtml = false,
BodyEncoding = Encoding.UTF8,
BodyTransferEncoding = System.Net.Mime.TransferEncoding.EightBit
If you wish to stick to using \r\n
I managed to get mine working after trying for one whole day!
Try using the verbatim operator "#" before your message:
message.Body =
#"
FirstLine
SecondLine
"
Consider that also the distance of the text from the left margin affects on the real distance from the email body left margin..
Try using a StringBuilder object and use the appendline method. That might work.
Sometimes you don't want to create a html e-mail.
I solved the problem this way :
Replace \n by \t\n
The tab will not be shown, but the newline will work.
Today I found the same issue on a Error reporting app. I don't want to resort to HTML, to allow outlook to display the messages I had to do (assuming StringBuilder sb):
sb.Append(" \r\n\r\n").Append("Exception Time:" + DateTime.UtcNow.ToString());
I realise this may have been answered before. However, i had this issue this morning with Environment.Newline not being preserved in the email body.
The following is a full (Now Working with Environment.NewLine being preserved) method i use for sending an email through my program.(The Modules.MessageUpdate portion can be skipped as this just writes to a log file i have.) This is located on the main page of my WinForms program.
private void MasterMail(string MailContents)
{
Modules.MessageUpdate(this, ObjApp, EH, 3, 25, "", "", "", 0, 0, 0, 0, "Master Email - MasterMail Called.", "N", MainTxtDict, MessageResourcesTxtDict);
Outlook.Application OApp = new Outlook.Application();
//Location of email template to use. Outlook wont include my Signature through this automation so template contains only that.
string Temp = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + ResourceDetails.DictionaryResources("SigTempEmail", MainTxtDict);
Outlook.Folder folder = OApp.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderDrafts) as Outlook.Folder;
//create the email object.
Outlook.MailItem TestEmail = OApp.CreateItemFromTemplate(Temp, folder) as Outlook.MailItem;
//Set subject line.
TestEmail.Subject = "Automation Results";
//Create Recipients object.
Outlook.Recipients oRecips = (Outlook.Recipients)TestEmail.Recipients;
//Set and check email addresses to send to.
Outlook.Recipient oRecip = (Outlook.Recipient)oRecips.Add("EmailAddressToSendTo");
oRecip.Resolve();
//Set the body of the email. (.HTMLBody for HTML Emails. .Body will preserve "Environment.NewLine")
TestEmail.Body = MailContents + TestEmail.Body;
try
{
//If outlook is not open, Open it.
Process[] pName = Process.GetProcessesByName("OUTLOOK.EXE");
if (pName.Length == 0)
{
System.Diagnostics.Process.Start(#"C:\Program Files\Microsoft Office\root\Office16\OUTLOOK.EXE");
}
//Send email
TestEmail.Send();
//Update logfile - Success.
Modules.MessageUpdate(this, ObjApp, EH, 1, 17, "", "", "", 0, 0, 0, 0, "Master Email sent.", "Y", MainTxtDict, MessageResourcesTxtDict);
}
catch (Exception E)
{
//Update LogFile - Fail.
Modules.MessageUpdate(this, ObjApp, EH, 5, 4, "", "", "", 0, 0, 0, 0, "Master Email - Error Occurred. System says: " + E.Message, "Y", MainTxtDict, MessageResourcesTxtDict);
}
finally
{
if (OApp != null)
{
OApp = null;
}
if (folder != null)
{
folder = null;
}
if (TestEmail != null)
{
TestEmail = null;
}
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
You can add multiple recipients by either including a "; " between email addresses manually, or in one of my other methods i populate from a Txt file into a dictionary and use that to create the recipients email addresses using the following snippet.
foreach (KeyValuePair<string, string> kvp in EmailDict)
{
Outlook.Recipient oRecip = (Outlook.Recipient)oRecips.Add(kvp.Value);
RecipientList += string.Format("{0}; ", kvp.Value);
oRecip.Resolve();
}
I hope at least some of this helps someone.
Adding . before \r\n makes it work if the original string before \r\n has no .
Other characters may work. I didn't try.
With or without the three lines including IsBodyHtml, not a matter.
Something that worked for me was simply separating data with a :
message.Body = FirstLine + ":" + SecondLine;
I hope this helps
I'm putting together a script to send out a preview html mail to hotmail etc, and I utlising an existing script - I'm new to c# so this is as much a learning expereince forme too.
The problem is I have made a few ammends, and it's fine up to the point I drop HTML source into the comments box - it then fails the sending.
I have looked into it and tried changing the new MailMessage(); to new MailDefinition(); and also adding in faqMsg.IsBodyHtml = true; but it's still failing. it's fine just on regular text. Any thoughts what I need to look into ?
SmtpClient smtpCli = new SmtpClient("localhost");
MailMessage faqMsg = new MailMessage();
//MailDefinition faqMsg = new MailDefinition();
//faqMsg.BodyFileName = "email.htm";
//faqMsg.IsBodyHtml = true;
faqMsg.From = new MailAddress("");
faqMsg.To.Add("");
faqMsg.Subject = "Mail test :" + subject.Text;
//Plain Text part
AlternateView plainView = AlternateView.CreateAlternateViewFromString("Enquiry Type:" + enquirytype.SelectedValue + "\r\nFrom:" + "\r\nEmail:" + email.Text + "\r\n\r\nComments\r\n" + comments.Text + "\r\n[EOF]", null, "text/plain");
//HTML part
AlternateView htmlView = AlternateView.CreateAlternateViewFromString("<strong>Enquiry Type:</strong>" + enquirytype.SelectedValue + "<br><br><strong>Email:</strong>" + email.Text + "<br><br><hr><br><strong>Comments</strong><br>" + HttpUtility.HtmlEncode(comments.Text) + "<br>EOF", null, "text/html");
faqMsg.AlternateViews.Add(plainView);
faqMsg.AlternateViews.Add(htmlView);
//Add Header Markers
faqMsg.Headers.Add("X-Company", "");
faqMsg.Headers.Add("X-Location", "");
faqMsg.Headers.Add("X-Brand", "");
smtpCli.Send(faqMsg);
lbReport.Text = "Your Message was Sent";
//Response.Redirect("email-thanks.aspx");
Hey, what error are you getting?
Looking at the code, you may need to add an # symbol before any strings with the \ inside. But that's a guess without the error.
Without the exact exception text you are seeing, Im going to take a stab at this.
This doesn't sound like an email error.
this sounds like the generic error that won't allow you to enter HTML content into a textbox.
Be sure VaildateRequest=false in the #Page attribute at the top of the page.