IMAPは、クライアントがサーバー上のメールボックス、メールの状態、およびコンテンツを操作することを可能にし、複数の端末で共通のビューを維持するのに適しています。POP3は主に、メールドロップ内のメッセージの列挙、ダウンロード、および削除を提供します。両方ともメールをローカルにキャッシュすることができ、「ダウンロードしたらすぐに削除しなければならない」というわけではありません。真の違いは、サーバーがクライアントに提供できる同期可能な状態と操作の量にあります。
能力 IMAP POP3
メッセージの整理 複数メールボックス、サーバー側状態と検索 現在のメールドロップのメッセージリスト中心
読み取り ヘッダー、本文の一部、または添付ファイルの一部のみ取得可能 RETRで全体を取得;オプションでTOPによりヘッダーと一部行を取得
削除 マークとクリアが分離されており、MOVEなどの操作も存在 DELEでマークし、通常のQUITで更新フェーズへ移行
複数端末の一貫性 UID、flags、同期拡張などのメカニズムあり 同等レベルの共有フォルダ/既読同期モデルは存在しない
オフライン ダウンロード済みコンテンツとローカル操作キューに依存 ダウンロード後にオフラインで読み取り可能
通知 IDLEでメールボックス変更の通知を受信 基本プロトコルでは通常、クライアントによるポーリングが必要
本稿では、IMAP4rev2(RFC 9051)を主要な意味論の根拠とし、IMAP4rev1サービスに遭遇した場合はCAPABILITYに基づいて拡張機能を判断し、新旧のレスポンス例を安易に混用しないようにします。RFC 9051
IMAPセッション:ログインしてもメールボックスが選択されたわけではない
接続は、未認証、認証済み、メールボックス選択済み、退出などの状態を経験します。LISTは認証後にメールボックスを列挙し、SELECTは読み書き可能なメールボックスを選択し、EXAMINEは読み取り専用ビューを取得します。FETCH、STOREなどは選択されたメールボックスに対して機能します。認証が成功しても、すべての後続コマンドが自動的にINBOXを対象とするわけではありません。
コマンドにはクライアントタグ(例:a003)が付与されます。同じタグを持つ最終的なOK/NO/BADは、そのコマンドが完了したことを示します。* で始まる未タグ付けレスポンスは、データや状態の変化を報告するものであり、別のコマンドの最終応答ではありません。+ は入力の継続を促すプロンプトです。クライアントパーサーは、レスポンスが交差するケースを処理すべきであり、「1行読み取った」ことを呼び出しの終了と見なしてはいけません。
例 解釈方法
* 42 EXISTS現在のメールボックスに42通のメールがあることを意味し、新たに42通追加されたことを意味しません
* OK [UIDVALIDITY 700]現在のUID名前空間の有効性値
* OK [UIDNEXT 109]将来のUIDの予測境界であり、次のメールがちょうど109であることを保証するものではありません
a003 OK [READ-WRITE] SELECT completed選択コマンドが完了し、メールボックス操作を継続できることを示す
旧IMAP4rev1のRECENT / \Recentはrev2で廃止されました。また、それは元々、全デバイスで一貫した未読数として直接扱うことはできませんでした。未読数は通常、\Seen フラグが存在するかどうかに基づいて判断されます。
シーケンス番号、UID、UIDVALIDITY:キャッシュキーが3つの要素を必要とする理由
メッセージシーケンス番号は、現在のメールボックス内の位置であり、1から始まります。前のメールがクリアされると、後ろのメールのシーケンス番号は前に移動します。UIDは、同じメールボックス内で同じUIDVALIDITYの下では安定して増分しますが、ギャップ(欠番)が生じる可能性があります。それはアカウント全体のグローバルメッセージIDではありません。
削除は現在の位置を変更するが、生存メールのそのUIDVALIDITYにおけるUIDは変更されない
シーケンス番号 1
UID 101
シーケンス番号 2
UID 104
シーケンス番号 3
UID 108
シーケンス番号 1
UID 104
シーケンス番号 2
UID 108
元のシーケンス番号1 / UID 101 を削除
UIDはメールボックスとUIDVALIDITYのスコープに属します。メールボックスを跨いで移動した後のUIDは、そのまま引き継ぐことはできません。
クライアントがシーケンス番号2がUID 104に対応しているのを見て、別のクライアントがシーケンス番号1を削除したと仮定します。記憶した位置に基づいて STORE 2 を実行し続けると、現在指し示しているのはすでにUID 108になっている可能性があります。同期ツールはUID操作と完全なスコープを使用し、受信した削除やシーケンス番号の変化情報をタイムリーに更新する必要があります。
uids = [ 101 , 104 , 108 ]
remembered_sequence = 2
remembered_uid = uids [ remembered_sequence - 1 ]
assert remembered_uid == 104
uids . pop ( 0 ) assert uids [ remembered_sequence - 1 ] == 108
assert uids . index ( remembered_uid ) + 1 == 1
old_key = ( ' INBOX' , 700 , 104 )
new_epoch_key = ( ' INBOX' , 701 , 104 )
assert old_key != new_epoch_key
print ( ' シーケンス番号の移動とUIDVALIDITYによるキャッシュ隔離モデルは正常に動作' )
実際のキャッシュキーには、アカウント/サーバー識別子も含まれるべきです。UIDVALIDITY が変更された場合、古いUIDマッピングはサーバー上のメールを特定するために引き続き使用することはできません。古いオフライン削除コマンドを新しいUID 104に直接適用するのではなく、再列挙して関連付けを構築する必要があります。コピーまたは別のメールボックスへの移動後、ターゲットのUIDも変更される可能性があります。サポートされているUIDマッピングレスポンスは、キャッシュの更新に役立ちます。UIDPLUS RFC 4315
UIDNEXT とアスタリスク範囲の詳細
UIDNEXT の値は将来のUID割り当てを予測するために使用されますが、UIDは飛び番になる可能性があります。もう一つの誤解されやすい点は、範囲の端点が逆順になることを許可している点です。メールボックスの最大UIDが108の場合、UID FETCH 109:* におけるアスタリスクは最大のUIDを表し、常に空集合を返すと単純に理解してはいけません。クライアントは適切な検索/同期メソッドを使用し、返されたUIDが実際にローカルの水位(直近の処理済み位置)よりも高いかどうかを検証する必要があります。
存在しない指定UIDは通常無視され、コマンドのOKは対象メールが見つかったことを保証するものではありません。したがって、「操作が完了した」「対象が存在する」「対象の状態が期待通りである」は、3つの異なる観察事項です。
1通目を削除した後、シーケンス番号2は同じメールですか?
1通目をクリアすると、シーケンス番号2はUID108を指すようになり、UID104はシーケンス番号1に移動します。メールボックスを再構築しても、104が再び現れたとしても、古いキャッシュキーを再利用することはできません。変化するのはリストの並べ替えだけでなく、アイデンティティのスコープそのものです。
図を準備しています 再読み込み
1通目を削除した後、シーケンス番号2は同じメールですか? シーケンス番号は現在の位置に合わせて移動し、UIDはメールボックスとUIDVALIDITYのスコープに依存します。数字が同じだからといって、オブジェクトの同一性が同じであるわけではありません。
{"id":"imap-position-identity","title":"1通目を削除した後、シーケンス番号2は同じメールですか?","summary":"シーケンス番号は現在の位置に合わせて移動し、UIDはメールボックスとUIDVALIDITYのスコープに依存します。数字が同じだからといって、オブジェクトの同一性が同じであるわけではありません。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"actions\"><button id=\"expunge\" data-i18n=\"expunge\"></button><button id=\"epoch\" data-i18n=\"epoch\"></button><button id=\"reset\" data-i18n=\"reset\"></button></div><section class=\"panel\"><h3 data-i18n=\"mailbox\"></h3><div id=\"mailbox\"></div></section><div class=\"metrics\"><div><span data-i18n=\"seq\"></span><strong id=\"seq\"></strong></div><div><span data-i18n=\"uid\"></span><strong id=\"uid\"></strong></div></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px var(--viz-font-ui);padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 var(--viz-font-ui);overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n#mailbox .tr>*{min-height:4.8em}.metrics strong{font-size:16px}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}let epoch=700,uids=[101,104,108];function draw(){table('mailbox',[['sequence','UID','epoch'],...[0,1,2].map(i=>[uids[i]?i+1:'—',uids[i]||'—',uids[i]?epoch:'—'])]);set('seq',uids[1]?'UID'+uids[1]+' @'+epoch:'∅');set('uid',epoch!==700?'INVALID SCOPE':uids.includes(104)?'UID104 @700':'∅');$('#expunge').disabled=!uids.length;$('#epoch').disabled=epoch!==700;$('#mailbox').dataset.snapshot=JSON.stringify({epoch,uids});}$('#expunge').onclick=()=>{uids.shift();draw()};$('#epoch').onclick=()=>{epoch=701;uids=[101,104,108];draw()};$('#reset').onclick=()=>{epoch=700;uids=[101,104,108];draw()};draw();","audio":false,"strings":{"epoch":"メールボックスを再構築し、UIDVALIDITYを変更","expunge":"現在の1通目をクリア","heading":"1通目を削除した後、シーケンス番号2は同じメールですか?","intro":"クライアントが記憶しているシーケンス番号とUIDを保持したまま、1通目をクリアするか、UIDVALIDITYを切り替えます。","mailbox":"現在のメールボックスオブジェクト","reset":"リセット","scope":"INBOXと同一アカウントを固定し、UID101/104/108、初期UIDVALIDITYは700、シーケンス番号2/UID104を記憶。世代交代後、同じ数字は新しいオブジェクトを意味し、古いオフライン操作を自動的に適用してはいけません。","scopeLabel":"モデル範囲と規約","seq":"記憶したシーケンス番号2で位置指定","uid":"古いスコープUID104で位置指定"}}
コンテンツの取得、フラグの変更、バイト単位の読み取り
UID FETCH 104 (UID FLAGS RFC822.SIZE BODY.PEEK[HEADER.FIELDS (FROM SUBJECT DATE)]) により、表示リストに必要なフィールドのみを取得でき、大きな添付ファイル全体をダウンロードする必要がありません。BODY.PEEK[...] はコンテンツの読み取りによって暗黙的に \Seen を設定しません。通常の BODY[...] の対応する取得動作は、これを設定する可能性があります。
サーバーはコンテンツを返す際に {N} リテラルを使用できます。Nは、それに続く生バイト数を示します。本文には改行、括弧、またはレスポンスのように見えるテキストが含まれている可能性があるため、パーサーは正確にNバイトを読み取り、その後でプロトコル解析を再開する必要があります。右括弧を探すために単純に行単位で処理してはいけません。
payload = ' 件名:ステータス\r \n ' . encode ( ' utf-8' )
assert len ( payload ) == 17 wire = b ' { 17} \r \n ' + payload + b ' )\r \n a004 OK done\r \n '
header , rest = wire . split ( b ' \r \n ' , 1 )
size = int ( header [ 1 : - 1 ] )
literal , tail = rest [ : size ] , rest [ size : ]
assert literal . decode ( ' utf-8' ) == ' 件名:ステータス\r \n '
assert tail . startswith ( b ' )\r \n a004 OK' )
assert len ( literal ) != len ( literal . decode ( ' utf-8' ) )
print ( ' UTF-8リテラルのバイト単位の区切りは正常に動作' )
既読マークは UID STORE 104 +FLAGS.SILENT (\Seen) で設定できます。+FLAGS は指定されたフラグを追加します。FLAGS を使用すると完全な集合が置き換えられ、他のクライアントの更新を上書きする可能性があります。.SILENT は、この操作による対応するフラグデータのエコーを抑制しますが、すべてのサーバー状態通知を閉じるわけではありません。
削除と移動:操作は同じ「削除」ボタンの意味ではない
\Deleted を設定するのはマーキングに過ぎません。EXPUNGE によって、選択されたメールボックス内で削除マーク付きのメールが実際にクリアされます。通常の EXPUNGE は、別のクライアントがマークしたメールも一緒にクリアする可能性があるため、自分自身が行ったUIDのみを処理すると仮定してはいけません。サポートされている場合は、限定されたUID集合を持つ UID EXPUNGE を使用することで範囲を狭めることができます。
MOVE はメールをあるメールボックスから別のメールボックスに移動します。古いクライアントは、COPY、削除マーク、EXPUNGE を組み合わせてシミュレートしますが、これには中間状態と失敗のウィンドウが存在します。専用の MOVE は部分的な組み合わせ操作の問題を回避できますが、複数メッセージの操作では部分的な失敗を処理する必要があり、一連の移動をアプリケーションデータベースのような「すべてまたはなし」のトランザクションとして記述してはいけません。MOVE RFC 6851
CLOSE は書き込み可能な選択状態において、削除マーク付きのメッセージをクリアする可能性があります。UNSELECT は、このクリアを実行せずに選択状態を終了します。読み取り専用チェックを行う際は、EXAMINE の意味合いを優先して理解し、「メールボックスから退出する」ために削除の影響を及ぼす操作を安易に呼び出してはいけません。
IDLE は変更通知であり、永続的なイベントログではない
クライアントはタグ付きの IDLE を送信し、サーバーが + で応答すると、そのコマンドを待機状態に保ちます。その間、EXISTS や flags への変更などを報告できます。クライアントが次の通常コマンドを実行する際は、タグなしの DONE を送信し、IDLE のタグ付き完了応答を待ってから、継続します。
時刻 例 クライアントの次のステップ
待機開始 a010 IDLE、サーバー + idling未タグ付けレスポンスを継続して読み取る
新状態の通知 * 43 EXISTS同期が必要なものを記録し、数字をUIDとして扱わない
待機終了 DONE、サーバー a010 OK ...その後、FETCH/SEARCH などを開始
接続切断 永続的なイベント位置の保証なし 再接続、UIDVALIDITY の確認、状態の補完
仕様では、サーバーのアイドルタイムアウトに適応するため、約29分以内の間隔で IDLE を終了して再送信することを推奨しています。中間のNATやモバイル端末のスリープにより、それよりも早く切断される可能性があります。IDLE はタイムリーな通知を提供しますが、固定されたレイテンシ、常時オンライン、または切断中のメッセージ通知のリプレイを保証するものではありません。IMAP IDLE
増分同期と並行衝突
UIDNEXT のみを比較しても、一部の新規変更は検出できますが、古いメールのフラグ変更や削除されたUIDを知ることはできません。CONDSTORE をサポートするサーバーは、MODSEQ によって状態変化を識別します。QRESYNC は、高速な再同期と VANISHED 削除情報をさらにサポートします。使用前には、プロトコルバージョンと能力に基づいて有効化を完了する必要があり、すべてのサービスが実装しているとは仮定してはいけません。CONDSTORE/QRESYNC RFC 7162
クライアントがメールの MODSEQ 20 を読み取り、オフライン中に別のクライアントがそれを 21 に変更したと仮定します。UNCHANGEDSINCE 20 を条件とする STORE コマンドは、新しい状態の上書きを拒否し、適用されなかった対象に対して MODIFIED を返すことができます。クライアントは衝突を無視してローカルの水位を進めるのではなく、再読み込みしてマージを決定する必要があります。
したがって、復旧フローは次のようになります。メールボックスと UIDVALIDITY を検証し、新規追加、変更、削除を補完し、ローカルデータが永続化されたことを確認してから、同期水位を保存します。IDLE による通知と同期水位は同じものではありません。水位の更新が実際のコンテンツの保存より先に行われると、クラッシュ後に同期が完了したと誤認する可能性があります。
POP3:ダウンロード、マーク、更新は3つのアクションに分かれる
POP3 では、認証後に TRANSACTION 状態に入り、LIST/STAT でメッセージとサイズを確認し、RETR でダウンロードし、DELE で削除マークを付けます。トランザクション状態から正常に QUIT して初めて UPDATE 状態に入り、マークに基づいて削除が実行されます。RSET により削除マークをクリアでき、異常な切断は、以前の DELE のみによって削除更新フェーズに入るべきではありません。POP3 RFC 1939
POP3:RETRでダウンロード、DELEでマーク、正常なQUITでのみ更新フェーズへ移行
TRANSACTION:認証済み
RETR 1 の後、DELE 1
ダウンロードとマークは独立した2つのコマンド
QUIT 受信 → UPDATE
マークに基づいて削除を実行し、結果を返す
切断または RSET
異常切断ではマークに基づいて削除しない;RSET でマークをクリア
QUIT の応答が失われた場合、結果には依然として曖昧さが残る;ローカル保存とサーバー削除は同じアトミックトランザクションではない。
したがって、「POP3 はダウンロード=削除」というわけではありません。クライアントはサーバーのコピーを保持することも、ローカルコピーの保存が成功してから削除を要求することもできます。複数のデバイスがすべてコピーを保持する場合、それぞれがダウンロードできますが、IMAP のような共有既読やフォルダ同期のセマンティクスはありません。
POP3 メッセージ番号は、現在のメールドロップセッション内でのみ位置指定に使用され、セッションを跨ぐと変化する可能性があります。オプションの UIDL はセッションを跨ぐ識別子を提供し、クライアントはこれに基づいて重複ダウンロードを回避します。これは RFC 5322 Message-ID ではありません。仕様では、完全に同一のコピーが同じ unique-id を持つ特殊なケースすら許可されており、UIDL を無視できるグローバル一意のUUIDとして扱うことはできません。
ローカル保存とサーバー UPDATE には共通のトランザクションがありません。まず「ダウンロード済み」を記録してからコンテンツを永続化すると、クライアントのクラッシュにより保存漏れが発生する可能性があります。コンテンツは保存済みだがローカル記録がまだコミットされていない場合、再接続時に重複ダウンロードが発生する可能性があります。QUIT が実行されたが応答が失われた場合、削除結果にも曖昧さが残ります。アーカイバプログラムは、ローカルコンテンツとインデックスを確実にコミットし、再接続時にサーバーの状態を再列挙する必要があります。
DELE の後に切断すると、メールは削除されますか?
まず RETR を実行してから DELE を実行すると、サーバーのコピーはまだ残っています。異常な切断後に再接続すると、マークは消え、メールは保持されます。リセット後に正常な QUIT を実行して初めて、サーバーは削除します。ダウンロードせずに削除して QUIT を実行した場合、このモデルではローカルにコピーがないことが露呈します。
図を準備しています 再読み込み
DELE の後に切断すると、メールは削除されますか? RETR でコンテンツを複製し、DELE はマークのみを付け、正常な QUIT でのみ UPDATE 状態へ移行します。異常な切断では、未コミットのマークが自動的に削除済みとして扱われることはありません。
{"id":"pop3-delete-commit","title":"DELE の後に切断すると、メールは削除されますか?","summary":"RETR でコンテンツを複製し、DELE はマークのみを付け、正常な QUIT でのみ UPDATE 状態へ移行します。異常な切断では、未コミットのマークが自動的に削除済みとして扱われることはありません。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"actions\"><button id=\"retr\" data-i18n=\"retr\"></button><button id=\"dele\" data-i18n=\"dele\"></button><button id=\"rset\" data-i18n=\"rset\"></button><button id=\"quit\" data-i18n=\"quit\"></button><button id=\"drop\" data-i18n=\"drop\"></button><button id=\"reconnect\" data-i18n=\"reconnect\"></button><button id=\"reset\" data-i18n=\"reset\"></button></div><section class=\"panel\"><h3 data-i18n=\"state\"></h3><div id=\"state\"></div></section><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px var(--viz-font-ui);padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 var(--viz-font-ui);overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n#state .tr{grid-template-columns:1fr 1fr}.tr>*{min-height:4em}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}let server=true,local=false,mark=false,open=true;function draw(){table('state',[['server copy',server?'✓':'∅'],['local saved',local?'✓':'∅'],['DELE mark',mark?'✓':'∅'],['session',open?'TRANSACTION':'CLOSED']]);$('#retr').disabled=$('#dele').disabled=!open||!server||mark;$('#rset').disabled=!open||!mark;$('#quit').disabled=$('#drop').disabled=!open;$('#reconnect').disabled=open;$('#state').dataset.snapshot=JSON.stringify({server,local,mark,open});}$('#retr').onclick=()=>{local=true;draw()};$('#dele').onclick=()=>{mark=true;draw()};$('#rset').onclick=()=>{mark=false;draw()};$('#quit').onclick=()=>{if(mark)server=false;mark=false;open=false;draw()};$('#drop').onclick=()=>{mark=false;open=false;draw()};$('#reconnect').onclick=()=>{open=true;draw()};$('#reset').onclick=()=>{server=true;local=false;mark=false;open=true;draw()};draw();","audio":false,"strings":{"dele":"DELE でマーク","drop":"異常な切断","heading":"DELE の後に切断すると、メールは削除されますか?","intro":"ダウンロード、削除マーク、正常な QUIT、異常な切断をそれぞれ別の操作として行い、サーバーとローカルのコピーを観察します。","quit":"正常な QUIT","reconnect":"再接続","reset":"リセット","retr":"RETR でダウンロードして保存","rset":"RSET でマークを取り消し","scope":"1通のメール、単一クライアント;QUIT 更新は成功し応答が到達したと仮定し、ローカルの RETR は即時永続保存されます。実際の UPDATE 失敗や応答の喪失は本文を参照し、POP3セッション全体はシミュレートしません。","scopeLabel":"モデル範囲と規約","state":"コピーとセッションの状態"}}
TLS、認証、および選択基準
IMAP では TCP 993、POP3 では TCP 995 が一般的に暗黙的 TLS を使用します。143/110 は、それぞれ STARTTLS/STLS によってアップグレード可能ですが、具体的な能力はネゴシエーションによって決定されます。パスワードを送信する前に、サーバーの身元検証を完了させる必要があります。OAuth やその他の SASL メカニズムのサポートはサーバー側の能力によって決まり、接続が TLS を使用しているからといって証明書検証をスキップしてはいけません。メールアクセス TLS RFC 8314
複数デバイスの協調、サーバー検索、フォルダ同期は IMAP の状態モデルに適合しています。一方、独立したアーカイブライブラリへの単方向ダウンロードには POP3 を使用できますが、「シンプル」だからといって、バックアップ、重複排除、削除のタイミングの問題が自動的に解消されるわけではありません。IMAP 同期はバックアップではありません。サーバー上での誤削除はすべてのクライアントに同期されるため、独立したバージョンの保持と復元ポイントは、別の機能です。