Skip to content

Commit 6887884

Browse files
committed
added CLAUDE.md
1 parent 86f3a25 commit 6887884

2 files changed

Lines changed: 349 additions & 0 deletions

File tree

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
.gitattributes export-ignore
22
.github/ export-ignore
33
.gitignore export-ignore
4+
CLAUDE.md export-ignore
45
ncs.* export-ignore
56
phpstan*.neon export-ignore
67
tests/ export-ignore

CLAUDE.md

Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
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.2 - 8.5, ext-iconv required
10+
- **Optional extensions:** ext-dom (CssInliner, PHP 8.4+), 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 four 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+
4. **CSS Inlining** (`src/Mail/`)
61+
- `CssInliner` - Converts CSS rules to inline `style` attributes for email HTML (requires PHP 8.4+ for `Dom\HTMLDocument`)
62+
63+
### Dependency Injection Integration
64+
65+
`src/Bridges/MailDI/MailExtension.php` - Nette DI compiler extension for configuration.
66+
67+
**DI Services registered:**
68+
- `mail.mailer` - Mailer instance (SendmailMailer or SmtpMailer based on config)
69+
- `mail.signer` - DKIM Signer instance (if DKIM is configured)
70+
- `nette.mailer` - Alias to mail.mailer (for backward compatibility)
71+
72+
**Configuration:**
73+
74+
```neon
75+
mail:
76+
# Use SmtpMailer instead of SendmailMailer
77+
smtp: true # (bool) defaults to false
78+
79+
# SMTP connection settings
80+
host: smtp.gmail.com # (string) SMTP server hostname
81+
port: 587 # (int) defaults: 25, 465 for ssl, 587 for tls
82+
username: user@example.com
83+
password: ****
84+
encryption: tls # (ssl|tls|null) null = no encryption
85+
timeout: 20 # (int) connection timeout in seconds, default 20
86+
persistent: false # (bool) use persistent connection
87+
clientHost: localhost # (string) defaults to $_SERVER['HTTP_HOST'] or 'localhost'
88+
89+
# SSL/TLS context options for SMTP connection
90+
context:
91+
ssl:
92+
verify_peer: true # NEVER set to false in production!
93+
verify_peer_name: true
94+
allow_self_signed: false # Do not allow self-signed certificates
95+
# See https://www.php.net/manual/en/context.ssl.php for all options
96+
97+
# DKIM signing configuration
98+
dkim:
99+
domain: example.com # Your domain name
100+
selector: dkim # DKIM selector from DNS
101+
privateKey: %appDir%/../dkim/private.key # Path to private key file
102+
passPhrase: **** # Optional passphrase for private key
103+
```
104+
105+
**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.
106+
107+
### Exception Hierarchy
108+
109+
All exceptions in `src/Mail/exceptions.php`:
110+
- `SendException` - Base exception for sending failures
111+
- `SmtpException` - SMTP-specific errors (extends SendException)
112+
- `FallbackMailerException` - All mailers failed (contains array of failures)
113+
- `SignException` - Signing/verification errors
114+
115+
### Key Features
116+
117+
**Message Creation:**
118+
- Fluent API with method chaining
119+
- Automatic text alternative generation from HTML
120+
- Auto-embedding images from filesystem using `[[...]]` syntax or `<img src=...>`
121+
- Subject auto-extraction from `<title>` element
122+
- Attachment support with auto-detection of MIME types
123+
124+
**MIME Handling:**
125+
- Encoding methods: Base64, 7bit, 8bit, quoted-printable
126+
- Line length management (76 characters default)
127+
- Full UTF-8 support throughout
128+
129+
**SMTP Features:**
130+
- TLS/SSL encryption support (`encryption: 'ssl'` or `'tls'`)
131+
- Default ports: 25 (unencrypted), 465 (SSL), 587 (TLS)
132+
- Persistent connections
133+
- Configurable timeout (default 20s)
134+
- Custom stream options for SSL context
135+
- Envelope sender support
136+
- AUTH PLAIN and LOGIN authentication methods
137+
138+
**DKIM Signing:**
139+
- RSA-SHA256 signing algorithm
140+
- Private key passphrase support
141+
- Automatic header canonicalization
142+
- Compatible with Gmail, Outlook, and other major providers
143+
144+
## Testing Strategy
145+
146+
Uses Nette Tester with `.phpt` format:
147+
148+
```php
149+
<?php
150+
declare(strict_types=1);
151+
152+
use Tester\Assert;
153+
154+
require __DIR__ . '/../bootstrap.php';
155+
156+
test('Message correctly sets recipient', function () {
157+
$mail = new Nette\Mail\Message;
158+
$mail->addTo('test@example.com');
159+
160+
Assert::same(['test@example.com' => null], $mail->getHeader('To'));
161+
});
162+
```
163+
164+
- **33 test files** covering all major functionality
165+
- Test fixtures in `tests/Mail/fixtures/` for email samples
166+
- Bootstrap in `tests/bootstrap.php` provides `test()` helper function
167+
- Tests run on PHP 8.2-8.5 in CI
168+
169+
## Coding Standards
170+
171+
Follows Nette Coding Standard (PSR-12 based) with these requirements:
172+
173+
- **Mandatory:** `declare(strict_types=1)` in all PHP files
174+
- **Indentation:** Tabs (not spaces)
175+
- **Method spacing:** Two empty lines between methods
176+
- **Types:** All properties, parameters, and return values must be typed
177+
- **Documentation:** Only when adding information beyond PHP types
178+
- Document array contents: `@return string[]`
179+
- Document nullable relationships: `@param ?string`
180+
- Skip obvious parameters (width, height, name)
181+
- **String quotes:** Single quotes unless containing apostrophes
182+
- **Naming:** PascalCase for classes, camelCase for methods/properties
183+
- **No prefixes:** No `Abstract`, `Interface`, or `I` prefixes
184+
185+
### Return Type Format
186+
187+
Opening brace on separate line after return type:
188+
189+
```php
190+
public function send(Message $mail):
191+
{
192+
// method body
193+
}
194+
```
195+
196+
### phpDoc Examples
197+
198+
```php
199+
/**
200+
* Adds email recipient.
201+
* @param string|array $email Address or [address => name] pairs
202+
*/
203+
public function addTo(string|array $email, ?string $name = null): static
204+
205+
/**
206+
* Sets message priority.
207+
*/
208+
public function setPriority(int $priority): static
209+
```
210+
211+
## Development Workflow
212+
213+
1. **Before making changes:**
214+
- Read existing code to understand patterns
215+
- Check related test files
216+
- Verify PHPStan passes: `composer run phpstan`
217+
218+
2. **When adding features:**
219+
- Add corresponding tests in `tests/Mail/`
220+
- Use `test()` helper for test cases
221+
- Run tests: `vendor/bin/tester tests -s`
222+
223+
3. **When fixing bugs:**
224+
- Add regression test first
225+
- Ensure fix doesn't break existing tests
226+
- Update PHPDoc if behavior changes
227+
228+
4. **Before committing:**
229+
- Run full test suite: `composer run tester`
230+
- Run static analysis: `composer run phpstan`
231+
- Check code style with Nette Code Checker
232+
233+
## Usage in Nette Application
234+
235+
When using Nette Mail within a full Nette Application (with presenters), you can integrate it with Latte templates and create absolute links using `LinkGenerator`.
236+
237+
### Email Templates with Links
238+
239+
To use `n:href` and `{link}` in email templates, inject both `TemplateFactory` and `LinkGenerator`:
240+
241+
```php
242+
use Nette;
243+
244+
class MailSender
245+
{
246+
public function __construct(
247+
private Nette\Application\LinkGenerator $linkGenerator,
248+
private Nette\Bridges\ApplicationLatte\TemplateFactory $templateFactory,
249+
) {
250+
}
251+
252+
253+
private function createTemplate(): Nette\Application\UI\Template
254+
{
255+
$template = $this->templateFactory->createTemplate();
256+
// Add LinkGenerator as 'uiControl' provider for n:href and {link}
257+
$template->getLatte()->addProvider('uiControl', $this->linkGenerator);
258+
return $template;
259+
}
260+
261+
262+
public function sendOrderConfirmation(int $orderId): void
263+
{
264+
$template = $this->createTemplate();
265+
$html = $template->renderToString(__DIR__ . '/templates/orderEmail.latte', [
266+
'orderId' => $orderId,
267+
]);
268+
269+
$mail = new Nette\Mail\Message;
270+
$mail->setFrom('shop@example.com')
271+
->addTo('customer@example.com')
272+
->setHtmlBody($html);
273+
274+
$this->mailer->send($mail);
275+
}
276+
}
277+
```
278+
279+
**Template with absolute links:**
280+
281+
```latte
282+
<p>Your order #{$orderId} has been confirmed.</p>
283+
<p><a n:href="Order:detail $orderId">View order details</a></p>
284+
```
285+
286+
All links created via `LinkGenerator` are absolute (include full domain), which is required for emails.
287+
288+
## Important Patterns
289+
290+
### Encoding Detection
291+
292+
The library automatically handles encoding with these patterns:
293+
- Uses `mb_detect_encoding()` for content detection
294+
- Defaults to UTF-8 for all string operations
295+
- Converts to ASCII for headers when needed
296+
297+
### Header Management
298+
299+
Headers are case-insensitive and normalized:
300+
- Storage: lowercase with first letter capitalized
301+
- Access: case-insensitive lookup
302+
- Special handling for To, Cc, Bcc, From headers
303+
304+
### Image Embedding
305+
306+
Automatic embedding supports:
307+
- `<img src="...">`
308+
- `<body background="...">`
309+
- CSS `url(...)` in style attributes
310+
- Special `[[filename]]` syntax
311+
312+
### SendmailMailer Configuration
313+
314+
`SendmailMailer` uses PHP's `mail()` function. To set return path when server overwrites it:
315+
316+
```php
317+
$mailer = new Nette\Mail\SendmailMailer;
318+
$mailer->commandArgs = '-fmy@email.com'; // Set return path
319+
```
320+
321+
### SMTP Connection
322+
323+
`SmtpMailer` handles SMTP protocol details:
324+
- Automatic STARTTLS negotiation
325+
- AUTH PLAIN and LOGIN support
326+
- Proper QUIT handling in persistent mode
327+
- Full error message parsing
328+
- Connection reuse with persistent mode
329+
330+
### CSS Inlining
331+
332+
`CssInliner` converts CSS rules to inline `style` attributes for email HTML compatibility. Uses `Dom\HTMLDocument` (PHP 8.4+) for DOM manipulation and CSS selectors.
333+
334+
```php
335+
// From <style> tags in HTML (automatically extracted, tag preserved)
336+
$html = (new CssInliner)->inline($html);
337+
338+
// External CSS string
339+
$html = (new CssInliner)->addCss('p { margin: 0; }')->inline($html);
340+
```
341+
342+
**Architecture:**
343+
- Single-regex tokenizer (comment, whitespace, string, url, escape, at-ident, hash, number, ident, char)
344+
- Token-based stylesheet parser with CSS nesting and @-rule skipping
345+
- Declarations parsed into `property => value` arrays (enables deduplication and HTML attribute generation)
346+
- Cascade: `<style>` → `addCss()` → existing inline `style=""` wins (last declaration wins)
347+
- `<style>` tags are preserved (keeps @media queries intact)
348+
- Automatic HTML attribute generation for Outlook compatibility (background-color→bgcolor, width→width, height→height, text-align→align, vertical-align→valign)

0 commit comments

Comments
 (0)