Eine vollständige, eigenständige PHP-Datei, die alle E-Mail-Adressen (From/To/Cc/Bcc/Reply-To) aus einem IMAP-Ordner ausliest, normalisiert, dedupliziert, optional validiert und auf Wunsch als CSV speichert. Es müssen nur die Zugangsdaten oben im Config-Block eintragen werden.
Voraussetzungen: PHP mit imap- und mbstring-Extension.
<?php
/**
* ImapAddressExtractor.php
* Liest E-Mail-Adressen gesammelt aus einem IMAP-Ordner.
* - Adresstypen: From, To, Cc, Bcc, Reply-To
* - Normalisierung: lowercasing, Trim, Unicode-Decoder
* - Deduplizierung & optionale RFC-Validierung
* - Ausgabe als HTML (Browser) oder Text (CLI)
* - Optionaler CSV-/TXT-Export
*
* Voraussetzungen: PHP-Extensions imap, mbstring
* Aufruf:
* - Browser: Datei aufrufen -> Tabelle + Download-Links
* - CLI: php ImapAddressExtractor.php
*/
declare(strict_types=1);
// =====================================================
// ================ KONFIGURATION ======================
// =====================================================
$config = [
// IMAP-Server: Host, Port, Flags
'host' => 'mail.example.com',
'port' => 993,
// Flags: z.B. '/imap/ssl' oder '/imap/ssl/novalidate-cert' (unsicher)
'flags' => '/imap/ssl',
// Ordner (Mailbox): z.B. 'INBOX', 'Sent', 'Gesendet'
'mailbox' => 'INBOX',
// Zugangsdaten
'username' => 'user@example.com',
'password' => 'CHANGE_ME',
// IMAP-Search-Query (siehe Gmail/IMAP-Syntax): 'ALL', 'SINCE "1-Jan-2024"', 'UNDELETED', etc.
'search' => 'ALL',
// Welche Header sollen berücksichtigt werden?
'include_headers' => [
'from' => true,
'to' => true,
'cc' => true,
'bcc' => true,
'reply_to' => true,
],
// Validierung & Normalisierung
'validate_emails' => true, // filter_var(..., FILTER_VALIDATE_EMAIL)
'lowercase' => true,
'trim' => true,
// Exporte
'export_csv' => true,
'export_txt' => true,
'export_dir' => __DIR__ . '/exports', // wird automatisch erstellt
'csv_filename' => 'imap_addresses.csv',
'txt_filename' => 'imap_addresses.txt',
// Timeout/Performance
'set_time_limit' => 0, // 0 = unbegrenzt
'batch_flush' => 1000, // nur relevant für sehr große Postfächer (Kein Effekt auf Ergebnis)
];
// =====================================================
// ================== PROGRAMMLOGIK ====================
// =====================================================
if ($config['set_time_limit'] !== null) {
@set_time_limit((int)$config['set_time_limit']);
}
if (!extension_loaded('imap')) {
exit("ERROR: PHP IMAP-Extension ist nicht geladen.\n");
}
if (!extension_loaded('mbstring')) {
// nicht kritisch, aber sehr empfohlen
}
$mboxString = sprintf(
'{%s:%d%s}%s',
$config['host'],
$config['port'],
$config['flags'],
$config['mailbox']
);
$connection = @imap_open($mboxString, $config['username'], $config['password']);
if (!$connection) {
$err = imap_last_error();
exitOutput("Verbindung fehlgeschlagen: " . ($err ?: 'Unbekannter Fehler'));
}
$emails = @imap_search($connection, $config['search']) ?: [];
// Bei leerem Ergebnis beenden
if (empty($emails)) {
imap_close($connection);
exitOutput("Keine Mails für Suchkriterium '{$config['search']}' im Ordner '{$config['mailbox']}' gefunden.");
}
$addressesSet = []; // assoziatives Array für Deduplizierung: ['email' => true]
$countProcessed = 0;
foreach ($emails as $num) {
$header = @imap_headerinfo($connection, $num);
if (!$header) {
continue;
}
// Sammelfunktion für alle Felder
collectFromAddressObject($addressesSet, $header->from ?? null, $config);
if (!empty($config['include_headers']['to'])) { collectFromAddressObject($addressesSet, $header->to ?? null, $config); }
if (!empty($config['include_headers']['cc'])) { collectFromAddressObject($addressesSet, $header->cc ?? null, $config); }
if (!empty($config['include_headers']['bcc'])) { collectFromAddressObject($addressesSet, $header->bcc ?? null, $config); }
if (!empty($config['include_headers']['reply_to'])) { collectFromAddressObject($addressesSet, $header->reply_to ?? null, $config); }
$countProcessed++;
// Optional: Bei sehr großen Postfächern könnte man hier flushen/loggen
if ($config['batch_flush'] && $countProcessed % $config['batch_flush'] === 0) {
// no-op
}
}
imap_close($connection);
$addresses = array_keys($addressesSet);
sort($addresses, SORT_STRING | SORT_FLAG_CASE);
// Exporte erzeugen
$exports = [];
if (!is_dir($config['export_dir'])) {
@mkdir($config['export_dir'], 0775, true);
}
if (!is_dir($config['export_dir'])) {
$exports[] = "Konnte Export-Verzeichnis nicht erstellen: " . $config['export_dir'];
} else {
if (!empty($config['export_csv'])) {
$csvPath = rtrim($config['export_dir'], DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $config['csv_filename'];
$ok = saveCsv($csvPath, $addresses);
$exports[] = $ok ? $csvPath : "CSV-Export fehlgeschlagen: $csvPath";
}
if (!empty($config['export_txt'])) {
$txtPath = rtrim($config['export_dir'], DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $config['txt_filename'];
$ok = saveTxt($txtPath, $addresses);
$exports[] = $ok ? $txtPath : "TXT-Export fehlgeschlagen: $txtPath";
}
}
// Ausgabe
outputResult($addresses, $countProcessed, $config, $exports);
// =====================================================
// =================== HILFSFUNKTIONEN =================
// =====================================================
/**
* Extrahiert Adressen aus dem IMAP-Address-Objekt-Array (from/to/cc/bcc/reply_to) und fügt sie dem Set hinzu.
*
* @param array $set Referenz auf Deduplizierungs-Set
* @param mixed $addrObj IMAP Address-Objekte oder null
* @param array $cfg Konfiguration
* @return void
*/
function collectFromAddressObject(array &$set, $addrObj, array $cfg): void
{
if (empty($addrObj) || !is_array($addrObj)) {
return;
}
foreach ($addrObj as $entry) {
// mailbox + host können fehlen (z. B. Gruppenadressen)
$mailbox = isset($entry->mailbox) ? (string)$entry->mailbox : '';
$host = isset($entry->host) ? (string)$entry->host : '';
if ($mailbox === '' || $host === '') {
// Fallback: manchmal steht alles in "personal" (Display-Name) mit Winkelklammern
if (!empty($entry->personal)) {
foreach (extractEmailsFromText(decodeHeaderToUtf8((string)$entry->personal)) as $fallbackEmail) {
addEmailToSet($set, $fallbackEmail, $cfg);
}
}
continue;
}
$email = $mailbox . '@' . $host;
addEmailToSet($set, $email, $cfg);
}
}
/**
* Fügt eine E-Mail (validiert/normalisiert) dem Set hinzu.
*/
function addEmailToSet(array &$set, string $email, array $cfg): void
{
$email = decodeHeaderToUtf8($email);
if (!empty($cfg['trim'])) { $email = trim($email); }
if (!empty($cfg['lowercase'])) { $email = mb_strtolower($email, 'UTF-8'); }
// Entferne umgebende Winkelklammern, falls vorhanden
$email = trim($email, "<> \t\n\r\0\x0B");
// Sehr selten: Kommentare in Adressen entfernen (user(comment)@domain)
$email = preg_replace('/\s*\(.*?\)\s*/', '', $email);
if ($email === '') {
return;
}
if (!empty($cfg['validate_emails'])) {
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return; // ungültig -> überspringen
}
}
$set[$email] = true; // Deduplizierung
}
/**
* Versucht, alle E-Mails aus freiem Text zu extrahieren (Fallback).
*/
function extractEmailsFromText(string $text): array
{
// Sehr einfache, robuste Regex (nicht 100% RFC-konform, aber praxistauglich)
preg_match_all('/[A-Z0-9._%+\-]+@[A-Z0-9.\-]+\.[A-Z]{2,}/i', $text, $m);
return $m[0] ?? [];
}
/**
* Dekodiert MIME-Header in UTF-8 (z.B. =?UTF-8?Q?...?=)
*/
function decodeHeaderToUtf8(string $str): string
{
$out = '';
foreach (imap_mime_header_decode($str) as $part) {
$charset = strtoupper($part->charset ?? 'US-ASCII');
$text = $part->text ?? '';
if ($charset !== 'DEFAULT' && $charset !== 'US-ASCII') {
$out .= @mb_convert_encoding($text, 'UTF-8', $charset) ?: $text;
} else {
$out .= $text;
}
}
return $out;
}
/**
* Speichert CSV mit Kopfzeile "email"
*/
function saveCsv(string $path, array $emails): bool
{
$fh = @fopen($path, 'w');
if (!$fh) return false;
fputcsv($fh, ['email']);
foreach ($emails as $e) {
fputcsv($fh, [$e]);
}
fclose($fh);
return true;
}
/**
* Speichert TXT (eine Adresse pro Zeile)
*/
function saveTxt(string $path, array $emails): bool
{
$data = implode(PHP_EOL, $emails) . PHP_EOL;
return @file_put_contents($path, $data) !== false;
}
/**
* Ausgabe je nach Kontext (CLI/Browser)
*/
function outputResult(array $emails, int $countProcessed, array $cfg, array $exports): void
{
$isCli = (PHP_SAPI === 'cli');
if ($isCli) {
echo "Verarbeitete Mails: {$countProcessed}\n";
echo "Gefundene eindeutige Adressen: " . count($emails) . "\n\n";
foreach ($emails as $e) {
echo $e . "\n";
}
if ($exports) {
echo "\nExporte:\n";
foreach ($exports as $ex) {
echo " - $ex\n";
}
}
echo "\n";
return;
}
// Browser-HTML
header('Content-Type: text/html; charset=utf-8');
?>
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>IMAP Address Extractor</title>
<style>
:root { color-scheme: light dark; }
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; margin: 2rem; }
h1 { margin: 0 0 0.5rem; }
.meta { color: #666; margin-bottom: 1rem; }
table { border-collapse: collapse; width: 100%; max-width: 900px; }
th, td { border: 1px solid #ddd; padding: 8px; font-size: 14px; }
th { background: #f3f3f3; text-align: left; }
.exports { margin: 1rem 0; }
.exports a { margin-right: 1rem; }
.count { font-weight: 600; }
</style>
</head>
<body>
<h1>IMAP Address Extractor</h1>
<div class="meta">
Ordner: <strong><?=htmlspecialchars($cfg['mailbox'])?></strong> |
Suchkriterium: <code><?=htmlspecialchars($cfg['search'])?></code> |
Verarbeitete Mails: <span class="count"><?=number_format($countProcessed,0,',','.')?></span> |
Eindeutige Adressen: <span class="count"><?=number_format(count($emails),0,',','.')?></span>
</div>
<?php if ($exports): ?>
<div class="exports">
<strong>Exporte:</strong>
<ul>
<?php foreach ($exports as $ex): ?>
<?php if (is_file($ex)): ?>
<?php
$rel = str_replace(__DIR__, '.', $ex);
$href = basename(dirname($ex)) . '/' . basename($ex);
?>
<li><a href="<?=htmlspecialchars($href)?>" download><?=htmlspecialchars($rel)?></a></li>
<?php else: ?>
<li><?=htmlspecialchars($ex)?></li>
<?php endif; ?>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<table>
<thead>
<tr><th>#</th><th>E-Mail-Adresse</th></tr>
</thead>
<tbody>
<?php $i = 1; foreach ($emails as $e): ?>
<tr>
<td><?= $i++ ?></td>
<td><code><?= htmlspecialchars($e) ?></code></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</body>
</html>
<?php
}
/**
* Einheitliche Fehlermeldung (CLI/Browser)
*/
function exitOutput(string $message): void
{
if (PHP_SAPI === 'cli') {
fwrite(STDERR, "ERROR: $message\n");
} else {
header('Content-Type: text/plain; charset=utf-8', true, 500);
echo "ERROR: $message";
}
exit(1);
}