with base64 data URIs using files from storage/app/public. * Only affects local public storage images; external URLs and existing data URIs are left intact. */ public function inline(string $html): string { if ($html === '' || stripos($html, ']+)src=[\"\']([^\"\']+)[\"\']([^>]*)>#i', function (array $m): string { $before = $m[1] ?? ''; $src = $m[2] ?? ''; $after = $m[3] ?? ''; // Skip if already data URI or external if (stripos($src, 'data:') === 0) { return $m[0]; } // Accept either relative (/storage/...) OR absolute URLs whose path begins with /storage/ $path = $src; if (preg_match('#^https?://#i', $src)) { $parts = parse_url($src); $path = $parts['path'] ?? ''; } if (! preg_match('#^/?storage/#i', (string) $path)) { return $m[0]; } $rel = ltrim(preg_replace('#^/?storage/#i', '', (string) $path), '/'); $full = storage_path('app/public/'.$rel); if (! File::exists($full)) { return $m[0]; } // Determine mime type $mime = null; try { $mime = File::mimeType($full); } catch (\Throwable) { $mime = null; } if ($mime === null) { $ext = strtolower(pathinfo($full, PATHINFO_EXTENSION)); $map = [ 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'png' => 'image/png', 'gif' => 'image/gif', 'svg' => 'image/svg+xml', 'webp' => 'image/webp', ]; $mime = $map[$ext] ?? 'application/octet-stream'; } // Cap size to avoid huge emails (e.g., 5 MB) $max = 5 * 1024 * 1024; try { if (File::size($full) > $max) { return $m[0]; } } catch (\Throwable) { // ignore size errors } try { $data = base64_encode(File::get($full)); } catch (\Throwable) { return $m[0]; } $dataUri = 'data:'.$mime.';base64,'.$data; return ''; }, $html); } }