preface

Email is a traditional way of communication and communication on the Internet, which is still widely used in the workplace. Recently, WHEN I was in charge of the development of an email client APP, I came into contact with some email-related codes and knowledge, so I took some time to sort out the open source libraries and resources related to email development.

The body of the

First of all, it is clear that mailbox can be roughly divided into two types from the use agreement/category:

  • Standard IMAP/SMTP email, such as GMail, Outlook, Yahoo, QQ, 163, etc.
  • Exchange, also known as Microsoft Exchange Server, is a mail Server developed by Microsoft corporation, such as Exchange 2010. Applications can use Exchange Web Service (EWS) to access emails, schedules, and contacts on an Exchange server.

mailcore2

MailCore2 is a mail protocol encapsulation library written in C language. It runs on iOS, Android, Mac, Windows and Linux platforms and supports SMTP, IMAP, POP3, MIME and HTML message rendering. The calls to the MailCore2 API are asynchronous, meaning that operations are performed in child threads and do not block the current main thread.

MailCore 2 provide a simple and asynchronous API to work with e-mail protocols IMAP, POP and SMTP. The API has been redesigned from ground up.

Making: github.com/MailCore/ma…

Many third-party mail clients are based on MailCore2, such as the open source project Mailspring, which is small and elegant

compile

  • Compilation environment: macOS 10.13.6, Intel processor, Android NDK 17.2.4988734
  • Install autoconf
Curl - OL http://ftpmirror.gnu.org/autoconf/autoconf-2.69.tar.gz tar - XZF autoconf - 2.69. Tar. GzcdAutoconf-2.69./configure && make && sudo make installCopy the code
  • Install automake
Curl - OL http://ftpmirror.gnu.org/automake/automake-1.14.tar.gz tar - XZF automake - 1.14. Tar. Gzcd/configure && make && sudo make installCopy the code
  • Install libtool
The curl - OL http://ftpmirror.gnu.org/libtool/libtool-2.4.2.tar.gz tar - XZF libtool - 2.4.2. Tar. GzcdLibtool -2.4.2./configure && make && sudo make installCopy the code
  • Install the PKG – config
The curl - OL http://pkgconfig.freedesktop.org/releases/pkg-config-0.29.2.tar.gz tar - XZF PKG - config - 0.29.2. Tar. GzcdPkg-config-0.29.2./configure --with-internal-glib && make && sudo make installCopy the code
  • Install cmake

Download cmake-3.22.2-macos-universal. DMG, install cmake-3.22.2-macos-universal. DMG, install cmake-3.22.2-macos-universal.

sudo "/Applications/CMake.app/Contents/bin/cmake-gui"--install cmake --versionCopy the code
  • Refer to the official document -Build for Android, compile MailCore2 source code, generate Mailcore2-Android-4. Aar, the size of about 30M 😀😔😱

The compiled AAR file has been uploaded to GitHub at the address:Github.com/kongpf8848/…

use

  • Put the generated mailcore2-Android-4. aar in the liBS directory of the project module
  • Add the following code to your module’s build.gradle file:
repositories {
    flatDir {
        dirs 'libs'
    }
}

dependencies {
    api(name: 'mailcore2-android-4'.ext: 'aar')}Copy the code

Add mailbox -> The overall process of pulling mail is as follows:

  • Check whether the mailbox is available -> Get folder information -> get the UID list of the specified folder
  • Get message Header –> Get message Structure –> Get message Body and attachment information –> Download message attachment

Build IMAPSession

The first step in using the API is to build the Session object, which contains the mailbox username, password, hostname, port, and so on.

IMAPSession imapSession = new IMAPSession();
imapSession.setUsername("[email protected]");
imapSession.setPassword("Authorization Code");
imapSession.setHostname("imap.163.com");
imapSession.setPort(993);
imapSession.setConnectionType(ConnectionType.ConnectionTypeTLS);
IMAPIdentity imapIdentity = imapSession.clientIdentity();
imapIdentity.setVendor("xxx");
imapIdentity.setName("xxx");
imapIdentity.setVendor("xxx");
Copy the code

If you do not need to verify the certificate, add the following code:

imapSession.setCheckCertificateEnabled(false);
Copy the code

If you need OAuth2 authentication, such as GMail, Outlook, etc., you need to add the following code:

imapSession.setOAuth2Token("token");
imapSession.setAuthType(AuthType.AuthTypeXOAuth2Outlook); / / Outlook email
//imapSession.setAuthType(AuthType.AuthTypeXOAuth2); // Non-outlook mailbox
Copy the code

Check whether the mailbox IMAP service is available

IMAPOperation imapOperation = imapSession.checkAccountOperation();
if (imapOperation == null) {
    return;
}
imapOperation.start(new OperationCallback() {
    @Override
    public void succeeded(a) {
        Log.d("MailCore2"."check imap succeeded()");
    }

    @Override
    public void failed(MailException e) {
        Log.d("MailCore2"."check imap failed() with: e = [" + e + "]"); }});Copy the code

Build SMTPSession

smtpSession = new SMTPSession();
smtpSession.setUsername("[email protected]");
smtpSession.setPassword("Authorization Code");
smtpSession.setHostname("smtp.163.com");
smtpSession.setPort(465);
smtpSession.setConnectionType(ConnectionType.ConnectionTypeTLS);
Copy the code

If you do not need to verify the certificate, add the following code:

smtpSession.setCheckCertificateEnabled(false);
Copy the code

If you need OAuth2 authentication, such as GMail, Outlook, etc., you need to add the following code:

smtpSession.setOAuth2Token("token");
smtpSession.setAuthType(AuthType.AuthTypeXOAuth2Outlook); / / Outlook email
//smtpSession.setAuthType(AuthType.AuthTypeXOAuth2); // Non-outlook mailbox
Copy the code

Check whether the SMTP service of the mailbox is available

Address address = new Address();
address.setMailbox(smtpSession.username());
SMTPOperation smtpOperation = smtpSession.checkAccountOperation(address);
if (smtpOperation == null) {
    return;
}
smtpOperation.start(new OperationCallback() {
    @Override
    public void succeeded(a) {
        Log.d("MailCore2"."check smtp succeeded()");
    }

    @Override
    public void failed(MailException e) {
        Log.d("MailCore2"."check smtp failed() with: e = [" + e + "]"); }});Copy the code

Get folder information

/ / get defaultNamespace
IMAPNamespace defaultNamespace = imapSession.defaultNamespace();
if (defaultNamespace == null) {
    IMAPFetchNamespaceOperation imapFetchNamespaceOperation = imapSession.fetchNamespaceOperation();
    imapFetchNamespaceOperation.start(new OperationCallback() {
        @Override
        public void succeeded(a) {
            getFolderHasNamespace(imapSession);
        }

        @Override
        public void failed(MailException e) { getFolderHasNamespace(imapSession); }}); }else {
    getFolderHasNamespace(imapSession);
}

private void getFolderHasNamespace(IMAPSession imapSession) {
    IMAPFetchFoldersOperation imapFetchFoldersOperation = imapSession.fetchAllFoldersOperation();
    imapFetchFoldersOperation.start(new OperationCallback() {
        @Override
        public void succeeded(a) {
            List<IMAPFolder> folderList = imapFetchFoldersOperation.folders();
            if(! ListUtil.isEmpty(folderList)) {for (IMAPFolder folder : folderList) {
                    Log.d("MailCore2"."succeeded() called with path=" + folder.path() + ",name="+ getFolderNameForMailCore(imapSession, folder)); }}}@Override
        public void failed(MailException e) {
            Log.d("MailCore2"."failed() called with: e = [" + e + "]"); }}); }// Get the folder name and convert path to name
private String getFolderNameForMailCore(IMAPSession imapSession, IMAPFolder folder) {
    List<String> componentNameList = imapSession.defaultNamespace().componentsFromPath(folder.path());
    if(! ListUtil.isEmpty(componentNameList)) {return componentNameList.get(componentNameList.size() - 1);
    } else {
        List<String> pathList = new ArrayList<>();
        pathList.add(folder.path());
        String namespacePath = imapSession.defaultNamespace().pathForComponents(pathList);
        List<String> nameList = imapSession.defaultNamespace().componentsFromPath(namespacePath);
        if (ListUtil.isEmpty(nameList)) {
            String[] defaultNameArray = {"inbox"."sent messages"."sent"."junk"."deleted"};
            if (Arrays.asList(defaultNameArray).contains(folder.path().toLowerCase())) {
                return folder.path();
            } else {
                return "Unknown"; }}else {
            return nameList.get(nameList.size() - 1); }}}Copy the code

Note here:

  • Before obtaining the folder, you must obtain the defaultNamespace. If you convert path to name, you need to use the defaultNamespace method. If you do not perform this operation, the value returned by defaultNamespace() is null
  • The path in the IMAPFolder object is a jumbled string that needs to be executed abovegetFolderNameForMailCoreMethod is converted to a readable name

Gets the UID list of the specified folder

Gets the specified number of folder messages based on the folder name, and then the UID list

// Get the inbox UID
String path = "INBOX";
IMAPFolderInfoOperation imapFolderInfoOperation = imapSession.folderInfoOperation(path);
imapFolderInfoOperation.start(new OperationCallback() {
    @Override
    public void succeeded(a) {
        IMAPFolderInfo folderInfo = imapFolderInfoOperation.info();
        int messageCount = folderInfo.messageCount();
        if (messageCount > 0) {
            Range range = new Range(1, messageCount - 1);
            IndexSet indexSet = new IndexSet();
            indexSet.addRange(range);

            IMAPFetchMessagesOperation imapFetchMessagesOperation = imapSession.fetchMessagesByNumberOperation(path, IMAPMessagesRequestKind.IMAPMessagesRequestKindUid, indexSet);
            imapFetchMessagesOperation.start(new OperationCallback() {
                @Override
                public void succeeded(a) {
                    List<IMAPMessage> messages = imapFetchMessagesOperation.messages();
                    if(messages ! =null && messages.size() > 0) {
                        for (IMAPMessage message : messages) {
                            Log.d("MailCore2"."succeeded() called,uid:"+ message.uid()); }}}@Override
                public void failed(MailException e) {
                    Log.d("MailCore2"."failed() called with: e = [" + e + "]"); }}); }}@Override
    public void failed(MailException e) {
        Log.d("MailCore2"."failed() called with: e = [" + e + "]"); }});Copy the code

Get the mail Header

Get the mail Header list from the UID list

String path = "INBOX"; //path
IndexSet indexSet = new IndexSet();
indexSet.addIndex(1318015733);  / / add the UID
int requestKind = IMAPMessagesRequestKind.IMAPMessagesRequestKindFlags | IMAPMessagesRequestKind.IMAPMessagesRequestKindInternalDate | IMAPMessagesRequestKind.IMAPMessagesRequestKindFullHeaders;
IMAPFetchMessagesOperation imapFetchMessagesOperation = imapSession.fetchMessagesByUIDOperation(path, requestKind, indexSet);
imapFetchMessagesOperation.start(new OperationCallback() {
    @Override
    public void succeeded(a) {
        List<IMAPMessage> messages = imapFetchMessagesOperation.messages();
        if(messages ! =null && messages.size() > 0) {
            for (IMAPMessage message : messages) {
                MessageHeader header = message.header();
                if(header ! =null) {
                    Log.d("MailCore2"."succeeded() called,subject:" + header.subject());
                    Address from = header.from();
                    if(from ! =null) {
                        Log.d("MailCore2"."succeeded() called,from:" + from.mailbox() + "," + from.displayName());
                    }
                    List<Address> to = header.to();
                    if(to ! =null) {
                        for (Address address : to) {
                            Log.d("MailCore2"."succeeded() called,to:" + address.mailbox() + "," + address.displayName());
                        }
                    }
                }
            }
        }
    }

    @Override
    public void failed(MailException e) {
        Log.d("MailCore2"."failed() called with: e = [" + e + "]"); }});Copy the code

Get the message Body

Obtain the message Structure information first, and then the message body information

String path = "INBOX";
IndexSet indexSet = new IndexSet();
indexSet.addIndex(1318015852);
int requestKind = IMAPMessagesRequestKind.IMAPMessagesRequestKindStructure;
IMAPFetchMessagesOperation imapFetchMessagesOperation = imapSession.fetchMessagesByUIDOperation(path, requestKind, indexSet);
imapFetchMessagesOperation.start(new OperationCallback() {
    @Override
    public void succeeded(a) {
        List<IMAPMessage> messages = imapFetchMessagesOperation.messages();
        if(messages ! =null && messages.size() > 0) {
            IMAPMessage message = messages.get(0);
            IMAPMessageRenderingOperation imapMessageRenderingOperation = imapSession.htmlBodyRenderingOperation(message, path);
            imapMessageRenderingOperation.start(new OperationCallback() {
                @Override
                public void succeeded(a) {
                    String body = imapMessageRenderingOperation.result();
                    Log.d(TAG, "succeeded() called,body:" + body);

                    // Inline attachment information
                    List<AbstractPart> inlineAttachments = message.htmlInlineAttachments();
                    if(inlineAttachments ! =null && inlineAttachments.size() > 0) {
                        for (AbstractPart part : inlineAttachments) {
                            IMAPPart imapPart = (IMAPPart) part;
                            Log.d("MailCore2"."succeeded() called,inline-attachment:id=" + imapPart.uniqueID()
                                    + ",filename:" + imapPart.filename() + ",contentId:" + imapPart.contentID()
                                    + ",mimeType:" + imapPart.mimeType() + ",isInLine:"
                                    + imapPart.isInlineAttachment() + ",size:" + imapPart.decodedSize()
                                    + ",partId:" + imapPart.partID() + ",encoding:"+ imapPart.encoding()); }}// Non-inline attachment information
                    List<AbstractPart> attachments = message.attachments();
                    if(attachments ! =null && attachments.size() > 0) {
                        for (AbstractPart part : attachments) {
                            IMAPPart imapPart = (IMAPPart) part;
                            Log.d("MailCore2"."succeeded() called,attachment:id=" + imapPart.uniqueID()
                                    + ",filename:" + imapPart.filename() + ",contentId:" + imapPart.contentID()
                                    + ",mimeType:" + imapPart.mimeType() + ",isInLine:"
                                    + imapPart.isInlineAttachment() + ",size:" + imapPart.decodedSize()
                                    + ",partId:" + imapPart.partID() + ",encoding:"+ imapPart.encoding()); }}}@Override
                public void failed(MailException e) {
                    Log.d("MailCore2"."failed() called with: e = [" + e + "]"); }}); }}@Override
    public void failed(MailException e) {
        Log.d("MailCore2"."failed() called with: e = [" + e + "]"); }});Copy the code

Download the attachment

When we obtained the email Body in the previous step, we already got the email attachment information. The main information of an attachment is as follows:

field type instructions
uniqueID String The attachment ID
filename String Attachment name, such as sun.png
contentId String Content Id. Value if the attachment is an inline attachment, for example, 1FA7edd3.png
mimeType String Attachment type, such as image/ PNG, application/ OCtet-stream
isInLine boolean Whether it is an inline attachment. The value is true or false
size long Attachment size, in bytes
partId String ID, such as 1,2
encoding int Attachment encoding, 0=Encoding7Bit, 1=Encoding8Bit, 2=EncodingBinary, 3=EncodingBase64, 4=EncodingQuotedPrintable

The code corresponding to the download attachment is as follows:

private void downloadAttachment(IMAPSession imapSession,String folder, long uid, String partId, int encoding) {
    IMAPFetchContentOperation imapFetchContentOperation = imapSession.fetchMessageAttachmentByUIDOperation(folder, uid, partId, encoding);
    imapFetchContentOperation.setProgressListener((current, max) -> {
        if (max > 0) {
            int percent = (int) (current * 1000 / max);
            if(percent ! = downloadPercent) { downloadPercent = percent; LogUtil.d("MailCore2"."download percent:" + (percent / 10) + "%"); }}}); imapFetchContentOperation.start(new OperationCallback() {
        @Override
        public void succeeded(a) {
            byte[] data = imapFetchContentOperation.data();
            String filePath = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath() + File.separator + "xx.png";
            writeFile(filePath, data);
        }

        @Override
        public void failed(MailException e) {
            Log.d("MailCore2"."failed() called with: e = [" + e + "]"); }}); }private void writeFile(String path, byte[] bytes) {
    File file = new File(path);
    if (file.exists()) {
        file.delete();
    }
    try (FileOutputStream outputStream = new FileOutputStream(file)) {
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
        bufferedOutputStream.write(bytes);
        bufferedOutputStream.flush();
        bufferedOutputStream.close();
    } catch(Exception e) { e.printStackTrace(); }}Copy the code

The full demo can be found at github.com/kongpf8848/…

ews-java-api

Microsoft Java class library for accessing Exchange Web Services. A subsequent article will be devoted to analyzing the EWS class library 😄😄😄

The Exchange Web Services (EWS) Java API provides a managed interface for developing Java applications that use EWS. By using the EWS Java API, you can access almost all the information stored in an Office 365, Exchange Online, or Exchange Server mailbox.

Making: github.com/OfficeDev/e…

ews-cpp

C++11 header-only library for Microsoft Exchange Web Services.

Making: github.com/otris/ews-c…

ical4j

A Java library for parsing and building iCalendar data models

Making: github.com/ical4j/ical…

JavaMail

The JavaMail API provides a platform-independent and protocol-independent framework to build mail and messaging applications. The JavaMail API is available as an optional package for use with the Java SE platform and is also included in the Java EE platform.

Making: github.com/javaee/java…

Official site: javaee. Making. IO/javamail

use

For the Java Maven program, add the following code to the POM.xml file:

<dependency>
    <groupId>com.sun.mail</groupId>
    <artifactId>javax.mail</artifactId>
    <version>1.6.2</version>
</dependency>
Copy the code

For Java Gradle programs, add the following code to the build. Gradle file:

implementation 'com. Sun. Mail: javax.mail. Mail: 1.6.2'
Copy the code

For Android applications, add the following code to the build.gradle file:

android {
    packagingOptions {
        pickFirst 'META-INF/LICENSE.txt'
        exclude 'META-INF/LICENSE.md'
        exclude 'META-INF/NOTICE'
        exclude 'META-INF/NOTICE.md'
        exclude 'META-INF/NOTICE.txt'
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'com. Sun. Mail: android E-mail: 1.6.7'
    implementation 'com. Sun. Mail: android - activation: 1.6.7'
}
Copy the code

k9-mail

Open Source Email App for Android

GitHub: github.com/k9mail/k-9

Official website: k9mail.app

Other resources

The name of the link The picture
GMail mail.google.com
Outlook outlook.live.com/owa
QQ email mail.qq.com
NetEase Mailbox Master dashi.163.com
Spike www.spikenow.com
Spark sparkmailapp.com
Hey www.hey.com