├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── config └── imap.php └── src ├── Commands ├── ConfigureIdleQuery.php ├── HandleMessageReceived.php └── WatchMailbox.php ├── Events ├── MailboxWatchAttemptsExceeded.php └── MessageReceived.php ├── Facades └── Imap.php ├── ImapManager.php ├── ImapServiceProvider.php └── Support ├── Loop.php ├── LoopFake.php └── LoopInterface.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `ImapEngine-Laravel` will be documented in this file. 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Steve Bauman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

Working with IMAP doesn't need to be hard.

6 | 7 |

ImapEngine provides a simple API for managing mailboxes -- without the PHP extension.

8 | 9 |

10 | 11 | 12 | 13 | 14 |

15 | 16 |

17 | View Documentation 18 |

19 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "directorytree/imapengine-laravel", 3 | "description": "This is my package imapengine-laravel", 4 | "keywords": [ 5 | "Steve Bauman", 6 | "laravel", 7 | "imap" 8 | ], 9 | "homepage": "https://github.com/directorytree/imapengine-laravel", 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "Steve Bauman", 14 | "email": "steven_bauman@outlook.com", 15 | "role": "Developer" 16 | } 17 | ], 18 | "require": { 19 | "php": "^8.1", 20 | "directorytree/imapengine": "^1.13.0", 21 | "illuminate/contracts": "^10.0|^11.0|^12.0" 22 | }, 23 | "require-dev": { 24 | "laravel/pint": "^1.14", 25 | "nunomaduro/collision": "^7.10|^8.1", 26 | "orchestra/testbench": "^8.22|^9.0|^10.0", 27 | "pestphp/pest": "^2.0|^3.0", 28 | "pestphp/pest-plugin-arch": "^2.0|^3.0", 29 | "pestphp/pest-plugin-laravel": "^2.0|^3.0", 30 | "spatie/laravel-ray": "^1.35" 31 | }, 32 | "autoload": { 33 | "psr-4": { 34 | "DirectoryTree\\ImapEngine\\Laravel\\": "src/" 35 | } 36 | }, 37 | "autoload-dev": { 38 | "psr-4": { 39 | "DirectoryTree\\ImapEngine\\Laravel\\Tests\\": "tests/" 40 | } 41 | }, 42 | "scripts": { 43 | "post-autoload-dump": "@composer run prepare", 44 | "prepare": "@php vendor/bin/testbench package:discover --ansi", 45 | "analyse": "vendor/bin/phpstan analyse", 46 | "test": "vendor/bin/pest", 47 | "test-coverage": "vendor/bin/pest --coverage", 48 | "format": "vendor/bin/pint" 49 | }, 50 | "config": { 51 | "sort-packages": true, 52 | "allow-plugins": { 53 | "pestphp/pest-plugin": true, 54 | "phpstan/extension-installer": true 55 | } 56 | }, 57 | "extra": { 58 | "laravel": { 59 | "providers": [ 60 | "DirectoryTree\\ImapEngine\\Laravel\\ImapServiceProvider" 61 | ] 62 | } 63 | }, 64 | "minimum-stability": "dev", 65 | "prefer-stable": true 66 | } 67 | -------------------------------------------------------------------------------- /config/imap.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'default' => [ 18 | 'port' => env('IMAP_PORT', 993), 19 | 'host' => env('IMAP_HOST'), 20 | 'timeout' => env('IMAP_TIMEOUT', 30), 21 | 'debug' => env('IMAP_DEBUG', false), 22 | 'username' => env('IMAP_USERNAME'), 23 | 'password' => env('IMAP_PASSWORD'), 24 | 'encryption' => env('IMAP_ENCRYPTION', 'ssl'), 25 | 'validate_cert' => env('IMAP_VALIDATE_CERT', true), 26 | 'authentication' => env('IMAP_AUTHENTICATION', 'plain'), 27 | 'proxy' => [ 28 | 'socket' => env('IMAP_PROXY_SOCKET'), 29 | 'username' => env('IMAP_PROXY_USERNAME'), 30 | 'password' => env('IMAP_PROXY_PASSWORD'), 31 | 'request_fulluri' => env('IMAP_PROXY_REQUEST_FULLURI', false), 32 | ], 33 | ], 34 | ], 35 | ]; 36 | -------------------------------------------------------------------------------- /src/Commands/ConfigureIdleQuery.php: -------------------------------------------------------------------------------- 1 | with)) { 22 | $query->withFlags(); 23 | } 24 | 25 | if (in_array('body', $this->with)) { 26 | $query->withBody(); 27 | } 28 | 29 | if (in_array('headers', $this->with)) { 30 | $query->withHeaders(); 31 | } 32 | 33 | return $query; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Commands/HandleMessageReceived.php: -------------------------------------------------------------------------------- 1 | command->info( 28 | "Message received: [{$message->uid()}]" 29 | ); 30 | 31 | $this->attempts = 0; 32 | 33 | $this->lastReceivedAt = Date::now(); 34 | 35 | Event::dispatch(new MessageReceived($message)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Commands/WatchMailbox.php: -------------------------------------------------------------------------------- 1 | argument('mailbox')); 38 | 39 | $with = explode(',', $this->option('with')); 40 | 41 | $this->info("Watching mailbox [$name]..."); 42 | 43 | $attempts = 0; 44 | 45 | $lastReceivedAt = null; 46 | 47 | $loop->run(function () use ($mailbox, $name, $with, &$attempts, &$lastReceivedAt) { 48 | try { 49 | $folder = $this->folder($mailbox); 50 | 51 | $folder->idle( 52 | new HandleMessageReceived($this, $attempts, $lastReceivedAt), 53 | new ConfigureIdleQuery($with), 54 | $this->option('timeout') 55 | ); 56 | } catch (Exception $e) { 57 | if ($this->isMessageMissing($e)) { 58 | return; 59 | } 60 | 61 | if ($this->isDisconnection($e)) { 62 | sleep(2); 63 | 64 | return; 65 | } 66 | 67 | if ($attempts >= $this->option('attempts')) { 68 | $this->info("Exception: {$e->getMessage()}"); 69 | 70 | Event::dispatch( 71 | new MailboxWatchAttemptsExceeded($name, $attempts, $e, $lastReceivedAt) 72 | ); 73 | 74 | throw $e; 75 | } 76 | 77 | $attempts++; 78 | } 79 | }); 80 | } 81 | 82 | /** 83 | * Get the mailbox folder to idle. 84 | */ 85 | protected function folder(MailboxInterface $mailbox): FolderInterface 86 | { 87 | return ($folder = $this->argument('folder')) 88 | ? $mailbox->folders()->findOrFail($folder) 89 | : $mailbox->inbox(); 90 | } 91 | 92 | /** 93 | * Determine if the exception is due to a message missing error. 94 | */ 95 | protected function isMessageMissing(Exception $e): bool 96 | { 97 | return Str::contains($e->getMessage(), [ 98 | 'no longer exist', 99 | ], true); 100 | } 101 | 102 | /** 103 | * Determine if the exception is caused by a disconnection. 104 | */ 105 | protected function isDisconnection(Exception $e): bool 106 | { 107 | return Str::contains($e->getMessage(), [ 108 | 'connection reset by peer', 109 | 'temporary system problem', 110 | 'failed to fetch content', 111 | 'connection failed', 112 | 'empty response', 113 | 'not connected', 114 | 'no response', 115 | 'broken pipe', 116 | 'unavailable', 117 | ], true); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/Events/MailboxWatchAttemptsExceeded.php: -------------------------------------------------------------------------------- 1 | swap($mailbox, $fake); 38 | 39 | return $fake; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/ImapManager.php: -------------------------------------------------------------------------------- 1 | config = $config; 27 | } 28 | 29 | /** 30 | * Get a mailbox instance. 31 | */ 32 | public function mailbox(string $name): MailboxInterface 33 | { 34 | if (isset($this->mailboxes[$name])) { 35 | return $this->mailboxes[$name]; 36 | } 37 | 38 | if (! array_key_exists($name, $this->config['mailboxes'] ?? [])) { 39 | throw new InvalidArgumentException( 40 | "Mailbox [{$name}] is not defined. Please check your IMAP configuration." 41 | ); 42 | } 43 | 44 | return $this->mailboxes[$name] = $this->build($this->config['mailboxes'][$name]); 45 | } 46 | 47 | /** 48 | * Register a mailbox instance. 49 | */ 50 | public function register(string $name, array $config): static 51 | { 52 | $this->mailboxes[$name] = $this->build($config); 53 | 54 | return $this; 55 | } 56 | 57 | /** 58 | * Build an on-demand mailbox instance. 59 | */ 60 | public function build(array $config): MailboxInterface 61 | { 62 | return new Mailbox($config); 63 | } 64 | 65 | /** 66 | * Remove a mailbox from the in-memory cache. 67 | */ 68 | public function forget(string $name): static 69 | { 70 | unset($this->mailboxes[$name]); 71 | 72 | return $this; 73 | } 74 | 75 | /** 76 | * Swap out a mailbox instance with a new one. 77 | */ 78 | public function swap(string $name, MailboxInterface $mailbox): void 79 | { 80 | $this->mailboxes[$name] = $mailbox; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/ImapServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton(ImapManager::class, function () { 17 | return new ImapManager(config('imap', [])); 18 | }); 19 | 20 | $this->app->bind(LoopInterface::class, Loop::class); 21 | } 22 | 23 | /** 24 | * Bootstrap application services. 25 | */ 26 | public function boot(): void 27 | { 28 | if ($this->app->runningInConsole()) { 29 | $this->commands([ 30 | Commands\WatchMailbox::class, 31 | ]); 32 | } 33 | 34 | $this->publishes([ 35 | __DIR__.'/../config/imap.php' => config_path('imap.php'), 36 | ]); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Support/Loop.php: -------------------------------------------------------------------------------- 1 |