Attachments are not stored separately from message text and headers – they are embedded inside an email message. This, along with inefficient Base64 encoding is the most important reason of email messages being large in size. Mail.dll provides an easy way to replace attachments in existing messages:
// C#
IMail email = new MailBuilder().CreateFromEml(eml);
email.ReplaceAttachments();
' VB.NET
Dim email As IMail = New MailBuilder().CreateFromEml(eml)
email.ReplaceAttachments()
Each attachment will be replaced with the following text information: “This file (‘[FileName]’) containing [Size] bytes of data was removed.”. Thus making email much smaller in size.
ReplaceAttachmentsmethod has an overloaded version, that allows you to skip visual elements (content-disposition: inline) or/and alternative email representations. It also allows to specify text template and custom Tag, that can be used, for example, to create a custom url. This url can point to a place to which attachment was moved.
Within the template you can use [FileName], [Size] and [Tag] as template placeholders.
// C#
IMail email = Limilabs.Mail.Fluent.Mail
.Text("body")
.AddAttachment(new byte[] { 1, 2, 3 })
.SetFileName("report.pdf")
.Create();
AttachmentReplacerConfiguration configuration = new AttachmentReplacerConfiguration();
configuration.ReplaceVisuals = false;
configuration.Tag =
att => "http://example.com/" + email.MessageID + "/" + att.FileName;
configuration.Template =
"Attachment [FileName] removed. You can download it here: [Tag]";
email.ReplaceAttachments(configuration);
' VB.NET
Dim email As IMail = Limilabs.Mail.Fluent.Mail _
.Text("body") _
.AddAttachment(New Byte() {1, 2, 3}) _
.SetFileName("report.pdf") _
.Create()
Dim configuration As New AttachmentReplacerConfiguration()
configuration.ReplaceVisuals = False
configuration.Tag = Function(att)
Return "http://example.com/" + email.MessageID + "/" + att.FileName
End Function
configuration.Template = _
"Attachment [FileName] removed. You can download it here: [Tag]"
email.ReplaceAttachments(configuration)
The following example illustrates the full process of downloading email from IMAP server,
creating new email, with the same information, but with all attachments replaced, uploading this message, and deleting original one:
// C#
using(Imap imap = new Imap())
{
imap.ConnectSSL("imap.example.org");
imap.UseBestLogin("user", "password");
imap.SelectInbox();
foreach (long uid in imap.GetAll())
{
var eml = imap.GetMessageByUID(uid);
IMail email = new MailBuilder().CreateFromEml(eml);
if (email.Attachments.Count > 0)
{
email.ReplaceAttachments();
imap.UploadMessage(email);
imap.DeleteMessageByUID(uid);
}
}
imap.Close();
}
' VB.NET
Using imap As New Imap()
imap.ConnectSSL("imap.example.org")
imap.UseBestLogin("user", "password")
imap.SelectInbox()
For Each uid As Long In imap.GetAll()
Dim eml = imap.GetMessageByUID(uid)
Dim email As IMail = New MailBuilder().CreateFromEml(eml)
If email.Attachments.Count > 0 Then
email.ReplaceAttachments()
imap.UploadMessage(email)
imap.DeleteMessageByUID(uid)
End If
Next
imap.Close()
End Using
Please make sure that you have disabled antivirus and firewall software or that you have configured them correctly.
This includes Windows Defender – especially if you use SMTP client.
Many reports show that AVG antivirus tracks IMAP sessions and interrupts them with no reason.
Similarly Norton Security scans outgoing messages (‘Scan outgoing email messages’ feature is on even when antivirus is disabled) and breaks as it has incorrectly implemented CHUNKING mechanism.
Some Cisco Pix and Cisco ASA firewall may interfere with SMTP traffic when SMTP Packet Inspection is on.
In most such cases turning on SSL/TLS prevents external programs from snooping and breaking the traffic.
Generally ‘Tried to read a line.’ error means that the connection was interrupted. It was lost, the server disconnected or your antivirus/firewall cut the connection. For instance: SMTP server may be configured to disconnect as soon as it decides your message is spam or when you are using incorrect from address.
In technical terms, it means exactly what the exception’s message says: component tried to read a line (ending with CRLF), the line was expected in accordance to the protocol in use, but it has not been received during the specified period.
On extremely slow networks you may increase timeout values: ReceiveTimeout and SendTimeout.
If increasing timeout values doesn’t help, it means that the connection was dropped by the remote server or some intermediary router – you’ll need to connect again.
If you are using *Imap.Idle* you can use lower IDLE timeout. It specifies timeout after which IDLE command is re-issued. This breaks inactivity period (which happens when no new message is delivered) and thus prevents routers from cutting the connection.
Mail.dll for Windows Store
If you are using Mail.dll for windows store applications: Connect and ConnectSSL methods are asynchronous. You must use await ConnectSSL and await Connect:
First, there is one thing you must be aware of: neither POP3 nor IMAP protocol provide a way to remove attachments from existing emails. This is because email stored on the server is immutable. With IMAP protocol you can copy email message to different folder, you can apply some flags (\SEEN) to it, but you can’t change any part of the message.
The second important thing is, that attachments are not stored separately from the message text and headers – they are embedded inside the email.
Nevertheless Mail.dll provides an easy way to remove attachments from existing email message.
// C#
IMail email = new MailBuilder().CreateFromEml(eml);
email.RemoveAttachments();
' VB.NET
Dim email As IMail = New MailBuilder().CreateFromEml(eml)
email.RemoveAttachments()
RemoveAttachments method has an overloaded version, that allows you to skip visual elements (content-disposition: inline) or/and alternative email representations:
// C#
IMail email = new MailBuilder().CreateFromEml(eml);
AttachmentRemoverConfiguration configuration = new AttachmentRemoverConfiguration();
configuration.RemoveVisuals = false;
email.RemoveAttachments(configuration);
' VB.NET
Dim email As IMail = New MailBuilder().CreateFromEml(eml)
Dim configuration As New AttachmentRemoverConfiguration()
configuration.RemoveVisuals = False
email.RemoveAttachments(configuration)
The following example illustrates the full process of downloading email from IMAP server,
creating new email, with the same information, but without attachments, and finally uploading it, and removing the original message:
// C#
using(Imap imap = new Imap())
{
imap.ConnectSSL("imap.example.org");
imap.UseBestLogin("user", "password");
imap.SelectInbox();
foreach (long uid in imap.GetAll())
{
var eml = imap.GetMessageByUID(uid);
IMail email = new MailBuilder().CreateFromEml(eml);
if (email.Attachments.Count > 0)
{
email.RemoveAttachments();
imap.UploadMessage(email);
imap.DeleteMessageByUID(uid);
}
}
imap.Close();
}
' VB.NET
Using imap As New Imap()
imap.ConnectSSL("imap.example.org")
imap.UseBestLogin("user", "password")
imap.SelectInbox()
For Each uid As Long In imap.GetAll()
Dim eml = imap.GetMessageByUID(uid)
Dim email As IMail = New MailBuilder().CreateFromEml(eml)
If email.Attachments.Count > 0 Then
email.RemoveAttachments()
imap.UploadMessage(email)
imap.DeleteMessageByUID(uid)
End If
Next
imap.Close()
End Using
On Azure Portal go to “Azure Active Directory / App Registrations / New Registration”
After registering, you need to add a new Client Secret for your application. You can do that using the “Certificates & secrets” menu within the “Azure Active Directory / App Registrations settings”.
Deprecated url for registering applications: https://account.live.com/developers/applications/
ClientID, ClientSecret, Scope
Now we can define clientID, clientSecret and scope variables, as well as Outlook.com OAuth 2.0 server addresses. Scope basically specifies what services we want to have access to. In our case it is user’s email address and IMAP/SMTP access:
string clientID = "000000014810009D";
string clientSecret = "wiRCccXnq1uyKcXnq1uyK";
AuthorizationServerDescription server = new AuthorizationServerDescription
{
AuthorizationEndpoint = new Uri("https://login.live.com/oauth20_authorize.srf"),
TokenEndpoint = new Uri("https://login.live.com/oauth20_token.srf"),
ProtocolVersion = ProtocolVersion.V20,
};
List<string> scope = new List<string>
{
OutlookScope.ImapAndSmtp.Name,
OutlookScope.EmailAddress.Name
};
Obtain an OAuth 2.0 access token
As we are using installed applications scenario we’ll use NativeApplicationClient class.
Because of a small issue in DotNetOpenAuth we can not use UserAgentClient directly. NativeApplicationClient inherits UserAgentClient and workarounds this issue. You can find the implementation of NativeApplicationClient on the bottom of this article.
NativeApplicationClient consumer = new NativeApplicationClient(server, clientID, clientSecret);
Uri userAuthorizationUri = consumer.RequestUserAuthorization(scope);
Process.Start(userAuthorizationUri.AbsoluteUri);
We are using Process.Start here, but you can also embed WebBrowser control in your application.
At this point user is redirected to Microsoft to authorize the access:
After this step user is presented a code that needs to be pasted to your application:
Please note that this code also appears in the title of the browser:
It is possible to monitor processes on your machine and act automatically when it is there.
If you use embedded WebBrowser control in your application, you can monitor the HTML document title after any redirect.
Following is a code that reads this code and contacts Outlook.com to exchange it for a refresh-token and an access-token:
An access token is usually short lived, and allows you to access the user’s data. You may also received a refresh token. A refresh token can be used to request a new access token once the previous expired.
Access IMAP/SMTP server
Finally we’ll ask Microsoft for user’s email and use LoginOAUTH2 method to access Outlook.com IMAP server:
IAuthorizationState grantedAccess = consumer.ProcessUserAuthorization(authCode);
string accessToken = grantedAccess.AccessToken;
OutlookApi api = new OutlookApi(accessToken);
string user = api.GetEmail();
using (Imap client = new Imap())
{
client.ConnectSSL("imap-mail.outlook.com");
imap.LoginOAUTH2(user, accessToken);
imap.SelectInbox();
Lis<long> uids = imap.Search(Flag.Unseen);
foreach (long uid in uids)
{
var eml = imap.GetMessageByUID(uid);
IMail email = new MailBuilder().CreateFromEml(eml);
Console.WriteLine(email.Subject);
}
imap.Close();
}
NativeApplicationClient class
The OAuth 2.0 client for use by native applications. It’s main purpose is to build a generic URL containing the auth code, because DotNetOpenAuth library only allows parsing an URL as a method of retrieving the AuthorizationState.
/// <summary>
/// The OAuth2 client for use by native applications.
/// This is a partial implementation which should be used until
/// the feature has been fully implemented in DotNetOpenAuth.
/// </summary>
public class NativeApplicationClient : UserAgentClient
{
/// <summary>
/// Represents a callback URL which points to a special out of band page
/// used for native OAuth2 authorization. This URL will cause the authorization
/// code to appear in the title of the window.
/// </summary>
public string OutOfBandCallbackUrl = "https://login.live.com/oauth20_desktop.srf"; // Outlook
/// <summary>
/// Initializes a new instance of the <see cref="UserAgentClient"/> class.
/// </summary>
/// <param name="authorizationServer">The token issuer.</param>
/// <param name="clientIdentifier">The client identifier.</param>
/// <param name="clientSecret">The client secret.</param>
public NativeApplicationClient(
AuthorizationServerDescription authorizationServer,
string clientIdentifier,
string clientSecret)
: base(authorizationServer, clientIdentifier, clientSecret)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="UserAgentClient"/> class.
/// </summary>
/// <param name="authorizationServer">The token issuer.</param>
public NativeApplicationClient(
AuthorizationServerDescription authorizationServer)
: this(authorizationServer, null, null)
{
}
/// <summary>
/// Creates the URL which should be used by the user to request the initial
/// authorization. Uses the default Out-of-band-URI as a callback.
/// </summary>
/// <param name="scope">Set of requested scopes</param>
/// <returns>URI pointing to the authorization server</returns>
public Uri RequestUserAuthorization(IEnumerable<string> scope)
{
var state = new AuthorizationState(scope);
state.Callback = new Uri(OutOfBandCallbackUrl);
return RequestUserAuthorization(state, false, null);
}
/// <summary>
/// Uses the provided authorization code to create an authorization state.
/// </summary>
/// <param name="authCode">The authorization code for getting an access token.</param>
/// <param name="authorizationState">The authorization. Optional.</param>
/// <returns>The granted authorization, or <c>null</c> if the authorization was null or rejected.</returns>
public IAuthorizationState ProcessUserAuthorization(
string authCode,
IAuthorizationState authorizationState)
{
if (authorizationState == null)
{
authorizationState = new AuthorizationState(null);
authorizationState.Callback = new Uri(OutOfBandCallbackUrl);
}
// Build a generic URL containing the auth code.
// This is done here as we cannot modify the DotNetOpenAuth library
// and the underlying method only allows parsing an URL as a method
// of retrieving the AuthorizationState.
string url = "http://example.com/?code=" + authCode;
return ProcessUserAuthorization(new Uri(url), authorizationState);
}
/// <summary>
/// Uses the provided authorization code to create an authorization state.
/// </summary>
/// <param name="authCode">The authorization code for getting an access token.</param>
/// <returns>The granted authorization, or <c>null</c> if the authorization was null or rejected.</returns>
public IAuthorizationState ProcessUserAuthorization(string authCode)
{
return ProcessUserAuthorization(authCode, null);
}
};
Refreshing access token
An access token is usually short lived. The main reason behind this is security and prevention of replay attacks. This means that for long-lived applications you need to refresh the access token. To force sending refresh token you need to add “wl.offline_access” to requested scopes:
List<string> scope = new List<string>
{
OutlookScope.ImapAndSmtp.Name,
OutlookScope.EmailAddress.Name,
OutlookScope.OfflineAccess.Name
};
Your refresh token will be sent only once – don’t loose it!
We recommend storing entire IAuthorizationState object received from WebServerClient.ProcessUserAuthorization method call. This object contains both: refresh token and access token, along with its expiration time.
On Azure Portal go to “Azure Active Directory / App Registrations / New Registration”
After registering, you need to add a new Client Secret for your application. You can do that using the “Certificates & secrets” menu within the “Azure Active Directory / App Registrations settings”.
Deprecated url for registering applications: https://account.live.com/developers/applications/
ClientID, ClientSecret, Scope
Now we can define clientID, clientSecret, redirect url and scope variables, as well as Outlook.com OAuth 2.0 server addresses. Scope basically specifies what services we want to have access to. In our case it is user’s email address and IMAP/SMTP access:
string clientID = "000000014810009D";
string clientSecret = "wiRCccXnq1uyKcXnq1uyK";
string redirectUri = "http://fake-domain-9650932456.com/OAuth2.aspx";
AuthorizationServerDescription server = new AuthorizationServerDescription
{
AuthorizationEndpoint = new Uri("https://login.live.com/oauth20_authorize.srf"),
TokenEndpoint = new Uri("https://login.live.com/oauth20_token.srf"),
ProtocolVersion = ProtocolVersion.V20,
};
List<string> scope = new List<string>
{
OutlookScope.ImapAndSmtp.Name,
OutlookScope.EmailAddress.Name
};
Obtain an OAuth 2.0 access token
As we are using ASP.NET we’ll use WebServerClient class:
WebServerClient consumer = new WebServerClient(server, clientID, clientSecret);
// Here redirect to authorization site occurs
consumer.RequestUserAuthorization(scope, new Uri(redirectUri));
If you use ASP.NET MVC the last line is different:
// Here redirect to authorization site occurs
OutgoingWebResponse response = consumer.PrepareRequestUserAuthorization(
scope, new Uri(redirectUri));
return response.AsActionResult();
At this point user is redirected to Microsoft to authorize the access:
After this step user is redirected back to your website (http://fake-domain-9650932456.com/OAuth2.aspx). Following is this callback code. Its purpose is to get a refresh-token and an access-token:
An access token is usually short lived, and allows you to access the user’s data. You also received a refresh token. A refresh token can be used to request a new access token once the previous expired.
Access IMAP/SMTP server
Finally we’ll ask Microsoft for user’s email and use LoginOAUTH2 method to access Outlook.com IMAP server:
OutlookApi api = new OutlookApi(accessToken);
string user = api.GetEmail();
using (Imap imap = new Imap())
{
imap.ConnectSSL("imap-mail.outlook.com");
imap.LoginOAUTH2(user, accessToken);
imap.SelectInbox();
List<long> uids = imap.Search(Flag.Unseen);
foreach (long uid in uids)
{
var eml = imap.GetMessageByUID(uid);
IMail email = new MailBuilder().CreateFromEml(eml);
Console.WriteLine(email.Subject);
}
imap.Close();
}
Refreshing access token
An access token is usually short lived. The main reason behind this is security and prevention of replay attacks. This means that for long-lived applications you need to refresh the access token.
In most cases web applications don’t need to refresh access token (they request new one every time), thus when using WebServerClient refresh token is not sent. To force sending refresh token you need to add “wl.offline_access” to requested scopes:
List<string> scope = new List<string>
{
OutlookScope.ImapAndSmtp.Name,
OutlookScope.EmailAddress.Name,
OutlookScope.OfflineAccess.Name
};
Your refresh token will be sent only once – don’t loose it!
We recommend storing entire IAuthorizationState object received from WebServerClient.ProcessUserAuthorization method call. This object contains both: refresh token and access token, along with its expiration time.