Send email with default email client from a WPF app - c#

I am trying to find a reliable way to be able to send emails using the system default external mail client from my WPF app.
I know similar questions are already posted on stackoverflow (Open default mail client along with a attachment), but they are outdated and I couldn't find any solution which is robust enough.
My requirements:
Works from a WPF desktop app on W10
Use the external email client which is the user's default app for Email
Works with the most popular email apps (Outlook, Thunderbird,
the Mail app in Windows)
Attachments
HTML, plain text and RTF body
The email appears in the drafts folder and recognized as a new, unsent message by the clients
The things I've found so far:
mailto links: no-go because no attachment support
EML file: issues like the from field has to be explicitly set and also the message is not marked as a new unsent message by some clients even if the "X-Unsent: 1" is set
MAPI: Depricated by MS and they themselves do not recommend using it. People complaining about several bugs and issues. Also the default mail client is not recognized properly on Windows 10.
So far MAPI seems to be the best option but honestly I don't like any of them but I couldn't find any better alternative.

You can simply use Mailkit
It works from any app, which is using .NET.
It supports HTML, plain text and also RTF body
Yes, the email appears in the drafts folder and recognized as a new, you can achieve it.
It supports attachments
Usage is quite simple, I can give you an example of how I am using it in my hobby project.
I am not using attachments, but with Mailkit, it is very easy to do that.
You can check their FAQ
But anyway I will post here how I am using it.
1) First create message class, which will be responsible to handle Subject, content and mail info about the person you are sending the email.
With the simple constructor, you will seed put into your properties.
using System;
using MimeKit;
namespace SomeNamespace
{
public class Message
{
public List<MailboxAddress> To { get; set; }
public string Subject { get; set; }
public string Content { get; set; }
public Message(IEnumerable<string> to, string subject, string content)
{
To = new List<MailboxAddress>();
To.AddRange(to.Select(x => new MailboxAddress(x)));
Subject = subject;
Content = content;
}
}
}
2) You will need also your SMTP configuration class, which will have information about your email. (From where you will send mails)
using System;
namespace SomeNamespace
{
public class EmailConfiguration
{
public string SMTPFrom { get; set; }
public string SMTPHost { get; set; }
public int SMTPPort { get; set; }
public string SMTPLogin { get; set; }
public string SMTPPassword { get; set; }
}
}
3) Then you will create your simple Interface
public interface IEmailSender
{
void SendEmail(Message message);
}
4) Then you will create an implementation of your service.
using System;
using MailKit.Net.Smtp;
using MimeKit;
using SomeNamespace.Configuration;
using SomeNamespace.Services.Interfaces;
namespace SomeNamespace
{
public class EmailSender : IEmailSender
{
private readonly EmailConfiguration _emailConfig;
public EmailSender(EmailConfiguration emailConfig)
{
_emailConfig = emailConfig;
}
public void SendEmail(Message message)
{
var emailMessage = CreateEmailMessage(message);
Send(emailMessage);
}
private MimeMessage CreateEmailMessage(Message message)
{
var emailMessage = new MimeMessage();
emailMessage.From.Add(new MailboxAddress(_emailConfig.SMTPFrom));
emailMessage.To.AddRange(message.To);
emailMessage.Subject = message.Subject;
emailMessage.Body = new TextPart(MimeKit.Text.TextFormat.Text) { Text = message.Content };
return emailMessage;
}
private void Send(MimeMessage mailMessage)
{
using (var client = new SmtpClient())
{
try
{
client.Connect(_emailConfig.SMTPHost, _emailConfig.SMTPPort, true);
client.AuthenticationMechanisms.Remove("XOAUTH2");
client.Authenticate(_emailConfig.SMTPLogin, _emailConfig.SMTPPassword);
client.Send(mailMessage);
}
catch(Exception message)
{
Console.WriteLine(message);
throw;
}
finally
{
client.Disconnect(true);
client.Dispose();
}
}
}
}
}
The last step is to create your Message and EmailConfiguration models.
Create the instance of the EmailSender and send the email.
var message = new Message("whotosend#gmail.com", "Some example Subject", "Some example content");
var emailConfiguration = new EmailConfiguration()
{
SMTPFrom = "yourSmtpAdress",
SMTPHost = "yourSmtpHost",
SMTPLogin = "yourSmtpLogin",
SMTPPassword = "yourSmtpPassword",
SMTPPort = 465
};
var _emailSender = new EmailSender(emailConfiguration);
_emailSender.SendEmail(message);
Of course, you can use DI to create your service instances but it's up to you.
Stay home, stay safe, wish you happy coding.

Related

How can I set a different profile pic for different messages sent by my Slack app?

Please excuse my English. I have a Slack app that I am using to post messages. I am using a web API written in C# .NET 6.0 to post these messages.
I would like to change the profile pic based on which one of three types of content the bot is sending.
The bot used to use the Slack.Webhooks package. This lets me set up a SlackClient and SlackMessage. Then all I had to do was say client.PostAsync(message). It has a username property. But I couldn't change the username. When I changed the Username property of the SlackMessage object it still posted under the app name.
So I have started posting instead to https://slack.com/api/chat.postMessage. I am following the documentation here. First I set up a bot token for my app with the scopes chat:write and chat:write.customize, which says it allows me to "send messages as #My-SlackApp with a customized username and avatar". I also had to add the bot to my test channel.
Now when I changed the username in my model this is now working!
But I have tried changing the icon_url (I've also tried using icon_emoji which would also work). No matter what I do I only get the app's profile picture.
I am not sure if my app is a "classic slack app" (i created it today), but just in case I have also tried setting as_user=true, as_user=false and without as_user at all.
Here is the code that I am using to send the messages. What I expected to happen is that the message that got posted to slack would have the image (a google logo) as its profile pic. Instead, the message that got posted had my app's profile pic.
Any idea what I should be doing differently to be able to choose a custom avatar or emoji to display as my bot's profile pic for each message?
var slackMessage = new MySlackMessage()
{
//As_User = false,
Username = "My Custom Name",
Text = "This text needs to have a google logo!",
Icon_Url = "https://fulltimeblog.com/wp-content/uploads/2019/05/1024px-Google__G__Logo-1.png",
//Icon_Emoji = ":heart:",
Channel = "test"
};
using (HttpClient client = new HttpClient())
{
var requestMessage = new HttpRequestMessage(HttpMethod.Post, "https://slack.com/api/chat.postMessage");
requestMessage.Content = JsonContent.Create<MySlackMessage>(slackMessage);
requestMessage.Headers.Add("Authorization", $"Bearer {token}");
var response = client.Send(requestMessage);
var result = response.Content.ReadAsStream();
using (StreamReader sr = new StreamReader(result))
{
Console.WriteLine(sr.ReadToEnd());
}
}
Here's the model I'm sending through by the way.
public class MySlackMessage
{
// public bool As_User { get; set; }
public string Text { get; set; }
public string Username { get; set; }
public string Icon_Url { get; set; }
// public string Icon_Emoji { get; set; }
public string Channel { get; set; }
}
Any ideas how I can set my bots profile pic dynamically when I create the message?

Azure SDK UnauthorizedException: Put token failed. status-code: 401, status description: Unauthorized: When KeyName is empty the resource URI must

Using C#, the Windows Form Template from Visual Studio and the Microsoft Azure Client SDK
I'm trying to send a message to an IOT Hub. I tested connection String with a Virtual Pi and it works there, I can see the connection and incoming messages in the Azure Shell.
Imports:
using System.Windows.Forms;
using Microsoft.Azure.Devices;
using Message = Microsoft.Azure.Devices.Message;
static string connectionString = "HostName=****.azure- devices.net;DeviceId=****;SharedAccessKey=*******=";
static string deviceId = "SensorTest";
static ServiceClient serviceClient;
serviceClient = ServiceClient.CreateFromConnectionString(connectionString);
SendMessageToCloud("takemystringyoupieceof***").Wait();
private static async Task SendMessageToCloud(string s)
{
MyData data = new MyData
{
Thing1 = "string",
Thing2 = "string",
Thing3 = 1234,
Thing4 = "string",
Thing5 = s
};
var serializeData = JsonConvert.SerializeObject(data);
var commandMessage = new Message(Encoding.ASCII.GetBytes(serializeData));
await serviceClient.SendAsync(deviceId, commandMessage);
}
This throws an Inner Exception:
{"Put token failed. status-code: 401, status-description:
Unauthorized: When KeyName is empty the resource URI must include a device id (Value '****.azure- devices.net').."}
System.Exception {Microsoft.Azure.Devices.Common.Exceptions.UnauthorizedException}
I would like some help understanding the error message:
Isn't KeyName "SharedKey" in this instance?`
The deviceID is included as you can see above?
The Value in the error message is the value for hostname?
For working code example, myData class:
internal class MyData
{
public string Thing1 { get; set; }
public string Thing2 { get; internal set; }
public int Thing3 { get; set; }
public string Thing4 { get; set; }
public string Thing5 { get; set; }
}
You are using a DeviceClient connection string with the ServiceClient. The SendAsync you are calling in your SendMessageToCloud is actually a Cloud to Device API. So depending on your intention, the answer is you either need to use the DeviceClient (a helpful sample can be found here) or you want to use an appropriate Service connection string which can be found in the Shared access policies blade for the IoTHub in the portal. Practicing least privilege, the Service key allows cloud-to-device messages (details on shared access policies can be found here)

Web API POST data is null.... sometimes

I have a WebApi I built that sends emails. I also have a console application that runs everynight, generates a basic report, and emails it to me, through the API.
It was working perfect, until randomly one day I stopped getting the emails. (I say randomly, but I'm sure there was something that happened, - that's why I'm here.) If I send a short HtmlMessage, like <h1>Hi!</h1> it works, but the longer email it actually generates hits the server as null. I'm not sure if I made a change or something that broke this, but I definitely didn't change anything in the email's html.
I have a Mailer class:
public class Mailer
{
public string From { get; set; }
public string To { get; set; }
public string Subject { get; set; }
public string HtmlMessage { get; set; }
}
Here is my WebAPI:
[HttpPost]
[Route("api/sendmail")]
public void Sendmail(Mailer mailer) //public void Sendmail([FromBody] Mailer mailer) tried with and without [FromBody] and neither work
{
/* A bunch of code that doesn't matter */
}
And here is the code that calls the API:
static void Main() {
string message;
/* a bunch of stuff that generates the message */
SendEmail(message);
}
static void SendEmail(string message) {
var data = new Mailer { From = "foo#foo.com", To = "timothy#foo.com", Subject = "Daily Report", HtmlMessage = message };
var data2 = new Mailer { From = "foo#foo.com", To = "timothy#foo.com", Subject = "Daily Report", HtmlMessage = "<h1 style=\"color: red;\">HI</h1>" };
// I was using new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(data); but changed to JSON.net as an attempt to fix
var json = Newtonsoft.Json.JsonConvert.SerializeObject(data); // THIS DOES NOT WORK?! mailer in Sendmail is null.
//var json = Newtonsoft.Json.JsonConvert.SerializeObject(data2); // THIS WORKS?!
var url = "https://server.com/api/sendmail";
using (var client = new WebClient())
{
client.Headers.Add(_headers);
client.Headers.Add("Content-Type", "application/json");
client.UploadString(url, json);
}
}
Any help is appreciated! Thank you in advance!
Well, I feel dumb, but I was able to figure it out. I have a disclaimer at the bottom of the email, where I originally had (C) but replaced it with a copyright sign (©). That appears to have broken it. I replaced it with © and it works perfect now.
So, the issue was this character, that I assume WebAPI declined or was unable to deserialize into the Mailer class.
Anyways, it's fixed! Hopefully this helps someone else out down the road!

Amazon Simple Notification Service with custom iOS payload not so simple

Sending a plain text notification is easy and well documented. But I've been pulling my hair today regarding sending a custom notification for iOS that has the alert and some fields like userId.
I started with this help page and implemented something similar to the last sample, then I found this answer that seems to invalidate the last sample on the help page, as the "url" property should be outside the "aps" object. I tried a good deal of combinations but each one of them gets sent as text to the app (the whole message, with the "default" property and "APNS" object)...
If I explicitly set MessageStructure to json I get the error: "Invalid parameter: Message Structure - JSON message body failed to parse" but I'm pretty sure my JSON is good, when sent to SNS the string in the Message property looks like this:
{ "default":"You received a new message from X.",
"APNS_SANDBOX":"{ \"aps\": {\"alert\":\"You received a new message from X.\"},
\"event\":\"Message\",
\"objectID\":\"7a39d9f4-2c3f-43d5-97e0-914c4a117cee\"
}",
"APNS":"{ \"aps\": {\"alert\":\"You received a new message from X.\"},
\"event\":\"Message\",
\"objectID\":\"7a39d9f4-2c3f-43d5-97e0-914c4a117cee\"
}"
}
Does anybody have a good example of sending a notification with custom payload through SNS in C#? Because Amazon sure hasn't...
Strangely when I implemented the clean way of doing this by using classes and serializing objects instead of just sending a formatted string it worked. The only difference was the spacing... in the clean version there are no spaces except in the property values:
{"default":"You received a new message from X.","APNS_SANDBOX":"{\"aps\":{\"alert\":\"You received a new message from X.\"},\"event\":\"Message\",\"objectID\":\"7a39d9f4-2c3f-43d5-97e0-914c4a117cee\"}","APNS":"{\"aps\":{\"alert\":\"You received a new message from X.\"},\"event\":\"Message\",\"objectID\":\"7a39d9f4-2c3f-43d5-97e0-914c4a117cee\"}"}
These are the classes that I'm serializing (only for APNS for the moment), use whatever properties you need instead of Event and ObjectID:
[DataContract]
public class AmazonSNSMessage
{
[DataMember(Name = "default")]
public string Default { get; set; }
[DataMember(Name = "APNS_SANDBOX")]
public string APNSSandbox { get; set; }
[DataMember(Name = "APNS")]
public string APNSLive { get; set; }
public AmazonSNSMessage(string notificationText, NotificationEvent notificationEvent, string objectID)
{
Default = notificationText;
var apnsSerialized = JsonConvert.SerializeObject(new APNS
{
APS = new APS { Alert = notificationText },
Event = Enum.GetName(typeof(NotificationEvent), notificationEvent),
ObjectID = objectID
});
APNSLive = APNSSandbox = apnsSerialized;
}
public string SerializeToJSON()
{
return JsonConvert.SerializeObject(this);
}
}
[DataContract]
public class APNS
{
[DataMember(Name = "aps")]
public APS APS { get; set; }
[DataMember(Name = "event")]
public string Event { get; set; }
[DataMember(Name = "objectID")]
public string ObjectID { get; set; }
}
[DataContract]
public class APS
{
[DataMember(Name = "alert")]
public string Alert { get; set; }
}
So I get the Amazon SNS message by doing:
new AmazonSNSMessage(...).SerializeToJSON();
The key that just fixed this for me was realizing that the outer JSON specific to SNS (e.g. the "default" and "APNS" properties) MUST NOT be escaped, ONLY the inner payload. For instance, this Message value succeeded (just posting the start):
{"APNS":"{\"aps\" ...
Notice the first property "APNS" is not escaped, but then it's value (your actual payload that will hit the device) IS escaped. The following gets the job done:
JObject payload = ... // your apns, etc payload JObject
var snsMsgJson = new JObject(
new JProperty("default", "message 1 2 3"), // "default" is optional if APNS provided
new JProperty("APNS", payload.ToString(Formatting.None))
);
string str = snsMsgJson.ToString(Formatting.None);
I figured this out looking at the example above, thanks! But, I knew the above 'clean classes' so-called solution couldn't and shouldn't actually be a requirement. So when he says: "The only difference was the spacing... in the clean version there are no spaces except in the property values" that is not correct. The real difference is as I said, outer (SNS specific) JSON shall not be escaped, but inner must.
Soap box: How about that documentation eh? So many things in this API that wasted major time and just as importantly: one's sense of well-being. I do appreciate the service though regardless.

How to get response from SES when using C# SMTP API

The .Net SmtpClient's Send method returns void. It only throws two exceptions, SmtpException and a FailureToSendToRecipientsException (or something like that).
When using SES, for successful email delivery SES sends back a 200 OK message with the message id. This message id needs to be tracked.
How do I do this using the C# SMTP api?
Edit: The SMTP protocol mentions various response codes sent by the SMTP server. I am looking for a SMTP library that exposes the "final" response code to the caller. I am already aware of the SES HTTP API. I am not looking to use that for the moment.
Have you tried the Amazon SES (Simple Email Service) C# Wrapper?
It has an SendEmail method that returns a Class with the MessageId:
public AmazonSentEmailResult SendEmail(string toEmail, string senderEmailAddress, string replyToEmailAddress, string subject, string body)
{
List<string> toAddressList = new List<string>();
toAddressList.Add(toEmail);
return SendEmail(this.AWSAccessKey, this.AWSSecretKey, toAddressList, new List<string>(), new List<string>(), senderEmailAddress, replyToEmailAddress, subject, body);
}
public class AmazonSentEmailResult
{
public Exception ErrorException { get; set; }
public string MessageId { get; set; }
public bool HasError { get; set; }
public AmazonSentEmailResult()
{
this.HasError = false;
this.ErrorException = null;
this.MessageId = string.Empty;
}
}
I dont think you can get the MessageId with System.Net.Mail.SmtpClient you will need to use
Amazon.SimpleEmail.AmazonSimpleEmailServiceClient as per the Amazon SES sample: http://docs.aws.amazon.com/ses/latest/DeveloperGuide/send-using-smtp-net.html

Categories