}`, the * server replies with a continuation request ("+"), and only then is the * message streamed back via {@see ResponseStream::respond()}. Returns the * assigned UID when the server reports APPENDUID (RFC 4315 / UIDPLUS), * otherwise null. * * @implements CommandInterface */ final class AppendCommand implements CommandInterface { private readonly string $literal; /** * @param list $flags optional initial flags, e.g. ['\\Seen'] */ public function __construct( private readonly string $mailbox, string $message, private readonly array $flags = [], ) { // IMAP literals are octet-counted; normalise to CRLF line endings. $this->literal = (string) preg_replace('/\r\n|\r|\n/', "\r\n", $message); } public function name(): string { return 'APPEND'; } public function allowedStates(): array { return [SessionState::Authenticated, SessionState::Selected]; } public function encode(string $tag, SessionContext $context): RequestFrame { unset($tag, $context); $flagSegment = $this->flags === [] ? '' : '(' . implode(' ', $this->flags) . ') '; return new RequestFrame(sprintf( 'APPEND %s %s{%d}', $this->quote($this->mailbox), $flagSegment, strlen($this->literal), )); } public function handle(ResponseStream $responses, SessionContext $context): ?int { unset($context); foreach ($responses as $response) { if ($response instanceof ContinuationResponse) { // Continuation granted: stream the literal, then CRLF to end the command. $responses->respond($this->literal . "\r\n"); continue; } if ($response instanceof TaggedResponse) { if (!$response->isOk()) { throw new ImapException('APPEND failed: ' . $response->text()); } return $this->parseAppendUid($response->text()); } } throw new ImapException('APPEND did not receive a tagged completion response.'); } private function quote(string $mailbox): string { return '"' . addcslashes($mailbox, "\\\"") . '"'; } private function parseAppendUid(string $text): ?int { if (preg_match('/\[APPENDUID\s+\d+\s+(\d+)\]/i', $text, $matches) === 1) { return (int) $matches[1]; } return null; } }