---
title: "Mail Project"
description: "Derafu Mail"
type: "docs"
category: "doc"
tags: [php]
authors: [Anonymous]
date: "2026-08-21"
last_update: "2026-08-21"
time_minutes: 1
draft: false
unlisted: false
url: "https://www.derafu.dev/docs/core/mail"
---

# Derafu Mail



---

## Introduction

Elegant orchestration of email communications for PHP

# Elegant orchestration of email communications for PHP

![GitHub last commit](https://img.shields.io/github/last-commit/derafu/mail/main)
![CI Workflow](https://github.com/derafu/mail/actions/workflows/ci.yml/badge.svg?branch=main&amp;event=push)
![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/derafu/mail)
![GitHub Issues](https://img.shields.io/github/issues-raw/derafu/mail)
![Total Downloads](https://poser.pugx.org/derafu/mail/downloads)
![Monthly Downloads](https://poser.pugx.org/derafu/mail/d/monthly)

A flexible PHP email library, built on Derafu Backbone architecture, that leverages other libraries and orchestrates the entire sending and receiving process.

## Overview

Derafu Mail provides a robust, extensible framework for sending and receiving emails in PHP applications. Built on the [Derafu Backbone architecture](https://www.derafu.dev/docs/core/backbone), it offers a clean, maintainable structure with clear separation of concerns.

## Features

- **Clean Architecture**: Follows the Derafu Backbone hierarchical structure.
- **Sending Emails**: SMTP support with easy extensibility for other transport methods.
- **Receiving Emails**: IMAP support with customizable search criteria and filtering.
- **Flexible Configuration**: Comprehensive options for both sending and receiving.
- **Robust Error Handling**: Proper exception handling throughout the library.
- **Attachment Management**: Support for both sending and receiving attachments.
- **Strategy Pattern**: Easily swap out sending/receiving implementations.

## Installation

Install the package via Composer:

```bash
composer require derafu/mail
```

## Quick Start

### Sending Emails

```php
use Derafu\Backbone\Contract\PackageRegistryInterface;
use Derafu\Mail\Model\Message;
use Derafu\Mail\Model\Envelope;
use Derafu\Mail\Model\Postman;
use Symfony\Component\Mime\Address;

// Find the package registry using dependency injection and get a $senderWorker
// Also you can inject directly the SenderWorkerInterface wherever you want.
$packageRegistry = $container-&gt;get(PackageRegistryInterface::class);
$mailPackage = $packageRegistry-&gt;getPackage(&#039;mail&#039;);
$exchangeComponent = $mailPackage-&gt;getExchangeComponent();
$senderWorker = $exchangeComponent-&gt;getSenderWorker();

// Create a message.
$message = new Message();
$message-&gt;subject(&#039;Hello World&#039;)
    -&gt;text(&#039;This is a plain text message&#039;)
    -&gt;html(&#039;&lt;h1&gt;Hello World&lt;/h1&gt;&lt;p&gt;This is an HTML message&lt;/p&gt;&#039;)
    -&gt;from(new Address(&#039;sender@example.com&#039;, &#039;Sender Name&#039;))
    -&gt;to(new Address(&#039;recipient@example.com&#039;, &#039;Recipient Name&#039;));

// Create an envelope and add the message.
$envelope = new Envelope(
    new Address(&#039;sender@example.com&#039;, &#039;Sender Name&#039;),
    [new Address(&#039;recipient@example.com&#039;, &#039;Recipient Name&#039;)]
);
$envelope-&gt;addMessage($message);

// Create a postman with SMTP configuration.
$postman = new Postman([
    &#039;strategy&#039; =&gt; &#039;smtp&#039;,
    &#039;transport&#039; =&gt; [
        &#039;host&#039; =&gt; &#039;smtp.example.com&#039;,
        &#039;port&#039; =&gt; 587,
        &#039;encryption&#039; =&gt; &#039;tls&#039;,
        &#039;username&#039; =&gt; &#039;your_username&#039;,
        &#039;password&#039; =&gt; &#039;your_password&#039;,
    ],
]);
$postman-&gt;addEnvelope($envelope);

// Send the email.
$envelopes = $senderWorker-&gt;send($postman);
```

### Receiving Emails

```php
use Derafu\Backbone\Contract\PackageRegistryInterface;
use Derafu\Mail\Model\Postman;

// Find the package registry using dependency injection and get a $receiverWorker
// Also you can inject directly the ReceiverWorkerInterface wherever you want.
$packageRegistry = $container-&gt;get(PackageRegistryInterface::class);
$mailPackage = $packageRegistry-&gt;getPackage(&#039;mail&#039;);
$exchangeComponent = $mailPackage-&gt;getExchangeComponent();
$receiverWorker = $exchangeComponent-&gt;getReceiverWorker();

// Create a postman with IMAP configuration.
$postman = new Postman([
    &#039;strategy&#039; =&gt; &#039;imap&#039;,
    &#039;transport&#039; =&gt; [
        &#039;host&#039; =&gt; &#039;imap.example.com&#039;,
        &#039;port&#039; =&gt; 993,
        &#039;encryption&#039; =&gt; &#039;ssl&#039;,
        &#039;username&#039; =&gt; &#039;your_username&#039;,
        &#039;password&#039; =&gt; &#039;your_password&#039;,
        &#039;mailbox&#039; =&gt; &#039;INBOX&#039;,
        &#039;search&#039; =&gt; [
            &#039;criteria&#039; =&gt; &#039;UNSEEN&#039;,
            &#039;markAsSeen&#039; =&gt; true,
            &#039;attachmentFilters&#039; =&gt; [
                &#039;extension&#039; =&gt; [&#039;pdf&#039;, &#039;doc&#039;, &#039;docx&#039;],
            ],
        ],
    ],
]);

// Receive emails.
$envelopes = $receiverWorker-&gt;receive($postman);

// Process received emails.
foreach ($envelopes as $envelope) {
    foreach ($envelope-&gt;getMessages() as $message) {
        echo &quot;Subject: &quot; . $message-&gt;getSubject() . PHP_EOL;
        echo &quot;From: &quot; . $message-&gt;getFrom()[0]-&gt;getAddress() . PHP_EOL;
        echo &quot;Body: &quot; . $message-&gt;getTextBody() . PHP_EOL;

        // Process attachments.
        foreach ($message-&gt;getAttachments() as $attachment) {
            file_put_contents(&#039;/path/to/save/&#039; . $attachment-&gt;getFilename(), $attachment-&gt;getBody());
        }
    }
}
```

## Extending with New Strategies

The library is designed to be easily extended with new sending or receiving strategies:

1. Create a new strategy class implementing `SenderStrategyInterface` or `ReceiverStrategyInterface`.
2. Tag it with the appropriate attribute.

Example:
```php
use Derafu\Backbone\Attribute\Strategy;
use Derafu\Mail\Component\Exchange\Worker\Sender\Strategy\Abstract\AbstractMailerStrategy;
use Derafu\Mail\Component\Exchange\Worker\Sender\Strategy\Contract\SenderStrategyInterface;

#[Strategy(name: &#039;mailgun&#039;, worker: &#039;sender&#039;, component: &#039;exchange&#039;, package: &#039;mail&#039;)]
class MailgunStrategy extends AbstractMailerStrategy implements SenderStrategyInterface
{
    // Implementation of SenderStrategyInterface that leverages AbstractMailerStrategy.
}
```

## Architecture

Derafu Mail follows the Derafu Backbone architecture:

- **Package**: MailPackage - The main entry point.
- **Component**: ExchangeComponent - Handles email exchange.
- **Workers**: SenderWorker and ReceiverWorker - Handle sending and receiving.
- **Handlers**: SendHandler and ReceiveHandler - Orchestrate the process.
- **Strategies**: Implement different methods of sending or receiving.




---

## Architecture

Derafu Mail Architecture

# Derafu Mail Architecture

This guide explains the architecture of the Derafu Mail library, which is built on the Derafu Backbone architectural framework. Understanding this architecture will help you effectively use and extend the library.

## Architectural Overview

Derafu Mail follows a hierarchical architecture that separates concerns into distinct layers, each with specific responsibilities:

1. **Package Layer**: The entry point and container for all components.
2. **Component Layer**: Functional modules within the package.
3. **Worker Layer**: Task executors within components.
4. **Handler Layer**: Process orchestrators that coordinate strategies.
5. **Strategy Layer**: Specific implementations of mail operations.
6. **Model Layer**: Domain models representing email-related entities.

![Derafu Mail Architecture](https://www.derafu.dev/img/diagrams/content/docs/core/mail/derafu-mail-architecture.svg)

## Package Layer

The `MailPackage` serves as the entry point for the entire library, following the Package pattern from Derafu Backbone.

```php
#[Package(name: &#039;mail&#039;)]
class MailPackage extends AbstractPackage implements MailPackageInterface
```

**Responsibilities**:

- Provides access to components (currently just `ExchangeComponent`).
- Acts as the root of the dependency tree.
- Maintains registry information for service discovery.

## Component Layer

The `ExchangeComponent` represents a specific functional area within the mail domain, handling both sending and receiving of emails.

```php
#[Component(name: &#039;exchange&#039;, package: &#039;mail&#039;)]
class ExchangeComponent extends AbstractComponent implements ExchangeComponentInterface
```

**Responsibilities**:

- Manages workers for sending and receiving emails.
- Provides access to these workers through getter methods.
- Groups related functionality under a single namespace.

## Worker Layer

Workers expose the public API for specific tasks:

### SenderWorker

```php
#[Worker(name: &#039;sender&#039;, component: &#039;exchange&#039;, package: &#039;mail&#039;)]
class SenderWorker extends AbstractWorker implements SenderWorkerInterface
```

**Responsibilities**:

- Provides a public `send()` method for sending emails.
- Delegates the actual sending process to handlers.
- Manages any worker-specific resources.

### ReceiverWorker

```php
#[Worker(name: &#039;receiver&#039;, component: &#039;exchange&#039;, package: &#039;mail&#039;)]
class ReceiverWorker extends AbstractWorker implements ReceiverWorkerInterface
```

**Responsibilities**:

- Provides a public `receive()` method for receiving emails.
- Delegates the receiving process to handlers.
- Manages any worker-specific resources.

## Handler Layer

Handlers orchestrate complex processes, selecting appropriate strategies and managing the workflow:

### SendHandler

```php
class SendHandler extends AbstractHandler
```

**Responsibilities**:

- Selects the appropriate sending strategy based on configuration.
- Orchestrates the email sending process.
- Handles errors in a centralized manner.
- Manages options and configuration.

### ReceiveHandler

```php
class ReceiveHandler extends AbstractHandler
```

**Responsibilities**:

- Selects the appropriate receiving strategy based on configuration.
- Orchestrates the email receiving process.
- Handles errors in a centralized manner.
- Manages options and configuration.

## Strategy Layer

Strategies implement specific methods for sending or receiving emails:

### SmtpStrategy

```php
#[Strategy(name: &#039;smtp&#039;, worker: &#039;sender&#039;, component: &#039;exchange&#039;, package: &#039;mail&#039;)]
class SmtpStrategy extends AbstractMailerStrategy implements SenderStrategyInterface
```

**Responsibilities**:

- Configures and uses Symfony Mailer for SMTP transport.
- Builds appropriate DSN string based on configuration.
- Handles the actual sending of emails.
- Manages SMTP-specific settings.

### ImapStrategy

```php
#[Strategy(name: &#039;imap&#039;, worker: &#039;receiver&#039;, component: &#039;exchange&#039;, package: &#039;mail&#039;)]
class ImapStrategy extends AbstractMailboxStrategy implements ReceiverStrategyInterface
```

**Responsibilities**:

- Configures and uses PHP-IMAP for IMAP access.
- Builds appropriate DSN string for IMAP connection.
- Searches for and retrieves emails based on criteria.
- Manages IMAP-specific settings.

## Abstract Base Classes

The library provides abstract base classes that implement common functionality:

### AbstractMailerStrategy

```php
abstract class AbstractMailerStrategy extends AbstractStrategy implements SenderStrategyInterface
```

**Responsibilities**:

- Provides common code for email sending strategies.
- Handles the creation of Symfony Mailer instances.
- Processes envelopes and messages.

### AbstractMailboxStrategy

```php
abstract class AbstractMailboxStrategy extends AbstractStrategy implements ReceiverStrategyInterface
```

**Responsibilities**:

- Provides common code for email receiving strategies.
- Handles the creation of Mailbox instances.
- Processes received emails into envelopes.

## Model Layer

Domain models represent the core entities of the email domain:

### Envelope

```php
class Envelope extends SymfonyEnvelope implements EnvelopeInterface
```

**Responsibilities**:

- Contains sender and recipient information.
- Holds one or more messages.
- Provides a container for email communication.

### Message

```php
class Message extends SymfonyEmail implements MessageInterface
```

**Responsibilities**:

- Represents an individual email message.
- Contains subject, body, attachments, etc.
- Tracks sending/receiving errors.

### Postman

```php
class Postman implements PostmanInterface
```

**Responsibilities**:

- Acts as a transport container for envelopes.
- Holds configuration options for sending/receiving.
- Provides a unified interface for email operations.

### Mailbox

```php
class Mailbox implements MailboxInterface
```

**Responsibilities**:

- Represents an email mailbox (IMAP folder).
- Provides methods for searching and retrieving emails.
- Handles connection to mail servers.

## Data Flow Examples

### Sending an Email

1. Client code creates a Message and adds it to an Envelope.
2. Envelope is added to a Postman with SMTP configuration.
3. Client calls SenderWorker&#039;s send() method with the Postman.
4. SenderWorker delegates to SendHandler.
5. SendHandler selects SmtpStrategy based on configuration.
6. SmtpStrategy uses Symfony Mailer to send the emails.
7. Results are returned through the same chain.

### Receiving Emails

1. Client creates a Postman with IMAP configuration.
2. Client calls ReceiverWorker&#039;s receive() method with the Postman.
3. ReceiverWorker delegates to ReceiveHandler.
4. ReceiveHandler selects ImapStrategy based on configuration.
5. ImapStrategy connects to the mailbox and retrieves emails.
6. Retrieved emails are converted to Envelopes with Messages.
7. Envelopes are added to the Postman and returned.

## Extending the Library

The architecture makes it easy to extend the library with new strategies:

1. **New Sending Strategy**: Create a class that extends AbstractMailerStrategy or implements SenderStrategyInterface.
2. **New Receiving Strategy**: Create a class that extends AbstractMailboxStrategy or implements ReceiverStrategyInterface.
3. **Tag with Attribute**: Use the #[Strategy] attribute for automatic discovery.
4. **Use via Configuration**: Specify your strategy in the Postman options.

## Key Benefits of This Architecture

1. **Separation of Concerns**: Each class has a single, well-defined responsibility.
2. **Extensibility**: Easy to add new strategies without modifying existing code.
3. **Testability**: Clean interfaces make unit testing straightforward.
4. **Configurability**: Comprehensive options at every level.
5. **Maintainability**: Clear structure makes code easy to understand and modify.

## Conclusion

Derafu Mail&#039;s architecture provides a solid foundation for email operations in PHP applications. By leveraging the Derafu Backbone patterns, it achieves a clean separation of concerns while remaining flexible and extensible.

Understanding this architecture will help you effectively use the library and extend it with new capabilities when needed.




---

## Custom Sender Strategy

Creating a Custom Sender Strategy for Derafu Mail

# Creating a Custom Sender Strategy for Derafu Mail

This guide explains how to create custom sender strategies for Derafu Mail. Sender strategies allow you to implement different methods of sending emails beyond the default SMTP implementation.

## Understanding Sender Strategies

A sender strategy in Derafu Mail is responsible for the actual transmission of email messages. The library comes with an SMTP implementation, but you might want to add support for:

- API-based email services (SendGrid, Mailgun, Postmark, etc.).
- Custom internal email systems.
- Database-based email queues.
- Testing/mock implementations.

## Option 1: Extending AbstractMailerStrategy

The simplest approach is to extend the `AbstractMailerStrategy` class, which provides reusable functionality for most email sending scenarios that utilize Symfony Mailer internally.

### When to Use This Approach

- When your email service has a Symfony Transport implementation.
- When you need to reuse the core sending logic.
- When your strategy follows a similar workflow to the SMTP strategy.

### Implementation Steps

1. Create a new class that extends `AbstractMailerStrategy`:

```php
&lt;?php

declare(strict_types=1);

namespace YourNamespace\Strategy;

use Derafu\Backbone\Attribute\Strategy;
use Derafu\Config\Contract\OptionsInterface;
use Derafu\Mail\Component\Exchange\Worker\Sender\Strategy\Abstract\AbstractMailerStrategy;
use Derafu\Mail\Component\Exchange\Worker\Sender\Strategy\Contract\SenderStrategyInterface;

#[Strategy(name: &#039;mailgun&#039;, worker: &#039;sender&#039;, component: &#039;exchange&#039;, package: &#039;mail&#039;)]
class MailgunStrategy extends AbstractMailerStrategy implements SenderStrategyInterface
{
    /**
     * Schema of the options.
     *
     * @var array&lt;string,array&gt;
     */
    protected array $optionsSchema = [
        &#039;strategy&#039; =&gt; [
            &#039;types&#039; =&gt; &#039;string&#039;,
            &#039;default&#039; =&gt; &#039;mailgun&#039;,
        ],
        &#039;transport&#039; =&gt; [
            &#039;types&#039; =&gt; &#039;array&#039;,
            &#039;schema&#039; =&gt; [
                &#039;api_key&#039; =&gt; [
                    &#039;types&#039; =&gt; &#039;string&#039;,
                    &#039;required&#039; =&gt; true,
                ],
                &#039;domain&#039; =&gt; [
                    &#039;types&#039; =&gt; &#039;string&#039;,
                    &#039;required&#039; =&gt; true,
                ],
                &#039;region&#039; =&gt; [
                    &#039;types&#039; =&gt; &#039;string&#039;,
                    &#039;default&#039; =&gt; &#039;us&#039;,
                ],
                &#039;dsn&#039; =&gt; [
                    &#039;types&#039; =&gt; &#039;string&#039;,
                ],
                &#039;endpoint&#039; =&gt; [
                    &#039;types&#039; =&gt; &#039;string&#039;,
                ],
            ],
        ],
    ];

    /**
     * {@inheritDoc}
     */
    protected function resolveDsn(OptionsInterface $options): string
    {
        $transportOptions = $options-&gt;get(&#039;transport&#039;);

        if (!empty($transportOptions[&#039;dsn&#039;])) {
            return $transportOptions[&#039;dsn&#039;];
        }

        // Construct the DSN for Mailgun using Symfony&#039;s format.
        $dsn = sprintf(
            &#039;mailgun://%s@%s?region=%s&#039;,
            $transportOptions[&#039;api_key&#039;],
            $transportOptions[&#039;domain&#039;],
            $transportOptions[&#039;region&#039;] ?? &#039;us&#039;
        );

        $options-&gt;set(&#039;transport.dsn&#039;, $dsn);

        return $dsn;
    }

    /**
     * {@inheritDoc}
     */
    protected function resolveEndpoint(OptionsInterface $options): string
    {
        $transportOptions = $options-&gt;get(&#039;transport&#039;);

        if (!empty($transportOptions[&#039;endpoint&#039;])) {
            return $transportOptions[&#039;endpoint&#039;];
        }

        $endpoint = sprintf(
            &#039;mailgun://%s&#039;,
            $transportOptions[&#039;domain&#039;]
        );

        $options-&gt;set(&#039;transport.endpoint&#039;, $endpoint);

        return $endpoint;
    }
}
```

2. Define your options schema to specify the required configuration.

3. Implement the `resolveDsn()` method to build the appropriate DSN string for your service.

4. Implement the `resolveEndpoint()` method to provide a human-readable representation of the endpoint.

### Key Benefits

- Leverages existing functionality from the abstract class.
- Reduces code duplication.
- Ensures consistent behavior across strategies.
- Automatically gets error handling and envelope processing.

## Option 2: Implementing SenderStrategyInterface

For more specialized use cases, you can implement the `SenderStrategyInterface` directly.

### When to Use This Approach

- When your sending mechanism is fundamentally different from Symfony Mailer.
- When you need complete control over the sending process.
- When you want to avoid dependencies on Symfony components.
- For custom integrations with proprietary systems

### Implementation Steps

1. Create a new class that implements `SenderStrategyInterface`:

```php
&lt;?php

declare(strict_types=1);

namespace YourNamespace\Strategy;

use Derafu\Backbone\Abstract\AbstractStrategy;
use Derafu\Backbone\Attribute\Strategy;
use Derafu\Config\Contract\OptionsInterface;
use Derafu\Mail\Component\Exchange\Worker\Sender\Strategy\Contract\SenderStrategyInterface;
use Derafu\Mail\Exception\MailException;
use Derafu\Mail\Model\Contract\PostmanInterface;
use GuzzleHttp\Client;
use Throwable;

#[Strategy(name: &#039;custom-api&#039;, worker: &#039;sender&#039;, component: &#039;exchange&#039;, package: &#039;mail&#039;)]
class CustomApiStrategy extends AbstractStrategy implements SenderStrategyInterface
{
    /**
     * Schema of the options.
     *
     * @var array&lt;string,array&gt;
     */
    protected array $optionsSchema = [
        &#039;strategy&#039; =&gt; [
            &#039;types&#039; =&gt; &#039;string&#039;,
            &#039;default&#039; =&gt; &#039;custom-api&#039;,
        ],
        &#039;transport&#039; =&gt; [
            &#039;types&#039; =&gt; &#039;array&#039;,
            &#039;schema&#039; =&gt; [
                &#039;api_url&#039; =&gt; [
                    &#039;types&#039; =&gt; &#039;string&#039;,
                    &#039;required&#039; =&gt; true,
                ],
                &#039;api_key&#039; =&gt; [
                    &#039;types&#039; =&gt; &#039;string&#039;,
                    &#039;required&#039; =&gt; true,
                ],
                // Add any other configuration options needed.
            ],
        ],
    ];

    /**
     * HTTP client for API requests.
     */
    private Client $httpClient;

    /**
     * Constructor.
     */
    public function __construct()
    {
        $this-&gt;httpClient = new Client();
    }

    /**
     * {@inheritDoc}
     */
    public function send(PostmanInterface $postman): array
    {
        $options = $this-&gt;resolveOptions($postman-&gt;getOptions());
        $transportOptions = $options-&gt;get(&#039;transport&#039;);

        $apiUrl = $transportOptions[&#039;api_url&#039;];
        $apiKey = $transportOptions[&#039;api_key&#039;];

        foreach ($postman-&gt;getEnvelopes() as $envelope) {
            foreach ($envelope-&gt;getMessages() as $message) {
                try {
                    // Transform the message to your API format.
                    $payload = $this-&gt;transformMessageToApiPayload($message, $envelope);

                    // Send via your custom API.
                    $response = $this-&gt;httpClient-&gt;post($apiUrl, [
                        &#039;headers&#039; =&gt; [
                            &#039;Authorization&#039; =&gt; &#039;Bearer &#039; . $apiKey,
                            &#039;Content-Type&#039; =&gt; &#039;application/json&#039;,
                        ],
                        &#039;json&#039; =&gt; $payload,
                    ]);

                    // Process response if needed.
                    if ($response-&gt;getStatusCode() &gt;= 400) {
                        throw new MailException(&#039;API returned error: &#039; . $response-&gt;getBody());
                    }

                } catch (Throwable $e) {
                    $message-&gt;error($e);
                }
            }
        }

        return $postman-&gt;getEnvelopes();
    }

    /**
     * Transforms a message to the format expected by the API.
     *
     * @param MessageInterface $message
     * @param EnvelopeInterface $envelope
     * @return array
     */
    private function transformMessageToApiPayload($message, $envelope): array
    {
        // Implement the transformation logic for your specific API.
        // This is where you map the Message and Envelope properties
        // to whatever format your API expects.

        return [
            &#039;from&#039; =&gt; $this-&gt;formatAddress($message-&gt;getFrom()[0]),
            &#039;to&#039; =&gt; array_map([$this, &#039;formatAddress&#039;], $message-&gt;getTo()),
            &#039;subject&#039; =&gt; $message-&gt;getSubject(),
            &#039;text&#039; =&gt; $message-&gt;getTextBody(),
            &#039;html&#039; =&gt; $message-&gt;getHtmlBody(),
            // Handle attachments, CC, BCC, etc.
        ];
    }

    /**
     * Formats an email address for the API.
     */
    private function formatAddress($address): array
    {
        return [
            &#039;email&#039; =&gt; $address-&gt;getAddress(),
            &#039;name&#039; =&gt; $address-&gt;getName(),
        ];
    }
}
```

2. Define your options schema to specify the required configuration.

3. Implement the `send()` method to handle the entire sending process.

4. Add any helper methods needed for your specific implementation.

### Key Considerations

When implementing from scratch:

- **Error Handling**: You must handle all exceptions and errors.
- **Message Processing**: You need to transform Derafu Mail messages to your API format.
- **State Management**: Consider how to track message status and handle failures.
- **Testing**: Create test cases for various scenarios and error conditions.

## Usage

Once you&#039;ve created your custom strategy, you can use it by specifying its name in the Postman configuration:

```php
$postman = new Postman([
    &#039;strategy&#039; =&gt; &#039;mailgun&#039;, // Or &#039;custom-api&#039;
    &#039;transport&#039; =&gt; [
        // Strategy-specific configuration options.
        &#039;api_key&#039; =&gt; &#039;your-api-key&#039;,
        &#039;domain&#039; =&gt; &#039;your-domain.com&#039;,
        // Other options...
    ],
]);
```

## Best Practices

1. **Error Handling**: Always catch and properly handle exceptions.
2. **Logging**: Add appropriate logging to help troubleshoot issues.
3. **Configuration Validation**: Use the options schema to validate configuration.
4. **Comprehensive Documentation**: Document your strategy&#039;s requirements.
5. **Unit Testing**: Create tests for various scenarios including error cases.

## Conclusion

Creating custom sender strategies allows you to extend Derafu Mail to work with any email service or system. Whether you extend the abstract class or implement the interface directly depends on your specific needs and how much you want to leverage the existing infrastructure.

For most API-based email services that have Symfony Transport implementations, extending `AbstractMailerStrategy` is recommended. For completely custom implementations, implementing `SenderStrategyInterface` directly gives you maximum flexibility.




---

## Custom Receiver Strategy

Creating a Custom Receiver Strategy for Derafu Mail

# Creating a Custom Receiver Strategy for Derafu Mail

This guide explains how to create custom receiver strategies for Derafu Mail. Receiver strategies allow you to implement different methods of retrieving emails beyond the default IMAP implementation.

## Understanding Receiver Strategies

A receiver strategy in Derafu Mail is responsible for connecting to mail sources and retrieving messages. The library comes with an IMAP implementation, but you might want to add support for:

- API-based email services (Gmail API, Microsoft Graph, etc.).
- Custom email storage systems.
- Database-stored emails.
- Webhook receivers for incoming emails.
- Testing/mock implementations.

## Option 1: Extending AbstractMailboxStrategy

The simplest approach is to extend the `AbstractMailboxStrategy` class, which provides reusable functionality for email retrieval scenarios that use a mailbox-like interface.

### When to Use This Approach

- When your email source follows a mailbox paradigm.
- When you can use the PHP-IMAP library or similar interfaces.
- When you need to reuse common mailbox operations.
- When your strategy follows a similar workflow to the IMAP strategy.

### Implementation Steps

1. Create a new class that extends `AbstractMailboxStrategy`:

```php
&lt;?php

declare(strict_types=1);

namespace YourNamespace\Strategy;

use Derafu\Backbone\Attribute\Strategy;
use Derafu\Config\Contract\OptionsInterface;
use Derafu\Mail\Component\Exchange\Worker\Receiver\Strategy\Abstract\AbstractMailboxStrategy;
use Derafu\Mail\Component\Exchange\Worker\Receiver\Strategy\Contract\ReceiverStrategyInterface;
use Derafu\Mail\Model\Mailbox;
use Derafu\Mail\Model\Contract\MailboxInterface;

#[Strategy(name: &#039;gmail-api&#039;, worker: &#039;receiver&#039;, component: &#039;exchange&#039;, package: &#039;mail&#039;)]
class GmailApiStrategy extends AbstractMailboxStrategy implements ReceiverStrategyInterface
{
    /**
     * Schema of the options.
     *
     * @var array&lt;string,array&gt;
     */
    protected array $optionsSchema = [
        &#039;strategy&#039; =&gt; [
            &#039;types&#039; =&gt; &#039;string&#039;,
            &#039;default&#039; =&gt; &#039;gmail-api&#039;,
        ],
        &#039;transport&#039; =&gt; [
            &#039;types&#039; =&gt; &#039;array&#039;,
            &#039;schema&#039; =&gt; [
                &#039;client_id&#039; =&gt; [
                    &#039;types&#039; =&gt; &#039;string&#039;,
                    &#039;required&#039; =&gt; true,
                ],
                &#039;client_secret&#039; =&gt; [
                    &#039;types&#039; =&gt; &#039;string&#039;,
                    &#039;required&#039; =&gt; true,
                ],
                &#039;refresh_token&#039; =&gt; [
                    &#039;types&#039; =&gt; &#039;string&#039;,
                    &#039;required&#039; =&gt; true,
                ],
                &#039;user_email&#039; =&gt; [
                    &#039;types&#039; =&gt; &#039;string&#039;,
                    &#039;required&#039; =&gt; true,
                ],
                &#039;label&#039; =&gt; [
                    &#039;types&#039; =&gt; &#039;string&#039;,
                    &#039;default&#039; =&gt; &#039;INBOX&#039;,
                ],
                &#039;dsn&#039; =&gt; [
                    &#039;types&#039; =&gt; &#039;string&#039;,
                ],
                &#039;endpoint&#039; =&gt; [
                    &#039;types&#039; =&gt; &#039;string&#039;,
                ],
                &#039;search&#039; =&gt; [
                    &#039;types&#039; =&gt; &#039;array&#039;,
                    &#039;schema&#039; =&gt; [
                        &#039;query&#039; =&gt; [
                            &#039;types&#039; =&gt; &#039;string&#039;,
                            &#039;default&#039; =&gt; &#039;is:unread&#039;,
                        ],
                        &#039;markAsSeen&#039; =&gt; [
                            &#039;types&#039; =&gt; &#039;bool&#039;,
                            &#039;default&#039; =&gt; false,
                        ],
                        &#039;attachmentFilters&#039; =&gt; [
                            &#039;types&#039; =&gt; &#039;array&#039;,
                            &#039;default&#039; =&gt; [],
                        ],
                    ],
                ],
            ],
        ],
    ];

    /**
     * {@inheritDoc}
     */
    protected function createMailbox(OptionsInterface $options): MailboxInterface
    {
        // Instead of using the standard Mailbox, create a specialized Gmail API mailbox.
        // This could be a custom class that implements MailboxInterface.

        $transportOptions = $options-&gt;get(&#039;transport&#039;);

        // This would be a custom implementation for Gmail API.
        return new GmailApiMailbox(
            $transportOptions[&#039;client_id&#039;],
            $transportOptions[&#039;client_secret&#039;],
            $transportOptions[&#039;refresh_token&#039;],
            $transportOptions[&#039;user_email&#039;],
            $transportOptions[&#039;label&#039;] ?? &#039;INBOX&#039;
        );
    }

    /**
     * {@inheritDoc}
     */
    protected function resolveDsn(OptionsInterface $options): string
    {
        $transportOptions = $options-&gt;get(&#039;transport&#039;);

        if (!empty($transportOptions[&#039;dsn&#039;])) {
            return $transportOptions[&#039;dsn&#039;];
        }

        // Construct a representative DSN for Gmail API.
        $dsn = sprintf(
            &#039;gmail-api://%s&#039;,
            $transportOptions[&#039;user_email&#039;]
        );

        $options-&gt;set(&#039;transport.dsn&#039;, $dsn);

        return $dsn;
    }

    /**
     * {@inheritDoc}
     */
    protected function resolveEndpoint(OptionsInterface $options): string
    {
        $transportOptions = $options-&gt;get(&#039;transport&#039;);

        if (!empty($transportOptions[&#039;endpoint&#039;])) {
            return $transportOptions[&#039;endpoint&#039;];
        }

        $endpoint = sprintf(
            &#039;https://gmail.googleapis.com/gmail/v1/users/%s&#039;,
            $transportOptions[&#039;user_email&#039;]
        );

        $options-&gt;set(&#039;transport.endpoint&#039;, $endpoint);

        return $endpoint;
    }
}

/**
 * Custom Mailbox implementation for Gmail API.
 * This class would need to implement all methods from MailboxInterface
 * but would use the Gmail API instead of IMAP.
 */
class GmailApiMailbox implements MailboxInterface
{
    // Implement all required methods from MailboxInterface.
    // This would use Google API Client or similar to fetch emails.
}
```

2. Define your options schema to specify the required configuration.

3. Override the `createMailbox()` method to return your custom mailbox implementation.

4. Implement the `resolveDsn()` and `resolveEndpoint()` methods.

5. Create a custom mailbox class that implements `MailboxInterface` if needed.

### Key Benefits

- Leverages existing workflow and error handling.
- Preserves compatibility with the rest of the library.
- Reuses attachment filtering and other common functionality.
- Maintains consistent behavior across strategies.

## Option 2: Implementing ReceiverStrategyInterface

For more specialized use cases, you can implement the `ReceiverStrategyInterface` directly.

### When to Use This Approach

- When your receiving mechanism is fundamentally different from a mailbox model.
- When you need complete control over the receiving process.
- When you&#039;re creating a strategy for webhooks or other non-polling mechanisms.
- For custom integrations with proprietary systems.

### Implementation Steps

1. Create a new class that implements `ReceiverStrategyInterface`:

```php
&lt;?php

declare(strict_types=1);

namespace YourNamespace\Strategy;

use Derafu\Backbone\Abstract\AbstractStrategy;
use Derafu\Backbone\Attribute\Strategy;
use Derafu\Mail\Component\Exchange\Worker\Receiver\Strategy\Contract\ReceiverStrategyInterface;
use Derafu\Mail\Exception\MailException;
use Derafu\Mail\Model\Contract\EnvelopeInterface;
use Derafu\Mail\Model\Contract\MessageInterface;
use Derafu\Mail\Model\Contract\PostmanInterface;
use Derafu\Mail\Model\Envelope;
use Derafu\Mail\Model\Message;
use Symfony\Component\Mime\Address;
use Throwable;

#[Strategy(name: &#039;webhook-receiver&#039;, worker: &#039;receiver&#039;, component: &#039;exchange&#039;, package: &#039;mail&#039;)]
class WebhookReceiverStrategy extends AbstractStrategy implements ReceiverStrategyInterface
{
    /**
     * Schema of the options.
     *
     * @var array&lt;string,array&gt;
     */
    protected array $optionsSchema = [
        &#039;strategy&#039; =&gt; [
            &#039;types&#039; =&gt; &#039;string&#039;,
            &#039;default&#039; =&gt; &#039;webhook-receiver&#039;,
        ],
        &#039;transport&#039; =&gt; [
            &#039;types&#039; =&gt; &#039;array&#039;,
            &#039;schema&#039; =&gt; [
                &#039;webhook_data&#039; =&gt; [
                    &#039;types&#039; =&gt; &#039;array&#039;,
                    &#039;required&#039; =&gt; true,
                ],
                &#039;secret_key&#039; =&gt; [
                    &#039;types&#039; =&gt; &#039;string&#039;,
                    &#039;default&#039; =&gt; &#039;&#039;,
                ],
                // Add any other configuration options needed.
            ],
        ],
    ];

    /**
     * {@inheritDoc}
     */
    public function receive(PostmanInterface $postman): array
    {
        $options = $this-&gt;resolveOptions($postman-&gt;getOptions());
        $transportOptions = $options-&gt;get(&#039;transport&#039;);

        $webhookData = $transportOptions[&#039;webhook_data&#039;];
        $secretKey = $transportOptions[&#039;secret_key&#039;] ?? &#039;&#039;;

        try {
            // Validate webhook data if a secret is configured.
            if ($secretKey &amp;&amp; !$this-&gt;validateWebhookSignature($webhookData, $secretKey)) {
                throw new MailException(&#039;Invalid webhook signature&#039;);
            }

            // Process the webhook data to extract email information.
            $emails = $this-&gt;processWebhookData($webhookData);

            // Create envelopes and add them to the postman.
            foreach ($emails as $emailData) {
                $envelope = $this-&gt;createEnvelope($emailData);
                $postman-&gt;addEnvelope($envelope);
            }

            // Optionally acknowledge receipt to the webhook source.
            $this-&gt;acknowledgeReceipt($webhookData);

        } catch (Throwable $e) {
            throw new MailException(
                sprintf(
                    &#039;An error occurred while processing webhook data: %s&#039;,
                    $e-&gt;getMessage()
                ),
                0,
                $e
            );
        }

        return $postman-&gt;getEnvelopes();
    }

    /**
     * Validates the webhook signature to ensure authenticity.
     *
     * @param array $webhookData
     * @param string $secretKey
     * @return bool
     */
    private function validateWebhookSignature(array $webhookData, string $secretKey): bool
    {
        // Implement signature validation logic.
        // The exact implementation depends on how your webhook source signs requests.
        return true; // Placeholder.
    }

    /**
     * Processes the webhook data to extract email information.
     *
     * @param array $webhookData
     * @return array
     */
    private function processWebhookData(array $webhookData): array
    {
        // Convert webhook data to a standardized email format.
        // This will depend entirely on the webhook format you&#039;re receiving.

        // Placeholder implementation - extract email data from webhook.
        $emails = [];

        if (isset($webhookData[&#039;emails&#039;]) &amp;&amp; is_array($webhookData[&#039;emails&#039;])) {
            foreach ($webhookData[&#039;emails&#039;] as $email) {
                $emails[] = [
                    &#039;from&#039; =&gt; $email[&#039;sender&#039;] ?? &#039;&#039;,
                    &#039;from_name&#039; =&gt; $email[&#039;sender_name&#039;] ?? &#039;&#039;,
                    &#039;to&#039; =&gt; $email[&#039;recipient&#039;] ?? &#039;&#039;,
                    &#039;to_name&#039; =&gt; $email[&#039;recipient_name&#039;] ?? &#039;&#039;,
                    &#039;subject&#039; =&gt; $email[&#039;subject&#039;] ?? &#039;&#039;,
                    &#039;text_body&#039; =&gt; $email[&#039;plain_text&#039;] ?? &#039;&#039;,
                    &#039;html_body&#039; =&gt; $email[&#039;html&#039;] ?? &#039;&#039;,
                    &#039;attachments&#039; =&gt; $email[&#039;attachments&#039;] ?? [],
                ];
            }
        }

        return $emails;
    }

    /**
     * Creates an envelope from email data.
     *
     * @param array $emailData
     * @return EnvelopeInterface
     */
    private function createEnvelope(array $emailData): EnvelopeInterface
    {
        // Create a sender address.
        $sender = new Address(
            $emailData[&#039;from&#039;],
            $emailData[&#039;from_name&#039;] ?? &#039;&#039;
        );

        // Create recipient addresses.
        $recipients = [
            new Address(
                $emailData[&#039;to&#039;],
                $emailData[&#039;to_name&#039;] ?? &#039;&#039;
            )
        ];

        // Create the envelope.
        $envelope = new Envelope($sender, $recipients);

        // Create and add the message.
        $message = $this-&gt;createMessage($emailData);
        $envelope-&gt;addMessage($message);

        return $envelope;
    }

    /**
     * Creates a message from email data.
     *
     * @param array $emailData
     * @return MessageInterface
     */
    private function createMessage(array $emailData): MessageInterface
    {
        // Create the message.
        $message = new Message();

        // Set basic properties
        $message-&gt;subject($emailData[&#039;subject&#039;] ?? &#039;&#039;);

        if (!empty($emailData[&#039;text_body&#039;])) {
            $message-&gt;text($emailData[&#039;text_body&#039;]);
        }

        if (!empty($emailData[&#039;html_body&#039;])) {
            $message-&gt;html($emailData[&#039;html_body&#039;]);
        }

        $message-&gt;from(new Address(
            $emailData[&#039;from&#039;],
            $emailData[&#039;from_name&#039;] ?? &#039;&#039;
        ));

        $message-&gt;to(new Address(
            $emailData[&#039;to&#039;],
            $emailData[&#039;to_name&#039;] ?? &#039;&#039;
        ));

        // Process attachments if any.
        if (!empty($emailData[&#039;attachments&#039;]) &amp;&amp; is_array($emailData[&#039;attachments&#039;])) {
            foreach ($emailData[&#039;attachments&#039;] as $attachment) {
                if (isset($attachment[&#039;content&#039;], $attachment[&#039;name&#039;], $attachment[&#039;type&#039;])) {
                    $content = base64_decode($attachment[&#039;content&#039;]);
                    $message-&gt;attach(
                        $content,
                        $attachment[&#039;name&#039;],
                        $attachment[&#039;type&#039;]
                    );
                }
            }
        }

        return $message;
    }

    /**
     * Acknowledges receipt to the webhook source if needed.
     *
     * @param array $webhookData
     * @return void
     */
    private function acknowledgeReceipt(array $webhookData): void
    {
        // Some webhook providers require an acknowledgement.
        // Implement if needed for your specific case.
    }
}
```

2. Define your options schema to specify the required configuration.

3. Implement the `receive()` method to handle the entire receiving process.

4. Add helper methods for your specific implementation needs.

### Key Considerations

When implementing from scratch:

- **Error Handling**: Properly catch and handle all exceptions.
- **Data Transformation**: Carefully map external data to Derafu Mail models.
- **Security**: Validate webhook signatures or implement other security measures.
- **Consistency**: Ensure your implementation behaves consistently with other strategies.
- **Testing**: Create comprehensive tests for your implementation.

## Usage

Once you&#039;ve created your custom strategy, you can use it by specifying its name in the Postman configuration:

```php
$postman = new Postman([
    &#039;strategy&#039; =&gt; &#039;gmail-api&#039;, // Or &#039;webhook-receiver&#039;
    &#039;transport&#039; =&gt; [
        // Strategy-specific configuration options.
        &#039;client_id&#039; =&gt; &#039;your-client-id&#039;,
        &#039;client_secret&#039; =&gt; &#039;your-client-secret&#039;,
        &#039;refresh_token&#039; =&gt; &#039;your-refresh-token&#039;,
        &#039;user_email&#039; =&gt; &#039;user@example.com&#039;,
        // Other options...
    ],
]);

$receiverWorker = $exchangeComponent-&gt;getReceiverWorker();
$envelopes = $receiverWorker-&gt;receive($postman);
```

## Best Practices

1. **Error Handling**: Always catch and handle exceptions appropriately.
2. **Logging**: Add detailed logging to help troubleshoot issues.
3. **Configuration Validation**: Use the options schema to validate configuration.
4. **Rate Limiting**: Consider rate limits for API-based strategies.
5. **Pagination**: Implement proper pagination for retrieving large volumes of emails.
6. **Performance**: Be mindful of memory usage when dealing with attachments.
7. **Documentation**: Document your strategy&#039;s specific requirements and limitations.

## Conclusion

Creating custom receiver strategies allows you to extend Derafu Mail to work with any email source or system. Whether you extend the abstract class or implement the interface directly depends on your specific needs and how much you want to leverage the existing infrastructure.

For sources that follow a mailbox-like model, extending `AbstractMailboxStrategy` is recommended. For completely different mechanisms like webhooks, implementing `ReceiverStrategyInterface` directly gives you maximum flexibility.





---
Last updated on 21/08/2026
#php
