1
Catch emails from C# .net website
Question asked by ilanmazuz - 1/23/2023 at 9:15 AM
Answered
Hello,

What is the correct way to use the Smartermail API from within our .net website (in C#), to catch emails that are sent to a specific mailbox, in order to manage an internal ticketing system (by reading the subject line to know the ticket number)

Thanks a lot.

19 Replies

Reply to Thread
0
Zach Sylvester Replied
Employee Post
Hello, 

Thanks for reaching out with this question. 
This is a bit of a hard question but here is how I would do it. 
This would return all the messages in the folder along with their subjects. You could then use something like the following code to search the returns by subject. 
HttpClient client = new HttpClient();
var input_post = new { 
    folder = "inbox\\test folder",
    ownerEmailAddress = "youremail@email.com",
    sortType = 5,
    sortAscending = false,
    searchFlags = new { 0 = false },
    query = "",
    skip = 0,
    take = 40051,
    selectedIds = new int[0]
};
var url = SmarterMailUrl + "api/v1/mail/messages";

var response = await client.PostAsync(url, input_post);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadAsStringAsync();
var data = JsonConvert.DeserializeObject(result);

var messagesWithSubjectTest = data.results.Where(x => x.subject.ToLower().Contains("test"));
foreach (var message in messagesWithSubjectTest) {
    Console.WriteLine(message.subject);
}
After doing this you would just need to get the message with the get message API call
This should be something like this

HttpClient client = new HttpClient();
var input_post = new { Folder = "inbox", UID = 44362, OwnerEmailAddress = "youremail@email.com" };
var url = SmarterMailUrl + "api/v1/mail/message";

var response = await client.PostAsync(url, input_post);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadAsStringAsync();
var data = JsonConvert.DeserializeObject(result);

var message = data.results.FirstOrDefault(x => x.folder == input_post.Folder && x.uid == input_post.UID && x.ownerEmailAddress == input_post.OwnerEmailAddress);
if (message != null)
    Console.WriteLine(message.subject);
else
    Console.WriteLine("No message found with the specified folder, UID and owner email address.");

I hope this helps. 😃

Kind Regards, 
Zach Sylvester Software Developer SmarterTools Inc. www.smartertools.com
0
ilanmazuz Replied
Hi Zach,  thanks so much for the prompt and detailed reply.

I have never used the api, so i will need some help to start. For example, when i called the /api/v1/mail/messages for my clients domain on postMan (using POST), i got the reply 
"Missing token in authorization header"
where do i get this token from? and after i will obtain it, how do is et it (in postMan and in the C# code)?

Thanks a lot for the help,
ilan.
0
Zach Sylvester Replied
Employee Post
Hello ilan

Thanks for getting back to me. I'm not very good at using postman but I did my best here with the time that I had. I made a postman method for authenticating and getting the user messages. 

You should be able to import this. Go to the body and replace the placeholders with your info as well as the URL and it should run. After that, you might need to copy and paste the auth token from the auth method to the get messages method. 

I hope this helps. 

Kind Regards, 
Zach Sylvester Software Developer SmarterTools Inc. www.smartertools.com
0
Rod Strumbel Replied
This is in VB.net but you should be able to traslate using the Telerik tool

The deserialization of the JSON communication utilizese the Newtonsoft.JSON library.

Imports Newtonsoft.Json
Imports System.Net.Http
Imports System.Text

Public accessToken As String = String.Empty

'Somewhere in your code before you attempt any API operations

'Establish Token for System Admin Communication
Dim authResult As api_authenticate_user_response = Await Authenticate_User_Async(Username, Password)
if authResult.success = False then
    'Do Nothing - display error message
Else
    'Save Access Token
     accessToken = authResult.accessToken
End If
'Continue on with your code here



'CLASSES / PROCS Used
Public Class api_authenticate_user_response
    Public Property emailAddress As String
    Public Property changePasswordNeeded As Boolean
    Public Property displayWelconeWizard As Boolean
    Public Property isAdmin As Boolean
    Public Property isDomainAdmin As Boolean
    Public Property isLicensed As Boolean
    Public Property autoLoginToken As String
    Public Property autoLoginUrl As String
    Public Property localeId As String
    Public Property isImpersonating As Boolean
    Public Property canViewPasswords As Boolean
    Public Property accessToken As String
    Public Property refreshToken As String
    Public Property accessTokenExpiration As Date
    Public Property username As String
    Public Property success As Boolean
    Public Property resultCode As Integer
End Class


Public Async Function Authenticate_User_Async(u As String, p As String) As Task(Of api_authenticate_user_response)
        Dim returnData As api_authenticate_user_response

        Dim d As New Dictionary(Of String, String)
        d.Add("username", u.Trim)
        d.Add("password", p.Trim)

        Dim jsonData As String = JsonConvert.SerializeObject(d)
        Dim callInputs As HttpContent = New StringContent(jsonData, Encoding.UTF8, "application/json")
        Using client As HttpClient = New HttpClient
            Using response As HttpResponseMessage = Await client.PostAsync(GetOperationURI("authenticate-user"), callInputs)
                Try
                    response.EnsureSuccessStatusCode() 'Will boot us to the Catch if we get anything but a 200 response
                    Dim result = Await response.Content.ReadAsStringAsync()
                    returnData = JsonConvert.DeserializeObject(Of api_authenticate_user_response)(result)
                Catch ex As Exception
                    returnData = New api_authenticate_user_response
                End Try
            End Using
        End Using

        Return returnData
    End Function

Hope that helps!
0
Zach Sylvester Replied
Employee Post
Hey Rod, 

Very nice bit of code you have there! 

Thanks for helping with this question!
Zach Sylvester Software Developer SmarterTools Inc. www.smartertools.com
0
ilanmazuz Replied
Marked As Answer
Thanks a lot Rod, i was able to convert the code and obtain the access token. 

Thanks Zach, i started with your code and witht the help of chatGPT i have this working demo (for anyone that will need it in the future:) 

This is just the begining of course, but its a good start:

    
public string accessToken = string.Empty;
    string Username = "mailbox@mydomain.com";
    string Password = "MysTrongPas!";
    protected void Page_Load(object sender, EventArgs e)
    {
        //std.dbg("page_load");
        //Somewhere in your code before you attempt any API operations
        RegisterAsyncTask(new PageAsyncTask(GetMessages));

    }
    private string GetOperationURI(string action)
    {
          return $"https://mail.domain.com/api/v1/{action}";
    }

    private async Task GetMessages()
    {
        //Establish Token for System Admin Communication
        api_authenticate_user_response authResult = await Authenticate_User_Async(Username, Password);
        if (authResult.success == false)
        {
            //Do Nothing - display error message
            token.Text = "failed to get token";
            return;
        }
        else
        {
            //Save Access Token
            accessToken = authResult.accessToken;
            //token.Text = accessToken;
        }
        //std.dbg("accessToken =",accessToken);
        //Continue on with your code here
        var input_post = new { folder = "inbox", ownerEmailAddress = Username, take = 1 };
        var jsonData = JsonConvert.SerializeObject(input_post);
        var url = GetOperationURI("mail/messages");

        using (var request = new HttpRequestMessage(HttpMethod.Post, url))
        {
            request.Content = new StringContent(jsonData, Encoding.UTF8, "application/json");
            request.Headers.Add("Authorization", "Bearer " + accessToken);
            using (var client = new HttpClient())
            {
                var response = await client.SendAsync(request);
                if (response.IsSuccessStatusCode)
                {
                    // Successful response
                    var responseContent = await response.Content.ReadAsStringAsync();
                    std.dbg("YES!", responseContent);
                    // Do something with the response content
                }
                else
                {
                    // Unsuccessful response
                    var responseContent = await response.Content.ReadAsStringAsync();
                    std.dbg("No!", responseContent);
                    // Log the response content for troubleshooting
                }
                response.EnsureSuccessStatusCode();
            }
        }
    }


    public async Task<api_authenticate_user_response> Authenticate_User_Async(string u, string p)
    {
        api_authenticate_user_response returnData;

        var d = new Dictionary<string, string>();
        d.Add("username", u.Trim());
        d.Add("password", p.Trim());

        var jsonData = JsonConvert.SerializeObject(d);
        var callInputs = new StringContent(jsonData, Encoding.UTF8, "application/json");
        using (var client = new HttpClient())
        {
            using (var response = await client.PostAsync(GetOperationURI("auth/authenticate-user"), callInputs))
            {
                try
                {
                    response.EnsureSuccessStatusCode(); //Will boot us to the Catch if we get anything but a 200 response
                    var result = await response.Content.ReadAsStringAsync();
                    returnData = JsonConvert.DeserializeObject<api_authenticate_user_response>(result);
                }
                catch (Exception ex)
                {
                    std.dbg(ex);
                    returnData = new api_authenticate_user_response();
                }
            }
        }

        return returnData;
    }

}


//CLASSES / PROCS Used
public class api_authenticate_user_response
{
    public string emailAddress { get; set; }
    public bool changePasswordNeeded { get; set; }
    public bool displayWelconeWizard { get; set; }
    public bool isAdmin { get; set; }
    public bool isDomainAdmin { get; set; }
    public bool isLicensed { get; set; }
    public string autoLoginToken { get; set; }
    public string autoLoginUrl { get; set; }
    public string localeId { get; set; }
    public bool isImpersonating { get; set; }
    public bool canViewPasswords { get; set; }
    public string accessToken { get; set; }
    public string refreshToken { get; set; }
    public DateTime accessTokenExpiration { get; set; }
    public string username { get; set; }
    public bool success { get; set; }
    public int resultCode { get; set; }
}


so thanks a lot guys, i hope i'll manage to take it from here, but if not - i know where to ask :)

- ilan.

0
Zach Sylvester Replied
Employee Post
Hello, 


Very nice. I'm glad that I could help. Have a great rest of your week. 

Kind Regards, 
Zach Sylvester Software Developer SmarterTools Inc. www.smartertools.com
0
ilanmazuz Replied
Hi Zach,

in the documentation there are not enough explanations and samples on how to use the api. For example, in the Messages api, i see that there is a "query" post element, but there is no sample on how to use it.
if i want to find all messages that contains a word in the subject, it seems that i can do it in the post arguments and not have to get back all the messages and loop over them to check if the Subject contains the word or not, but i don't see the syntax that shows how to do it.

there is another enum element called "fieldsToSearch " so i guess it should be somthing like:

"fieldsToSearch ": "Subject",
"query": "1045"

But when i try it, i get emails that contains 1045 in the body as well, so it seems that the fieldsToSearch is not affecting my query. Can you explain how do i use the enums?

Here is the input_param var:
var input_post =  new { folder = "inbox", ownerEmailAddress = Username, take = 100, fieldsToSearch ="Subject", query="1045"};

Another question i have is about email threads. if i request 1 mail, will i get the whole thread of the email or just the last reply to it?

Thanks a lot,
ilan.

0
ilanmazuz Replied
Just to clarify, the query=1045 is searching in both, the subject and the body. but i want to limit it to only the subject. if the fieldsToSearch si not the correct place to set it up, then where is?

Thanks,
ilan.
0
Rod Strumbel Replied
Sorry, I don't do any interacting with individual messages via the interface, all my work is for system provisioning.  Hopefully someone else can chime-in!
0
Zach Sylvester Replied
Employee Post
Hello, 

To understand the API calls better, you can reverse engineer the API calls in order to determine how to write your scripts.

In order to reverse engineer the calls, you'll first need to understand how calls are authenticated, and we have this documented in the "Authentication and API Access" section of the API Documentation Overview. Then, using the Networking tab of your browser's developer tools, you will need to perform actions within webmail to see what the requests look like. Please use the following link to download an example video showing how to obtain the details needed in order to utilize the API calls: ReverseEngineerAPI.mp4.

I hope this helps. 

Kind Regards, 

Zach Sylvester Software Developer SmarterTools Inc. www.smartertools.com
0
ilanmazuz Replied
Thanks Zach and Rod.

I'm not sure how a revers engineering will help me here, since in the webmail itself there is no way to search only by the subject line. When i use the search bar in it, i get the same results that i get using the API, which returns emails that contains the string in the body or the subject.

Why does the API documentation is lacking so much information, it doesn't make sense. It's like they stopped in the middle of writing it...

Zach, Is it possible for you to ask the developers how i can limit the search in the API Messages method to the Subject only?
Can you tell them that i tried specifying the input param of fieldsToSearch ="subject" but it has no affect?

Thanks a lot,
ilan.


0
Zach Sylvester Replied
Employee Post
Hello, 

Thanks for the question. I'm not sure what version of SmarterMail you're using but there should be a search bar. At least in the newer versions.
When I do a search and my browser returns this as the post input.
  
{"folder":"inbox","ownerEmailAddress":"myemail@email.com","query":"enter subject to search here","skip":0,"take":151,"selectedIds":[]}
The API documentation from what I understand is somewhat autogenerated so that's why it looks so weird. 

But reverse engineering is the easiest way to get fast results in my opinion. 
One thing that might work better for your use case is to use the advanced search option. 
The request goes like mail.mailserver.com/api/v1/settings/advanced-search
This takes the following post inputs. 

{"searchCriteriaMap":{"text:email:subject":"test"},"section":1,"or":false}
I hope this helps. 

Kind Regards, 
Zach Sylvester Software Developer SmarterTools Inc. www.smartertools.com
0
ilanmazuz Replied
Hello Zach,

I was away from this project for a while and now i'm back to it.

I was surprised to see the suggested search under the "settings" API and not mail - but it seems to work!
i will spend some time on the logic of my project and i'll report back if i have any comments or questions.

thanks a lot,
ilan.
0
Zach Sylvester Replied
Employee Post
Hey Ilan, 

Thanks for the update good to hear from you!

Kind Regards, 
Zach Sylvester Software Developer SmarterTools Inc. www.smartertools.com
0
ilanmazuz Replied
Thank you Zach.

I made a great progress in the project and now i have another question for you, which i hope you can answer.

Is it possible that when i send an email from my C# code, that this email will show up in the Sent Items folder of the account i used as the sender?

Currently what i'm doing is that i add the sender as a bcc, so that the thread of the email with the latest response will be available in the next time i pull the data. But obviously this is not accurate, because the sent item should not be placed in the inbox but in the Sent Items folder. 

Just to clarify, i am using the .net MailMessage class to send the email, not the API "SendMessage" method. 

Is it something that is possible to do or do i have to send the email via the API itself and it will place it in the Sent Items folder?

Thanks a lot,
ilan.
0
ilanmazuz Replied
Hello again,

i tried calling the SendMessage method, but keep failing with the error:
{"message":"Value cannot be null.\r\nParameter name: key"} 

i thought maybe the Actions list is required to have at least 1 value, so i addedd one, but it didn't help.
the only other "key" in the documentation is in "originalCidLinks " but i don't even know what it is...

i don't know which "key" is missing... here is my C# code:

 api_authenticate_user_response authResult = await Authenticate_User_Async(Username, Password);
        if (authResult.success == false)
        {
            //Do Nothing - display error message
            lblResult.Text = "failed to get token";
            return;
        }
        else
        {
            //Save Access Token
            accessToken = authResult.accessToken;
            //lblResult.Text = accessToken;
        }


        //read the current message again, to extract the required information
        var url = GetOperationURI("mail/message-put");


        EmailMessage message = new EmailMessage();
        message.To = txtClientEmailAddress.Text;
        message.From  = Username;
        message.Subject = $"RE: Lost & Found: Report #LF{report_id}";
        message.ReplyUid = Convert.ToInt32(hidLatestMsgId.Value);
        message.ReplyFromFolder= hidLatestMsgFolder.Value;
        message.Folder = "Sent Items";
        message.MessageHTML = radEditorNewMail.Content;
        message.Actions.Add("Replied", true);

        var jsonData = JsonConvert.SerializeObject(message);
        std.dbg("sonData =", jsonData);
        using (var request = new HttpRequestMessage(HttpMethod.Post, url))
        {
            request.Content = new StringContent(jsonData, Encoding.UTF8, "application/json");
            request.Headers.Add("Authorization", "Bearer " + accessToken);
            using (var client = new HttpClient())
            {
                var response = await client.SendAsync(request);
                if (response.IsSuccessStatusCode)
                {
                    // Successful response
                    var responseContent = await response.Content.ReadAsStringAsync();
                    //std.dbg(responseContent);
                    SendMessageResult result = JsonConvert.DeserializeObject<SendMessageResult>(responseContent);

                    if (!result.Success)
                    {
                        lblSendResults.Text = $"Sent failed. Error code: {result.ResultCode}.<br/>Message:{result.Message}.<br/>debugInfo:{result.DebugInfo}.";
                        lblSendResults.CssClass = "error";
                    }
                    return ;
                }
                else
                {
                    // Unsuccessful response
                    var responseContent = await response.Content.ReadAsStringAsync();
                    //std.dbg("No!", responseContent);
                    lblSendResults.Text = responseContent;
                    lblSendResults.CssClass = "error";
                    return;
                }
                //response.EnsureSuccessStatusCode();
            }
        }

Any idea what is missing?

thanks,
ilan

1
Zach Sylvester Replied
Employee Post
Hey Ilan, 

I posted an answer to this in this thread. 

Please check it out. 

Kind Regards, 
Zach Sylvester Software Developer SmarterTools Inc. www.smartertools.com
0
CLEBER SAAD Replied
If you are creating a ticket system, uses IMAP to download the messages and after the process that, move to another folder and go to the next. 

Reply to Thread