|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +Nette Mail is a standalone PHP library for creating and sending emails with support for SMTP, sendmail, DKIM signing, and fallback mechanisms. Part of the Nette Framework ecosystem but usable independently. |
| 8 | + |
| 9 | +- **Requirements:** PHP 8.0 - 8.5, ext-iconv required |
| 10 | +- **Optional extensions:** ext-fileinfo (attachment type detection), ext-openssl (DKIM signing) |
| 11 | +- **Main dependency:** nette/utils ^4.0 |
| 12 | + |
| 13 | +## Essential Commands |
| 14 | + |
| 15 | +### Testing |
| 16 | + |
| 17 | +```bash |
| 18 | +# Run all tests |
| 19 | +composer run tester |
| 20 | +# or |
| 21 | +vendor/bin/tester tests -s |
| 22 | + |
| 23 | +# Run specific test file |
| 24 | +vendor/bin/tester tests/Mail/Message.phpt -s |
| 25 | + |
| 26 | +# Run tests in specific directory |
| 27 | +vendor/bin/tester tests/Mail/ -s |
| 28 | +``` |
| 29 | + |
| 30 | +### Static Analysis |
| 31 | + |
| 32 | +```bash |
| 33 | +# Run PHPStan analysis (level 5) |
| 34 | +composer run phpstan |
| 35 | +# or |
| 36 | +vendor/bin/phpstan analyse |
| 37 | +``` |
| 38 | + |
| 39 | +## Architecture |
| 40 | + |
| 41 | +### Core Components |
| 42 | + |
| 43 | +The library consists of three main areas: |
| 44 | + |
| 45 | +1. **Email Creation** (`src/Mail/`) |
| 46 | + - `Message` - Main class for composing emails, extends MimePart |
| 47 | + - `MimePart` - Base class handling MIME encoding, headers, and structure |
| 48 | + - Priority constants: `Message::High`, `Message::Normal`, `Message::Low` |
| 49 | + |
| 50 | +2. **Email Sending** (`src/Mail/`) |
| 51 | + - `Mailer` interface - Contract for all mailer implementations |
| 52 | + - `SendmailMailer` - Uses PHP's `mail()` function |
| 53 | + - `SmtpMailer` - Full SMTP protocol implementation with TLS/SSL support |
| 54 | + - `FallbackMailer` - Retry mechanism across multiple mailers |
| 55 | + |
| 56 | +3. **Email Signing** (`src/Mail/`) |
| 57 | + - `Signer` interface - Contract for signing implementations |
| 58 | + - `DkimSigner` - DKIM (DomainKeys Identified Mail) signing using RSA-SHA256 |
| 59 | + |
| 60 | +### Dependency Injection Integration |
| 61 | + |
| 62 | +`src/Bridges/MailDI/MailExtension.php` - Nette DI compiler extension for configuration. |
| 63 | + |
| 64 | +**DI Services registered:** |
| 65 | +- `mail.mailer` - Mailer instance (SendmailMailer or SmtpMailer based on config) |
| 66 | +- `mail.signer` - DKIM Signer instance (if DKIM is configured) |
| 67 | +- `nette.mailer` - Alias to mail.mailer (for backward compatibility) |
| 68 | + |
| 69 | +**Configuration:** |
| 70 | + |
| 71 | +```neon |
| 72 | +mail: |
| 73 | + # Use SmtpMailer instead of SendmailMailer |
| 74 | + smtp: true # (bool) defaults to false |
| 75 | +
|
| 76 | + # SMTP connection settings |
| 77 | + host: smtp.gmail.com # (string) SMTP server hostname |
| 78 | + port: 587 # (int) defaults: 25, 465 for ssl, 587 for tls |
| 79 | + username: user@example.com |
| 80 | + password: **** |
| 81 | + encryption: tls # (ssl|tls|null) null = no encryption |
| 82 | + timeout: 20 # (int) connection timeout in seconds, default 20 |
| 83 | + persistent: false # (bool) use persistent connection |
| 84 | + clientHost: localhost # (string) defaults to $_SERVER['HTTP_HOST'] or 'localhost' |
| 85 | +
|
| 86 | + # SSL/TLS context options for SMTP connection |
| 87 | + context: |
| 88 | + ssl: |
| 89 | + verify_peer: true # NEVER set to false in production! |
| 90 | + verify_peer_name: true |
| 91 | + allow_self_signed: false # Do not allow self-signed certificates |
| 92 | + # See https://www.php.net/manual/en/context.ssl.php for all options |
| 93 | +
|
| 94 | + # DKIM signing configuration |
| 95 | + dkim: |
| 96 | + domain: example.com # Your domain name |
| 97 | + selector: dkim # DKIM selector from DNS |
| 98 | + privateKey: %appDir%/../dkim/private.key # Path to private key file |
| 99 | + passPhrase: **** # Optional passphrase for private key |
| 100 | +``` |
| 101 | + |
| 102 | +**Security Warning:** Never disable SSL certificate verification (`verify_peer: false`) as it makes your application vulnerable to man-in-the-middle attacks. Instead, add certificates to the trust store if needed. |
| 103 | + |
| 104 | +### Exception Hierarchy |
| 105 | + |
| 106 | +All exceptions in `src/Mail/exceptions.php`: |
| 107 | +- `SendException` - Base exception for sending failures |
| 108 | +- `SmtpException` - SMTP-specific errors (extends SendException) |
| 109 | +- `FallbackMailerException` - All mailers failed (contains array of failures) |
| 110 | +- `SignException` - Signing/verification errors |
| 111 | + |
| 112 | +### Key Features |
| 113 | + |
| 114 | +**Message Creation:** |
| 115 | +- Fluent API with method chaining |
| 116 | +- Automatic text alternative generation from HTML |
| 117 | +- Auto-embedding images from filesystem using `[[...]]` syntax or `<img src=...>` |
| 118 | +- Subject auto-extraction from `<title>` element |
| 119 | +- Attachment support with auto-detection of MIME types |
| 120 | + |
| 121 | +**MIME Handling:** |
| 122 | +- Encoding methods: Base64, 7bit, 8bit, quoted-printable |
| 123 | +- Line length management (76 characters default) |
| 124 | +- Full UTF-8 support throughout |
| 125 | + |
| 126 | +**SMTP Features:** |
| 127 | +- TLS/SSL encryption support (`encryption: 'ssl'` or `'tls'`) |
| 128 | +- Default ports: 25 (unencrypted), 465 (SSL), 587 (TLS) |
| 129 | +- Persistent connections |
| 130 | +- Configurable timeout (default 20s) |
| 131 | +- Custom stream options for SSL context |
| 132 | +- Envelope sender support |
| 133 | +- AUTH PLAIN and LOGIN authentication methods |
| 134 | + |
| 135 | +**DKIM Signing:** |
| 136 | +- RSA-SHA256 signing algorithm |
| 137 | +- Private key passphrase support |
| 138 | +- Automatic header canonicalization |
| 139 | +- Compatible with Gmail, Outlook, and other major providers |
| 140 | + |
| 141 | +## Testing Strategy |
| 142 | + |
| 143 | +Uses Nette Tester with `.phpt` format: |
| 144 | + |
| 145 | +```php |
| 146 | +<?php |
| 147 | +declare(strict_types=1); |
| 148 | + |
| 149 | +use Tester\Assert; |
| 150 | + |
| 151 | +require __DIR__ . '/../bootstrap.php'; |
| 152 | + |
| 153 | +test('Message correctly sets recipient', function () { |
| 154 | + $mail = new Nette\Mail\Message; |
| 155 | + $mail->addTo('test@example.com'); |
| 156 | + |
| 157 | + Assert::same(['test@example.com' => null], $mail->getHeader('To')); |
| 158 | +}); |
| 159 | +``` |
| 160 | + |
| 161 | +- **31 test files** covering all major functionality |
| 162 | +- Test fixtures in `tests/Mail/fixtures/` for email samples |
| 163 | +- Bootstrap in `tests/bootstrap.php` provides `test()` helper function |
| 164 | +- Tests run on PHP 8.0-8.5 in CI |
| 165 | + |
| 166 | +## Coding Standards |
| 167 | + |
| 168 | +Follows Nette Coding Standard (PSR-12 based) with these requirements: |
| 169 | + |
| 170 | +- **Mandatory:** `declare(strict_types=1)` in all PHP files |
| 171 | +- **Indentation:** Tabs (not spaces) |
| 172 | +- **Method spacing:** Two empty lines between methods |
| 173 | +- **Types:** All properties, parameters, and return values must be typed |
| 174 | +- **Documentation:** Only when adding information beyond PHP types |
| 175 | + - Document array contents: `@return string[]` |
| 176 | + - Document nullable relationships: `@param ?string` |
| 177 | + - Skip obvious parameters (width, height, name) |
| 178 | +- **String quotes:** Single quotes unless containing apostrophes |
| 179 | +- **Naming:** PascalCase for classes, camelCase for methods/properties |
| 180 | +- **No prefixes:** No `Abstract`, `Interface`, or `I` prefixes |
| 181 | + |
| 182 | +### Return Type Format |
| 183 | + |
| 184 | +Opening brace on separate line after return type: |
| 185 | + |
| 186 | +```php |
| 187 | +public function send(Message $mail): |
| 188 | +{ |
| 189 | + // method body |
| 190 | +} |
| 191 | +``` |
| 192 | + |
| 193 | +### phpDoc Examples |
| 194 | + |
| 195 | +```php |
| 196 | +/** |
| 197 | + * Adds email recipient. |
| 198 | + * @param string|array $email Address or [address => name] pairs |
| 199 | + */ |
| 200 | +public function addTo(string|array $email, ?string $name = null): static |
| 201 | + |
| 202 | +/** |
| 203 | + * Sets message priority. |
| 204 | + */ |
| 205 | +public function setPriority(int $priority): static |
| 206 | +``` |
| 207 | + |
| 208 | +## Development Workflow |
| 209 | + |
| 210 | +1. **Before making changes:** |
| 211 | + - Read existing code to understand patterns |
| 212 | + - Check related test files |
| 213 | + - Verify PHPStan passes: `composer run phpstan` |
| 214 | + |
| 215 | +2. **When adding features:** |
| 216 | + - Add corresponding tests in `tests/Mail/` |
| 217 | + - Use `test()` helper for test cases |
| 218 | + - Run tests: `vendor/bin/tester tests -s` |
| 219 | + |
| 220 | +3. **When fixing bugs:** |
| 221 | + - Add regression test first |
| 222 | + - Ensure fix doesn't break existing tests |
| 223 | + - Update PHPDoc if behavior changes |
| 224 | + |
| 225 | +4. **Before committing:** |
| 226 | + - Run full test suite: `composer run tester` |
| 227 | + - Run static analysis: `composer run phpstan` |
| 228 | + - Check code style with Nette Code Checker |
| 229 | + |
| 230 | +## Usage in Nette Application |
| 231 | + |
| 232 | +When using Nette Mail within a full Nette Application (with presenters), you can integrate it with Latte templates and create absolute links using `LinkGenerator`. |
| 233 | + |
| 234 | +### Email Templates with Links |
| 235 | + |
| 236 | +To use `n:href` and `{link}` in email templates, inject both `TemplateFactory` and `LinkGenerator`: |
| 237 | + |
| 238 | +```php |
| 239 | +use Nette; |
| 240 | + |
| 241 | +class MailSender |
| 242 | +{ |
| 243 | + public function __construct( |
| 244 | + private Nette\Application\LinkGenerator $linkGenerator, |
| 245 | + private Nette\Bridges\ApplicationLatte\TemplateFactory $templateFactory, |
| 246 | + ) { |
| 247 | + } |
| 248 | + |
| 249 | + |
| 250 | + private function createTemplate(): Nette\Application\UI\Template |
| 251 | + { |
| 252 | + $template = $this->templateFactory->createTemplate(); |
| 253 | + // Add LinkGenerator as 'uiControl' provider for n:href and {link} |
| 254 | + $template->getLatte()->addProvider('uiControl', $this->linkGenerator); |
| 255 | + return $template; |
| 256 | + } |
| 257 | + |
| 258 | + |
| 259 | + public function sendOrderConfirmation(int $orderId): void |
| 260 | + { |
| 261 | + $template = $this->createTemplate(); |
| 262 | + $html = $template->renderToString(__DIR__ . '/templates/orderEmail.latte', [ |
| 263 | + 'orderId' => $orderId, |
| 264 | + ]); |
| 265 | + |
| 266 | + $mail = new Nette\Mail\Message; |
| 267 | + $mail->setFrom('shop@example.com') |
| 268 | + ->addTo('customer@example.com') |
| 269 | + ->setHtmlBody($html); |
| 270 | + |
| 271 | + $this->mailer->send($mail); |
| 272 | + } |
| 273 | +} |
| 274 | +``` |
| 275 | + |
| 276 | +**Template with absolute links:** |
| 277 | + |
| 278 | +```latte |
| 279 | +<p>Your order #{$orderId} has been confirmed.</p> |
| 280 | +<p><a n:href="Order:detail $orderId">View order details</a></p> |
| 281 | +``` |
| 282 | + |
| 283 | +All links created via `LinkGenerator` are absolute (include full domain), which is required for emails. |
| 284 | + |
| 285 | +## Important Patterns |
| 286 | + |
| 287 | +### Encoding Detection |
| 288 | + |
| 289 | +The library automatically handles encoding with these patterns: |
| 290 | +- Uses `mb_detect_encoding()` for content detection |
| 291 | +- Defaults to UTF-8 for all string operations |
| 292 | +- Converts to ASCII for headers when needed |
| 293 | + |
| 294 | +### Header Management |
| 295 | + |
| 296 | +Headers are case-insensitive and normalized: |
| 297 | +- Storage: lowercase with first letter capitalized |
| 298 | +- Access: case-insensitive lookup |
| 299 | +- Special handling for To, Cc, Bcc, From headers |
| 300 | + |
| 301 | +### Image Embedding |
| 302 | + |
| 303 | +Automatic embedding supports: |
| 304 | +- `<img src="...">` |
| 305 | +- `<body background="...">` |
| 306 | +- CSS `url(...)` in style attributes |
| 307 | +- Special `[[filename]]` syntax |
| 308 | + |
| 309 | +### SendmailMailer Configuration |
| 310 | + |
| 311 | +`SendmailMailer` uses PHP's `mail()` function. To set return path when server overwrites it: |
| 312 | + |
| 313 | +```php |
| 314 | +$mailer = new Nette\Mail\SendmailMailer; |
| 315 | +$mailer->commandArgs = '-fmy@email.com'; // Set return path |
| 316 | +``` |
| 317 | + |
| 318 | +### SMTP Connection |
| 319 | + |
| 320 | +`SmtpMailer` handles SMTP protocol details: |
| 321 | +- Automatic STARTTLS negotiation |
| 322 | +- AUTH PLAIN and LOGIN support |
| 323 | +- Proper QUIT handling in persistent mode |
| 324 | +- Full error message parsing |
| 325 | +- Connection reuse with persistent mode |
0 commit comments