feat: smtp sending

Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
2026-06-23 16:29:16 -04:00
parent e031831719
commit ad6b7c5eba
13 changed files with 929 additions and 0 deletions
+72
View File
@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace KTXM\ProviderImap\Smtp;
use KTXM\ProviderImap\Smtp\Protocol\Reply;
/**
* Parsed EHLO capabilities.
*
* The first line of an EHLO reply is the server greeting/domain; every
* subsequent line is a capability keyword optionally followed by parameters
* (e.g. "AUTH PLAIN LOGIN", "SIZE 35882577").
*/
final class SmtpCapabilities
{
/**
* @param array<string,list<string>> $keywords capability keyword => parameters
*/
private function __construct(
private readonly array $keywords,
) {}
public static function fromEhlo(Reply $reply): self
{
$keywords = [];
$lines = $reply->lines();
// Skip the first line (greeting/domain); the rest are capabilities.
foreach (array_slice($lines, 1) as $line) {
$parts = preg_split('/\s+/', trim($line)) ?: [];
$keyword = strtoupper((string) array_shift($parts));
if ($keyword === '') {
continue;
}
$keywords[$keyword] = array_map('strtoupper', $parts);
}
return new self($keywords);
}
public function has(string $keyword): bool
{
return isset($this->keywords[strtoupper($keyword)]);
}
public function supportsStartTls(): bool
{
return $this->has('STARTTLS');
}
/**
* @return list<string> Advertised AUTH mechanisms (uppercased).
*/
public function authMechanisms(): array
{
return $this->keywords['AUTH'] ?? [];
}
public function maxSize(): ?int
{
$size = $this->keywords['SIZE'][0] ?? null;
return $size !== null && ctype_digit($size) ? (int) $size : null;
}
}