From 80e5404d61a903e22d7aa3591ed800d3c5847454 Mon Sep 17 00:00:00 2001 From: nick evans Date: Wed, 2 Sep 2026 09:48:50 -0400 Subject: [PATCH 1/6] =?UTF-8?q?=F0=9F=93=9A=20Update=20responses=20`frozen?= =?UTF-8?q?=5Fdup`=20configuration=20doc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This should've been updated for v0.6.0. --- lib/net/imap.rb | 2 +- lib/net/imap/config.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/net/imap.rb b/lib/net/imap.rb index 67fc83c0..da93c8d2 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -3331,7 +3331,7 @@ def idle_done # Prints a warning and returns the mutable responses hash. # This is not thread-safe. # - # [+:frozen_dup+ (planned default for +v0.6+)] + # [+:frozen_dup+ (default since +v0.6+)] # Returns a frozen copy of the unhandled responses hash, with frozen # array values. # diff --git a/lib/net/imap/config.rb b/lib/net/imap/config.rb index a144f4aa..027034aa 100644 --- a/lib/net/imap/config.rb +++ b/lib/net/imap/config.rb @@ -365,7 +365,7 @@ def self.[](config) # Prints a warning and returns the mutable responses hash. # This is not thread-safe. # - # [+:frozen_dup+ (planned default for +v0.6+)] + # [+:frozen_dup+ (default since +v0.6+)] # Returns a frozen copy of the unhandled responses hash, with frozen # array values. # From aa35482a31bd8bbacee0e52337a91f24ec5e210c Mon Sep 17 00:00:00 2001 From: nick evans Date: Wed, 2 Sep 2026 09:57:28 -0400 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=93=9A=EF=B8=8F=20Update=20`Net::IMAP?= =?UTF-8?q?`=20"What's=20here"=20documentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In addition to minor changes to a some of the documentation text, this adds some missing methods and attributes and a couple of new sections. --- lib/net/imap.rb | 65 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 16 deletions(-) diff --git a/lib/net/imap.rb b/lib/net/imap.rb index da93c8d2..f39de1ac 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -289,7 +289,9 @@ module Net # # == What's here? # - # * {Connection control}[rdoc-ref:Net::IMAP@Connection+control+methods] + # * {Client configuration}[rdoc-ref:Net::IMAP@Client+configuration] + # * {Connection control}[rdoc-ref:Net::IMAP@Connection+control] + # * {Connection attributes}[rdoc-ref:Net::IMAP@Connection+attributes] # * {Server capabilities}[rdoc-ref:Net::IMAP@Server+capabilities] # * {Handling server responses}[rdoc-ref:Net::IMAP@Handling+server+responses] # * {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands] @@ -300,40 +302,66 @@ module Net # * {for the "logout" state}[rdoc-ref:Net::IMAP@Logout+state] # * {IMAP extension support}[rdoc-ref:Net::IMAP@IMAP+extension+support] # - # === Connection control methods + # === Client configuration + # - #host: The hostname this client connected to. + # - #port: The port this client connected to. + # - #config: The client configuration. See Net::IMAP::Config. + # - #open_timeout: Delegates to {config.open_timeout}[rdoc-ref:Config#open_timeout]. + # - #idle_response_timeout: Delegates to {config.idle_response_timeout}[rdoc-ref:Config#idle_response_timeout]. + # - #max_response_size: Delegates to {config.max_response_size}[rdoc-ref:Config#max_response_size]. + # - #ssl_ctx_params: Returns the params that were sent to {`ssl_ctx.set_params`}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params]. + # + # === Connection control # # - Net::IMAP.new: Creates a new \IMAP client which connects immediately and # waits for a successful server greeting before the method returns. - # - #connection_state: Returns the connection state. # - #starttls: Asks the server to upgrade a clear-text connection to use TLS. + # + # Requires the +STARTTLS+ capability. + # + # *NOTE:* Connecting to the implicit TLS port should be preferred. # - #logout: Tells the server to end the session. Enters the +logout+ state. + # - #logout!: Calls #logout then #disconnect, converting most errors into + # warnings. # - #disconnect: Disconnects the connection (without sending #logout first). + # + # === Connection attributes + # + # - #greeting: The server's initial untagged response. + # - #connection_state: Returns the connection state. # - #disconnected?: True if the connection has been closed. + # - #tls_verified?: Returns whether TLS is used and #host has been verified. + # - #ssl_ctx: Returns the {SSLContext}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html] + # after attempting to start TLS. # # === Server capabilities # + # ==== Cached capabilities # - #capable?: Returns whether the server supports a given capability. # - #capabilities: Returns the server's capabilities as an array of strings. + # - #capabilities_cached?: Returns whether capabilities are cached. + # - #clear_cached_capabilities: Clears cached capabilities. + # + # *NOTE:* The cache is automatically cleared when capabilities can change. + # + # ==== \SASL Auth mechanisms + # # - #auth_capable?: Returns whether the server advertises support for a given # SASL mechanism, for use with #authenticate. # - #auth_mechanisms: Returns the #authenticate SASL mechanisms which # the server claims to support as an array of strings. - # - #clear_cached_capabilities: Clears cached capabilities. # - # The capabilities cache is automatically cleared after completing - # #starttls, #login, or #authenticate. - # - #capability: Sends the +CAPABILITY+ command and returns the #capabilities. + # ==== Enabled capabilities # - # In general, #capable? should be used rather than explicitly sending a - # +CAPABILITY+ command to the server. + # *NOTE:* The following require the +ENABLE+ or +IMAP4rev2+ server capability. # - #enable: Enables backwards incompatible server extensions. - # Requires the +ENABLE+ or +IMAP4rev2+ capability. # - #enabled: Returns a set of enabled server extensions. # - #enabled?: Returns whether a server extension has been enabled. # - #utf8_enabled?: Returns whether UTF-8 string encoding has been enabled. # # === Handling server responses # + # ==== Stored responses methods # - #greeting: The server's initial untagged response, which can indicate a # pre-authenticated connection. # - #responses: Yields unhandled UntaggedResponse#data and non-+nil+ @@ -341,6 +369,8 @@ module Net # - #extract_responses: Removes and returns the responses for which the block # returns a true value. # - #clear_responses: Deletes unhandled data from #responses and returns it. + # + # ==== Response handler methods # - #add_response_handler: Add a block to be called inside the receiver thread # with every server response. # - #response_handlers: Returns the list of response handlers. @@ -364,8 +394,9 @@ module Net # # - #capability: Returns the server's capabilities as an array of strings. # - # In general, #capable? should be used rather than explicitly - # sending a +CAPABILITY+ command to the server. + # *NOTE:* Use {cached capabilities + # methods}[rdoc-ref:Net::IMAP@Server+Capabilities] instead, to avoid sending + # unnecessary commands to the server. # - #noop: Allows the server to send unsolicited untagged #responses. # - #logout: Tells the server to end the session. Enters the +logout+ state. # @@ -377,6 +408,8 @@ module Net # - #starttls: Upgrades a clear-text connection to use TLS. # # Requires the +STARTTLS+ capability. + # + # *NOTE:* Connecting to the implicit TLS port should be preferred. # - #authenticate: Identifies the client to the server using the given # {SASL mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml] # and credentials. Enters the +authenticated+ state. @@ -453,10 +486,10 @@ module Net # # ==== RFC9051: +IMAP4rev2+ # - # Although IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051] is not supported - # yet, Net::IMAP supports several extensions that have been folded into it: - # +ENABLE+, +IDLE+, +LITERAL-+, +MOVE+, +NAMESPACE+, +SASL-IR+, +UIDPLUS+, - # +UNSELECT+, STATUS=SIZE, and the fetch side of +BINARY+. + # Although IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051] is not fully + # supported yet, Net::IMAP supports several extensions that have been folded + # into it: +ENABLE+, +IDLE+, +LITERAL-+, +MOVE+, +NAMESPACE+, +SASL-IR+, + # +UIDPLUS+, +UNSELECT+, STATUS=SIZE, and the fetch side of +BINARY+. # Commands for these extensions are listed with the {Core IMAP # commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands], above. # From bce5e733fe01a47fbde5687bc5021715d175047c Mon Sep 17 00:00:00 2001 From: nick evans Date: Wed, 2 Sep 2026 09:59:50 -0400 Subject: [PATCH 3/6] =?UTF-8?q?=F0=9F=93=9A=20Add=20warning=20to=20`ssl=5F?= =?UTF-8?q?ctx`=20`ssl=5Fctx=5Fparams`=20rdoc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Emphasizes that these attributes to _NOT_ indicate a secure connection. --- lib/net/imap.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/net/imap.rb b/lib/net/imap.rb index f39de1ac..84692f86 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -311,6 +311,8 @@ module Net # - #max_response_size: Delegates to {config.max_response_size}[rdoc-ref:Config#max_response_size]. # - #ssl_ctx_params: Returns the params that were sent to {`ssl_ctx.set_params`}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params]. # + # *NOTE:* Presence does _NOT_ indicate a secure TLS connection. + # # === Connection control # # - Net::IMAP.new: Creates a new \IMAP client which connects immediately and @@ -334,6 +336,8 @@ module Net # - #ssl_ctx: Returns the {SSLContext}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html] # after attempting to start TLS. # + # *NOTE:* Presence does _NOT_ indicate a secure TLS connection. + # # === Server capabilities # # ==== Cached capabilities @@ -964,6 +968,9 @@ def max_response_size=(val) config.max_response_size = val end # is unsuccessful. The context object will be frozen. # # Returns +nil+ for a plaintext connection. + # + # *NOTE:* The presence of this attribute does _NOT_ indicate that the + # connection is using TLS. attr_reader :ssl_ctx # Returns the parameters that were sent to #ssl_ctx @@ -971,6 +978,9 @@ def max_response_size=(val) config.max_response_size = val end # when the connection tries to use TLS (even when unsuccessful). # # Returns +false+ for a plaintext connection. + # + # *NOTE:* The presence of this attribute does _NOT_ indicate that the + # connection is using TLS. attr_reader :ssl_ctx_params # Returns the current connection state. From 5a713b3e10b1fe00d5c7f3c7ffcad1cced6d52a7 Mon Sep 17 00:00:00 2001 From: nick evans Date: Tue, 8 Sep 2026 15:22:07 -0400 Subject: [PATCH 4/6] =?UTF-8?q?=F0=9F=93=9A=20Add=20line=20breaks=20to=20l?= =?UTF-8?q?ong=20lines=20in=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a1b13449..53ac3473 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,11 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. -To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). +To install this gem onto your local machine, run `bundle exec rake install`. To +release a new version, update the version number in `version.rb`, and then run +`bundle exec rake release`, which will create a git tag for the version, push +git commits and tags, and push the `.gem` file to +[rubygems.org](https://rubygems.org). ## Contributing From ac9357f567c7d5e11b060e2ea10199b9d8086608 Mon Sep 17 00:00:00 2001 From: nick evans Date: Tue, 8 Sep 2026 17:12:36 -0400 Subject: [PATCH 5/6] =?UTF-8?q?=F0=9F=93=9A=20Add=20Net::IMAP::Config=20"W?= =?UTF-8?q?hat's=20Here"=20rdoc=20section?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is especially usefor for Config, since rdoc doesn't include attributes in the sidebar method list. --- lib/net/imap/config.rb | 60 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/lib/net/imap/config.rb b/lib/net/imap/config.rb index 027034aa..72655c05 100644 --- a/lib/net/imap/config.rb +++ b/lib/net/imap/config.rb @@ -120,6 +120,66 @@ class IMAP # # *NOTE:* Updates to config objects are not synchronized for thread-safety. # + # == What's here? + # + # === \Config attributes + # + # ==== Timeouts and other limits + # + # * #open_timeout: seconds to wait for connection to open or start TLS + # * #idle_response_timeout: seconds to wait for +IDLE+ command to complete + # * max_response_size: Maximum allowed server response size. + # + # ==== Server capabilities + # + # * #sasl_ir: Controls +SASL-IR+ behavior for Net::IMAP#authenticate. + # * #enforce_logindisabled: Controls +LOGINDISABLED+ behavior in + # Net::IMAP#login. + # * max_non_synchronizing_literal: maximum bytesize for LITERAL+ / + # LITERAL- non-synchronizing literals. + # + # ==== Inherited defaults + # {Versioned defaults}[rdoc-ref:Net::IMAP@Versioned+defaults] inherit these + # from ::global and #load_defaults doesn't update them. + # + # * #debug (aliased as #debug?): whether debug mode is enabled + # + # ==== Backward compatibility + # These attributes will be removed by some future release. + # + # * #responses_without_block: Controls the behavior of Net::IMAP#responses + # when called without any arguments (+type+ or +block+). + # * #parser_use_deprecated_uidplus_data: Ignored since +v0.6.0+. + # * #parser_max_deprecated_uidplus_data_size: Ignored since +v0.6.0+. + # + # === Getting a new or existing config + # * ::global: The global config, used as the default #parent. + # * ::default: The hardcoded frozen default config, and parent of ::global. + # * ::version_defaults: Hard-coded frozen default configurations, indexed + # by version. + # * ::[]: Returns a config from ::version_defaults or created by ::new. + # * ::new: Return a new Config which inherits from a given +parent+. + # * #new: Return a new Config which inherits from +self+. + # + # === Updating multiple attributes + # * #load_defaults: Sets attributes to a given +version+'s default values. + # * #update: Assigns multiple attribute values to +self+. + # * #reset: Resets attributes to inherit from #parent. + # + # === Exporting multiple attributes + # * #to_h: Return a hash with all attributes. + # * #inspect (aliased as #to_s): Returns a string representation of + # overriden config attributes and the config inheritance chain. + # * #pretty_print: Used by PP[https://docs.ruby-lang.org/en/master/PP.html] + # to create a string representation of all config attributes and the + # inheritance chain. + # + # === Inheritance inspection + # * #parent: Returns the parent config object. + # * #inherited?: Returns whether all attributes inherit from #parent. + # * #inherits_defaults?: Returns whether all attributes inherit from a default config. + # * #overrides?: Returns whether any attributes override the #parent value. + # class Config # Array of attribute names that are _not_ loaded by #load_defaults. DEFAULT_TO_INHERIT = %i[debug].freeze From a18e4f76e3708f5a424d8518e440a9b9fe7d6143 Mon Sep 17 00:00:00 2001 From: nick evans Date: Wed, 2 Sep 2026 09:50:32 -0400 Subject: [PATCH 6/6] =?UTF-8?q?=F0=9F=93=9A=20Update=20examples=20in=20REA?= =?UTF-8?q?DME=20and=20class=20rdoc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This simplifies some of the examples to to remove unnecessary confusing bits, or to operate on message sets without looping. This also updates them to use more modern features and work in more situations. More documentation is added to the Net::IMAP class rdoc examples, linking to the various commands. The README examples have been updated to match, but the text has been intentionally left out to keep it simple and clean. These are only meant as simple examples, so they're still missing error handling that would be needed in a production system. --- README.md | 64 +++++++++++++++++++----------- lib/net/imap.rb | 103 ++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 129 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 53ac3473..661579be 100644 --- a/README.md +++ b/README.md @@ -26,41 +26,61 @@ Or install it yourself as: ### Connect with TLS to port 993 ```ruby -imap = Net::IMAP.new('mail.example.com', ssl: true) -imap.port => 993 -imap.tls_verified? => true -case imap.greeting.name -in /OK/i - # The client is connected in the "Not Authenticated" state. - imap.authenticate("PLAIN", "joe_user", "joes_password") -in /PREAUTH/i - # The client is connected in the "Authenticated" state. +hostname = "mail.example.com" +username = "user@example.com" +password = "correct-horse-battery-staple" + +imap = Net::IMAP.new(hostname, ssl: true) +imap.authenticate(:plain, username, password) +``` + +To authenticate with an OAuth2 access token: +```ruby +if imap.auth_capable?(:OAUTHBEARER) + imap.authenticate(:OAUTHBEARER, username, oauth2_token) +elsif imap.auth_capable?(:XOAUTH2) + imap.authenticate(:XOAUTH2, username, oauth2_token) +else + raise "OAuth2 not supported?" end ``` -### List sender and subject of all recent messages in the default mailbox +### List sender and subject of recent messages ```ruby imap.examine('INBOX') -imap.search(["RECENT"]).each do |message_id| - envelope = imap.fetch(message_id, "ENVELOPE")[0].attr["ENVELOPE"] - puts "#{envelope.from[0].name}: \t#{envelope.subject}" +search_result = imap.uid_search(["SINCE", Date.today - 7]) +imap.uid_fetch(search_result, "ENVELOPE").each do |fetch_data| + envelope = fetch_data.envelope + puts "#{envelope.from.first.name}: \t#{envelope.subject}" end ``` -### Move all messages from April 2003 from "Mail/sent-mail" to "Mail/sent-apr03" +### Move messages between two dates to another mailbox ```ruby -imap.select('Mail/sent-mail') -if imap.list('Mail/', 'sent-apr03').empty? - imap.create('Mail/sent-apr03') +source = "Mail/sent-mail" +destination = "Mail/sent-apr03" + +# The "BEFORE" and "AFTER" search criteria are not inclusive. +since = Date.parse("2003-04-01").prev_day +before = Date.parse("2003-05-01") + +if imap.list("", destination).empty? + imap.create(destination) end -imap.search(["BEFORE", "30-Apr-2003", "SINCE", "1-Apr-2003"]).each do |message_id| - if imap.capable?(:move) || imap.capable?(:IMAP4rev2) - imap.move(message_id, "Mail/sent-apr03") +imap.select(source) +search_result = imap.uid_search(["SINCE", since, "BEFORE", before]) +if imap.capable?(:MOVE) || imap.capable?(:IMAP4rev2) + imap.uid_move(search_result, destination) +else + # Atomic MOVE is not supported. Copy, delete, and expunge. + imap.uid_copy(search_result, destination) + imap.uid_store(search_result, "+FLAGS", [:Deleted]) + if imap.capable?(:UIDPLUS) || imap.capable?(:IMAP4rev2) + imap.uid_expunge(search_result) else - imap.copy(message_id, "Mail/sent-apr03") - imap.store(message_id, "+FLAGS", [:Deleted]) + # NOTE: This may expunge _other_ deleted messages, too. imap.expunge end end diff --git a/lib/net/imap.rb b/lib/net/imap.rb index 84692f86..7b82b48c 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -85,29 +85,100 @@ module Net # # === Examples of Usage # - # ==== List sender and subject of all recent messages in the default mailbox + # ==== Connect with TLS to port 993 + # + # Use Net::IMAP.new to open a new connection, with ssl: true for TLS. + #
+ # Use #authenticate to log in. + # + # hostname = "mail.example.com" + # username = "user@example.com" + # password = "correct-horse-battery-staple" + # + # imap = Net::IMAP.new(hostname, ssl: true) + # imap.authenticate(:plain, username, password) + # + # To authenticate with an OAuth2 access token: + # if imap.auth_capable?(:OAUTHBEARER) + # imap.authenticate(:OAUTHBEARER, oauth2_token:) + # elsif imap.auth_capable?(:XOAUTH2) + # imap.authenticate(:XOAUTH2, oauth2_token:) + # else + # raise "OAuth2 not supported?" + # end + # + # See #authenticate for other supported authentication mechanisms. + # + # ==== List sender and subject of recent messages + # + # Use #examine to open a mailbox with read-only access.
+ # Use #uid_search for a list of UIDs (or #search for sequence numbers).
+ # Use #uid_fetch (or #fetch) to read message attributes, such as "envelope". + # + # Search returns a SearchResult or ESearchResult, which is coercible to + # SequenceSet so it can be used directly as a message set argument for other + # commands. The first #uid_fetch argument is the set of message UIDs + # (sequence numbers for #fetch). Fetch returns an array of FetchData (or + # UIDFetchData when +UIDONLY+ is enabled). # - # imap = Net::IMAP.new('mail.example.com') - # imap.authenticate('PLAIN', 'joe_user', 'joes_password') # imap.examine('INBOX') - # imap.search(["RECENT"]).each do |message_id| - # envelope = imap.fetch(message_id, "ENVELOPE")[0].attr["ENVELOPE"] - # puts "#{envelope.from[0].name}: \t#{envelope.subject}" + # search_result = imap.uid_search(["SINCE", Date.today - 7]) + # imap.uid_fetch(search_result, "ENVELOPE").each do |fetch_data| + # envelope = fetch_data.envelope + # puts "#{envelope.from.first.name}: \t#{envelope.subject}" # end # - # ==== Move all messages from April 2003 from "Mail/sent-mail" to "Mail/sent-apr03" + # ==== Move messages between two dates to another mailbox # - # imap = Net::IMAP.new('mail.example.com') - # imap.authenticate('PLAIN', 'joe_user', 'joes_password') - # imap.select('Mail/sent-mail') - # if not imap.list('Mail/', 'sent-apr03') - # imap.create('Mail/sent-apr03') + # Use #list to check if the destination mailbox exists.
+ # Use #create to create a missing destination mailbox.
+ # Use #select to open the source mailbox with read-write access.
+ # Use #uid_search (or #search) to search for messages within a date range.
+ # Use #uid_move (or #move) to atomically move messages to another mailbox. + # + # *NOTE:* Most servers support atomic +MOVE+, but not all do. + # source = "Mail/sent-mail" + # destination = "Mail/sent-apr03" + # + # # The "BEFORE" and "AFTER" search criteria are not inclusive. + # since = Date.parse("2003-04-01").prev_day + # before = Date.parse("2003-05-01") + # + # if imap.list("", destination).empty? + # imap.create(destination) # end - # imap.search(["BEFORE", "30-Apr-2003", "SINCE", "1-Apr-2003"]).each do |message_id| - # imap.copy(message_id, "Mail/sent-apr03") - # imap.store(message_id, "+FLAGS", [:Deleted]) + # imap.select(source) + # search_result = imap.uid_search(["SINCE", since, "BEFORE", before]) + # imap.uid_move(search_result, destination) + # + # When atomic +MOVE+ is not supported, the messages can be copied and deleted. + # \IMAP message deletion requires two steps: set \Deleted flag to + # mark a message for deletion, then expunge the \Deleted messages. + # + # Use #uid_copy (or #copy) to copy messages to another mailbox.
+ # Use #uid_store (or #store) to mark messages for deletion.
+ # Use #uid_expunge (or #expunge) to remove deleted messages. + # + # *NOTE:* #uid_expunge is not supported by every server, and #expunge removes + # _all_ \Deleted messages in the mailbox, even if the + # \Deleted flag was added by another session. + # + # if imap.capable?(:MOVE) || imap.capable?(:IMAP4rev2) + # imap.uid_move(search_result, destination) + # else + # # Atomic MOVE is not supported. Copy, delete, and expunge. + # imap.uid_copy(search_result, destination) + # imap.uid_store(search_result, "+FLAGS", [:Deleted]) + # if imap.capable?(:UIDPLUS) || imap.capable?(:IMAP4rev2) + # imap.uid_expunge(search_result) + # else + # # NOTE: This may expunge _other_ deleted messages, too. + # imap.expunge + # end # end - # imap.expunge + # + # Additional error handling may be required for non-atomic moves. Smaller + # batch sizes are recommended. # # == Capabilities #