Harus cepat. Sinkronisasi Email IMAP cepat

Halo! Saya Ilya. Dua tahun lalu, saya bergabung dengan klien seluler IMAP. Versi aplikasi yang lebih lama mengunduh daftar surat untuk waktu yang lama dan menghabiskan banyak lalu lintas untuk memperbarui kotak surat. Muncul pertanyaan tentang mengoptimalkan pekerjaan dengan protokol dan tentang kemampuan protokol ini secara umum. Saya tidak tahu apa-apa tentang protokol dan terjun membaca dokumentasi. Ternyata selama ini klien menggunakan protokol tanpa istirahat dan sama sekali tidak memperhitungkan fitur implementasi. Fitur-fitur ini membantu mempercepat unduhan surat sebanyak 2 hingga 3 kali. Tentang apa itu IMAP dan apa chip untuk mengoptimalkannya nanti di artikel saya.

Saya tidak akan menyelami protokol terlalu dalam. Sebuah artikel dari kategori “Saya ingin membaca artikel ini dua tahun yang lalu.” Guru IMAP tidak mungkin menemukan informasi baru untuk diri mereka sendiri. Artikel ini bergantung pada deskripsi protokol dari RFC 3501 .

Koneksi server


IMAP adalah protokol stateful. Ini adalah penemuan bagi saya, sebelum itu saya belum melihat atau bekerja dengan protokol semacam itu. Pertimbangkan skema bekerja dengan server. 


Mari kita mulai, dan yang paling penting, dengan contoh-contoh. Pertama, Anda perlu membuat koneksi ke server. Untuk melakukan ini, gunakan perpustakaan openSSL.

openssl s_client -connect imap.server.com:993 -crlf 

Hebat, koneksi dibuat dan Anda dapat mengamati respons OK dengan garis yang dimulai dengan respons KAPABILITAS

OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE  SPECIAL-USE AUTH=PLAIN AUTH=LOGIN]

Ada lembar contekan yang nyaman untuk masing - masing KAPABILITAS , di mana semua kemungkinan nilai KAPABILITAS ditulis dengan tautan ke RFC. Misalnya, IMAP4rev1 memberi tahu klien bahwa server berfungsi sesuai dengan standar IMAP4, dan IDLE memberi sinyal bahwa Anda dapat berlangganan perubahan yang terjadi di kotak surat.

Otorisasi server


Setelah terhubung ke server, Anda harus pergi ke kotak surat Anda. Ini dilakukan dengan menggunakan perintah LOGIN.

a1 LOGIN email pass

Jadi, berhenti, masuk, saya mengerti, dan apa ini? - Mungkin Anda bertanya. Dan ini adalah tag tim. Untuk kepentingan klien, tag harus berbeda, karena responsnya datang dengan tag yang sama dengan permintaan, yang berarti bahwa tag tersebut dapat dicocokkan untuk penguraian antar tim. Server juga dapat mengembalikan respons dengan tanda bintang di awal, seperti * OK, ini disebut respons yang tidak ditandai. Pada dasarnya, jawaban seperti itu dikembalikan untuk tim yang mengharapkan beberapa entitas dalam respons, misalnya, DAFTAR. 

Permintaan Daftar Folder


Untuk meminta daftar surat dalam folder, Anda harus mencari tahu folder-folder ini terlebih dahulu. Ini dilakukan oleh perintah LIST. Perintah ini mengembalikan daftar folder di server.

A2 LIST «» *
* LIST (\HasNoChildren \Trash) «/» Trash
* LIST (\HasNoChildren \Sent) «/» Sent
* LIST (\HasNoChildren \Drafts) «/» Drafts
* LIST (\HasNoChildren \Junk) «/» Junk
* LIST (\HasNoChildren) «/» INBOX
A2 OK List completed (0.001 + 0.000 + 0.001 secs).

Parameter pertama dalam perintah ini adalah namespace. Jika server mendukung namespace, maka nilainya dapat diminta menggunakan kueri NAMESPACE. Namespace standar terlihat seperti string kosong. Selanjutnya, parameter wildcard mulai digunakan. Dengan itu, kita dapat memberi tahu server folder mana yang harus kita kembalikan. Sebagai contoh, kita bisa mendapatkan: cabang folder pohon, hanya root, atau hanya semuanya, seperti pada contoh di atas. Lebih baik tidak melakukan ini, karena siapa yang tahu berapa banyak folder yang dimiliki pengguna di dalam kotak. Penulis protokol merekomendasikan menggunakan "%" - dalam hal ini Anda akan mendapatkan semua folder tingkat atas dari kotak surat. 

Dari jawabannya, kami memahami bahwa ini adalah jawaban yang tidak ditandai di mana setiap baris adalah folder Anda di dalam kotak. Pada awalnya ada bendera tempat kita membaca meta-informasi folder, misalnya, dalam contoh semua folder tidak memiliki keturunan dan beberapa folder tujuan khusus (seperti Sampah, Sampah, dll.). Selanjutnya adalah karakter pemisah folder. Simbol ini digunakan untuk subfolder. Misalnya, untuk keturunan folder Sampah, namanya akan terlihat seperti "Sampah / Folder Baru". Setelah semua folder, server akan kembali kepada kami dengan tag yang kami tetapkan untuk perintah dan waktu eksekusi dari perintah ini.  

Pemilihan folder


Selanjutnya sesuai dengan skema, kita harus memilih folder dari mana kita akan mengencangkan pesan kita. Ini dilakukan dengan menggunakan perintah SELECT.

4 SELECT INBOX
* FLAGS (\Answered \Flagged \Deleted \Seen \Draft $Forwarded $MDNSent)
* OK [PERMANENTFLAGS (\Answered \Flagged \Deleted \Seen \Draft $Forwarded $MDNSent \*)] Flags permitted.
* 16337 EXISTS
* 2 RECENT
* OK [UNSEEN 6037] First unseen.
* OK [UIDVALIDITY 1532079879] UIDs valid
* OK [UIDNEXT 17412] Predicted next UID
* OK [HIGHESTMODSEQ 21503] Highest
4 OK [READ-WRITE] Select completed (0.015 + 0.000 + 0.014 secs).

Ketika Anda memilih folder, semua informasi tentang itu dikembalikan. Mari kita mulai.

  • Jawab dengan bendera yang diizinkan di dalam folder untuk surat.  
  • Jawab dengan tanda bahwa klien dapat berubah selamanya
  • Balas dengan jumlah huruf dalam folder
  • Jawabannya adalah dengan jumlah surat terbaru, yaitu surat-surat yang kami terima di antara pilihan folder
  • Balas dengan jumlah pesan yang belum dibaca

Nah, untuk sekarang, mari kita bahas ini. Sisa informasi yang tidak kita butuhkan.

Minta surat


Sekarang hal yang paling menarik adalah permintaan surat. Anda harus sangat berhati-hati di sini, terutama pada klien seluler. Setuju, kecil kemungkinan bahwa ketika Anda memasukkan aplikasi Anda akan menerima ribuan pesan dari server ke database Anda. Selain itu, tidak masuk akal untuk mengunduh seluruh surat, karena mungkin tidak praktis untuk menampilkan, misalnya, daftar semua surat. Misalnya, untuk menampilkan huruf pengguna dengan cepat, kami hanya akan meminta "amplop". Dalam amplop ini kami ingin melihat: pengirim, penerima, subjek surat dan tanggal pengiriman. Kami akan memuat 10 posting pertama.

5 FETCH 16337:16327 (ENVELOPE)

Kolon menyebutkan segmen dari jumlah huruf yang ingin kami terima, dan dalam tanda kurung apa yang ingin kami baca dari surat-surat ini, dalam hal ini, amplop surat.

Saya akan memberikan jawabannya dalam bentuk singkat:

* 16334 FETCH (ENVELOPE ("Sat, 07 Sep 2019 23:07:48 +0000" "Hello from Fabric.io" (("Fabric" NIL "notifier" "fabric.io")) (("Fabric" NIL "notifier" "fabric.io")) (("Fabric" NIL "notifier" "fabric.io")) ((NIL NIL "me" "me@mail")) NIL NIL NIL "<5d7438441b07c_2d872ad30967b9646405c6@answers-notifier2012.mail>"))

Jelas bahwa tidak ada yang jelas. Dan masalahnya adalah bahwa format amplop ditentukan oleh RFC 2822. Saya tidak akan mempertimbangkannya dalam artikel ini. Amplop ini memiliki semua informasi yang diperlukan: tanggal penerimaan surat, subjek surat, pengirim, penerima, dan bahkan messageId. Kliennya gunakan untuk menampilkan percakapan.

Jadi, kami dapat menunjukkan informasi dasar kepada pengguna tentang surat itu, tetapi bagaimana dengan badannya?
Kita dapat segera mengunduh seluruh isi surat itu, terlepas dari ukurannya, ini tentu saja tidak lama tetapi tetap mahal melalui jaringan dan memori. Omong-omong, ini dilakukan dengan perintah FETCH yang sama. 

6 FETCH 16337:16327 (BODY[]) 

Coba perintah seperti itu di kotak masuk Anda, dan Anda akan mengerti apa yang saya maksud dengan "mahal", bahkan dengan 10 pesan kami mendapatkan respons yang cukup banyak dengan benar-benar semua informasi tentang surat itu. Berbicara tentang dia.

Seberapa sering Anda mengunduh sumber surat di klien mana pun yang Anda kenal untuk melihat tampilannya dalam bentuk aslinya? Jika tidak, mari kita ambil surat ujian darinya. Di dalamnya, saya menambahkan gambar langsung ke surat dan gambar sebagai lampiran. Simpan dalam format eml, lalu buka dengan editor teks apa pun. Bergantung pada klien, Anda akan menerima berbagai sumber surat, tetapi secara umum mereka akan serupa. 

Mari kita mulai dengan tajuk email:

Return-Path: <myemail>
Delivered-To:myemail
Received: from localhost (localhost [127.0.0.1])
	byimap.server.com (imap.server.com) with ESMTP id 6C2BE2A0363
	for <myemail>; Sun,  8 Sep 2019 23:41:29 +0300 (MSK)
X-Virus-Scanned: amavisd-new at imap.server.com
Received: from imap.server.com ([127.0.0.1])
	by localhost ( imap.server.com [127.0.0.1]) (amavisd-new, port 10026)
	with ESMTP id abx8HQQT_k5A for <myemail>;
	Sun,  8 Sep 2019 23:41:29 +0300 (MSK)
Mime-Version: 1.0
Date: Sun, 08 Sep 2019 20:41:28 +0000
Content-Type: multipart/mixed;
 boundary=»--=_Part_722_554093397.1567975288»
Message-ID: <9e4e3872e603eac2c20f26bb1d65548d>
From: "Me" <myemail>
Subject: Hey, Habr!
To: myemail
X-Priority: 3 (Normal)

Semua meta-informasi dijelaskan di header surat, dari siapa, kepada siapa, kapan, jenis konten pesan, subjek dan prioritas surat itu. Bidang batas menunjukkan batas surat itu.

Lebih memahami apa artinya ini.

----=_Part_722_554093397.1567975288
Content-Type: multipart/related;
 boundary=»--=_Part_583_946112260.1567975288»
----=_Part_583_946112260.1567975288
Content-Type: multipart/alternative;
 boundary=»--=_Part_881_599167713.1567975288»
----=_Part_881_599167713.1567975288
Content-Type: text/plain; charset=«utf-8»
Content-Transfer-Encoding: quoted-printable
----=_Part_881_599167713.1567975288
Content-Type: text/html; charset=«utf-8»
Content-Transfer-Encoding: quoted-printable
<!DOCTYPE html><html><head><meta http-equiv=3D"Content-Type" content=3D"t=
ext/html; charset=3Dutf-8" /></head><body><div data-crea=3D"font-wrapper"=
 style=3D«font-family: XO Tahion; font-size: 16px; direction: ltr»> <img =
src=3D"cid:jua-uid-q1nz1guinitrcfd3-1567975257318"><br><br><div></div> <b=
r> </div></body></html>
----=_Part_881_599167713.1567975288--
----=_Part_583_946112260.1567975288
Content-Type: image/jpeg; name=«2018-09-04 22.46.36.jpg»
Content-Disposition: inline; filename=«2018-09-04 22.46.36.jpg»
Content-ID: <jua-uid-q1nz1guinitrcfd3-1567975257318>
Content-Transfer-Encoding: base64

Setiap batas adalah batas yang biasa dari sebuah tulisan. Mereka mulai dengan dua tanda hubung "-". Batas penutupan memiliki dua tanda hubung ini pada akhirnya. Ini dijelaskan secara lebih rinci dalam RFC1341.

Ini dapat disebut bagian utama surat, bagian surat dan jenis MIME mereka dijelaskan di sini.

Tentang Jenis MIME
MIME- , MIME (Multipurpose Internet Mail Extensions) email . 

  • multipart/mixed , . 

  • multipart/related , , , 

  • multipart/alternative , , , text/plain text/html, . 


Kami tidak memiliki teks sederhana di sini, jadi lebih logis untuk mengambil representasi html. Dalam html-representasi ini hanya ada gambar, dengan parameter Content-Disposition: inline, yaitu, ia terletak langsung di badan surat, dan tidak dalam dokumen yang dilampirkan.

Tautan ke gambar ini tidak cukup sederhana. Ini dijelaskan oleh parameter Content-ID, yang sama dengan jua-uid-q1nz1guinitrcfd3-1567975257318 . Ini adalah tautan ke bagian selanjutnya dari surat itu - sebuah gambar yang dikodekan dalam basis-64. Untuk menyelamatkanku, aku tidak memasukkan semua kode base-64.

Bagian terakhir surat itu berbentuk 

----=_Part_722_554093397.1567975288
Content-Type: image/png; name=«2018-07-02 11.08.23 pm.png»
Content-Disposition: attachment; filename=«2018-07-02 11.08.23 pm.png»
Content-Transfer-Encoding: base64

yang sudah memiliki Content-Disposition bukan inline, seperti gambar di atas, tetapi lampiran. Gambar ini seharusnya hanya pergi ke panel lampiran file, dengan cara itu juga dikodekan dalam basis-64 dan memiliki ukuran besar. Di sini menjadi jelas bahwa Anda tidak boleh sekali lagi memuat seluruh isi surat jika kami hanya ingin menunjukkan informasi dasar. 

Kembali ke protokol


Setelah mengerjakan surat-surat itu, Anda harus menutup folder yang dipilih dan mengucapkan selamat tinggal kepada server. Untuk menutup folder, kita harus memasukkan perintah TUTUP. Ya, sangat sederhana


7 CLOSE
7 OK Close completed (0.001 + 0.000 secs).

Ngomong-ngomong, jika Anda bekerja dengan konsol secara paralel dengan saya dan membaca artikel, maka peristiwa yang tidak begitu menyenangkan bisa terjadi, server dapat menutup koneksi Anda dengan batas waktu. Ini sepenuhnya normal, dan setiap server memiliki batas waktu sendiri, misalnya, kami memiliki 30 menit. 
Oleh karena itu, disarankan untuk melakukan perintah NOOP di latar belakang

1 NOOP
1 OK NOOP completed (0.001 + 0.000 secs).

Secara harfiah tidak melakukan apa-apa, tetapi memungkinkan Anda untuk menjaga koneksi tanpa batas waktu sebanyak yang kita butuhkan. Jika saat ini Anda memilih folder, NOOP dapat berfungsi sebagai permintaan berkala untuk perubahan pada folder ini 

1 NOOP
* 16472 EXPUNGE
* 16471 EXPUNGE
* 16472 EXISTS
* 1 RECENT
1 OK NOOP completed (0.004 + 0.000 + 0.003 secs).

Di sini, sebagai respons, kami diberitahu dua pesan yang dihapus, satu pesan baru dan jumlah pesan di folder ini adalah 16 472.

Saya juga mencatat bahwa Anda hanya dapat bekerja dengan satu folder yang dipilih, tidak ada kerja paralel di sini.

Nah, pada akhirnya, tutup sesi dengan server dan kami akan mengucapkan selamat tinggal padanya.

8 LOGOUT
* BYE Logging out
8 OK Logout completed (0.001 + 0.000 secs).

Kami melihat jawaban BYE yang sedih tanpa tanda, yang berarti ini saatnya untuk menyelesaikan pekerjaan.

Sinkronisasi cepat dengan CONDSOTORE dan QRESYNC


Anda dapat menggunakan operasi NOOP untuk melacak perubahan dalam kotak di folder yang dipilih. Tetapi bagaimana jika kita ingin mengetahui apa yang telah berubah di folder saat kita bekerja dengan yang lain? Opsi yang paling jelas adalah memilah-milah semua huruf di penyimpanan lokal, apakah itu cache atau database, dan membandingkan dengan apa yang akan dikembalikan oleh server. Di satu sisi, ini memang solusi, dan di beberapa server itu akan menjadi satu-satunya yang benar. Di sisi lain, kami ingin menunjukkan surat secepat yang umumnya dimungkinkan oleh protokol. Untungnya, server kami mendukung ekstensi protokol seperti CONDSTORE dan QRESYNC, yang ditambahkan ke RFC7162. Yang pertama menambahkan nomor 63-bit khusus ke pesan dan folder, yang disebut mod-sequence, yang meningkat dengan setiap operasi pada surat ini. Mod-urutan tertinggi di antara semua pesan ditambahkan ke folder. Akibatnya, setiap kali Anda terhubung ke folder di server yang mendukung CONDSTORE, kami dapat dengan mudah mengetahui apakah ada sesuatu yang berubah atau tidak, hanya dengan membandingkan nilai mod-sequence untuk folder lokal dan server.

Selain itu, ekstensi ini menambahkan parameter tambahan untuk perintah STORE dan FETCH - CHANGEDSINCE mod-sequence dan UNCHANGEDSINCE mod-sequence, yang memungkinkan Anda untuk melakukan operasi jika urutan mod dari pesan yang dikirimkan masing-masing lebih besar dan lebih kecil dari ini. Mari kita lihat sebuah contoh.

FETCH 17221:17241 (UID) (CHANGEDSINCE 0)
* OK [HIGHESTMODSEQ 22746] Highest
* 17222 FETCH (UID 18319 MODSEQ (22580))
* 17223 FETCH (UID 18320 MODSEQ (22601))
* 17224 FETCH (UID 18324 MODSEQ (22607))
* 17225 FETCH (UID 18325 MODSEQ (22604))
* 17226 FETCH (UID 18326 MODSEQ (22608))
* 17227 FETCH (UID 18327 MODSEQ (22614))
* 17228 FETCH (UID 18328 MODSEQ (22613))
* 17229 FETCH (UID 18336 MODSEQ (22628))
* 17230 FETCH (UID 18338 MODSEQ (22628))
* 17231 FETCH (UID 18340 MODSEQ (22628)
* 17232 FETCH (UID 18341 MODSEQ (22628))
* 17221 FETCH (UID 18318 MODSEQ (22583))

Saya mensimulasikan situasi di mana kita masuk ke kotak surat dan tidak tahu apa-apa tentang itu sebelumnya, yaitu, urutan mod lokal kami adalah 0. Seperti yang Anda lihat, server mengembalikan kepada kami secara umum semua pesan yang ada di kotak surat, karena sebelumnya kami tidak menerima apa pun dan tidak tahu apa-apa tentang kotak itu. Menanggapi permintaan surat UID dari CHANGEDSINCE, respons yang tidak bertanda OK juga dilengkapi dengan HIGHESTMODESEQ yang sekarang akan kami simpan, dan untuk setiap pesan MODSEQ kami.

Kami akan melakukan beberapa operasi dengan kotak surat: tambahkan huruf baru, ubah benderanya. Mari kita membuat permintaan baru tetapi dengan urutan mod sebelumnya

1 fetch 17221:* (UID FLAGS) (CHANGEDSINCE 22746)
* 17267 FETCH (UID 18378 FLAGS () MODSEQ (22753))
* 17270 FETCH (UID 18381 FLAGS (\Seen) MODSEQ (22754))
* 17271 FETCH (UID 18382 FLAGS () MODSEQ (22751))
* 17273 FETCH (UID 18384 FLAGS () MODSEQ (22750))

dan kami sudah melihat perbedaannya, alih-alih mengeluarkan 20 komunitas lama dan baru yang baru saja tiba (tanda bintang pada 17221: * berarti mengambil huruf dari angka 17221 semaksimal mungkin) kami menerima surat yang MODSEQ-nya lebih besar daripada yang sebelumnya. Ini sangat membantu untuk menyinkronkan folder yang belum pernah kami kunjungi selama beberapa waktu dan mendapatkan semacam surat yang diubah, alih-alih mencoba semua yang mungkin.

Tampaknya, jauh lebih baik? Tetapi QRESYNC membuat operasi sinkronisasi lebih cepat, ini memungkinkan Anda untuk menentukan parameter MODSEQ dan pesan UID yang kita kenal tepat saat pemilihan folder. Mari kita jelaskan dengan sebuah contoh. Pertama, QRESYNC harus diaktifkan dengan perintah ENABLE. 

1 ENABLE QRESYNC
* ENABLED QRESYNC
1 OK Enabled (0.001 + 0.000 secs).
1 SELECT INBOX (QRESYNC (0 0))
* FLAGS (\Answered \Flagged \Deleted \Seen \Draft $Forwarded $MDNSent)
* OK [PERMANENTFLAGS (\Answered \Flagged \Deleted \Seen \Draft $Forwarded $MDNSent \*)] Flags permitted.
* 17271 EXISTS
* 0 RECENT
* OK [UNSEEN 17241] First unseen.
* OK [UIDVALIDITY 1532079879] UIDs valid
* OK [UIDNEXT 18385] Predicted next UID
* OK [HIGHESTMODSEQ 22754] Highest
1 OK [READ-WRITE] Select completed (0.001 + 0.000 secs).

karena kami tidak tahu apa-apa tentang folder itu sebelumnya, server hanya mengembalikan informasi tentang folder itu kepada kami, tanpa nugget perubahannya. Misalkan kita menanyakan dua puluh pesan pertama dan mengingat UID mereka dan juga HIGHESTMODESEQ. Kami meninggalkan folder, mengirim pesan kepada diri sendiri, menghapus pesan, mengubah tanda dan kembali dengan informasi sebelumnya tentang folder

1 CLOSE
1 OK Close completed (0.001 + 0.000 secs).
1 SELECT INBOX (QRESYNC (1532079879 22754 18300:18385))
* FLAGS (\Answered \Flagged \Deleted \Seen \Draft $Forwarded $MDNSent)
* OK [PERMANENTFLAGS (\Answered \Flagged \Deleted \Seen \Draft $Forwarded $MDNSent \*)] Flags permitted.
* 17271 EXISTS
* 0 RECENT
* OK [UNSEEN 17241] First unseen.
* OK [UIDVALIDITY 1532079879] UIDs valid
* OK [UIDNEXT 18386] Predicted next UID
* OK [HIGHESTMODSEQ 22757] Highest
* VANISHED (EARLIER) 18380
* 17269 FETCH (UID 18383 FLAGS () MODSEQ (22757))
* 17271 FETCH (UID 18385 FLAGS () MODSEQ (22755))
1 OK [READ-WRITE] Select completed (0.001 + 0.000 secs).

Dan sekarang, ketika memilih folder yang diubah, kami segera mendapatkan nugget perubahan, dalam bentuk respons VANISHED (EARLIER) untuk pesan yang dihapus, dan FETCH untuk pesan yang ditambahkan atau diubah. Sekarang lebih mudah untuk menyinkronkan folder jika pengguna belum mengunjunginya untuk waktu yang lama. Ini adalah cara yang sangat keren jika Anda memiliki banyak pesan yang disimpan secara lokal di dalam cache dan Anda tidak ingin membandingkannya dengan pesan di server.

Parameter pertama dari permintaan ini adalah UIDVALIDITY, yang pada dasarnya digunakan untuk memverifikasi bahwa uid yang Anda terima sebelumnya tidak berubah dalam folder. Ini dapat terjadi jika server mengubah sesi dari setiap sesi ke sesi untuk semua pesan atau folder dihapus dan folder dengan nama yang sama dibuat di tempatnya.

Parameter kedua adalah HIGHESTMODSEQ yang diketahui oleh kami dan yang terakhir adalah interval UID yang diketahui, mereka dapat ditulis sebagai titik dua, jika intervalnya berkelanjutan, atau dipisahkan oleh koma.

Kesimpulan


Dalam contoh saya, saya menemukan sebuah situasi di mana ketidaktahuan area subjek mengarah pada operasi aplikasi yang salah dan suboptimal. Saya tidak membahas semua opsi yang mungkin untuk menggunakan protokol dengan artikel ini. Tetapi saya berharap untuk pengembang IMAP klien selanjutnya informasi di atas akan bermanfaat.

IMAP memiliki banyak hal menarik. Perintah untuk sinkronisasi cepat hanyalah permulaan, pada kenyataannya, Anda dapat lebih mengoptimalkan berbagai perintah IMAP, tergantung pada kemampuan server, dan membuat bekerja dengan email lebih cepat, lebih ekonomis pada jaringan dan memori, dan umumnya lebih menyenangkan. Tapi saya akan membicarakannya nanti.

All Articles