Custom Wrappers
You can implement your own mail wrapper to integrate with any email service or custom mail handling logic.
Creating a Custom Wrapper
To create a custom wrapper, extend the BaseWrapper class and implement the required methods:
<?php
namespace MyApp\Mail;
use ByJG\Mail\Envelope;
use ByJG\Mail\SendResult;
use ByJG\Mail\Wrapper\BaseWrapper;
class MyCustomWrapper extends BaseWrapper
{
/**
* Define which URI schemes this wrapper handles
*/
public static function schema(): array
{
return ['myservice', 'mycustom'];
}
/**
* Implement the send logic
*/
public function send(Envelope $envelope): SendResult
{
// Access connection details from $this->uri
$apiKey = $this->uri->getUsername();
$apiSecret = $this->uri->getPassword();
$host = $this->uri->getHost();
// Validate the envelope
$this->validate($envelope);
// Your custom logic to send the email
$messageId = $this->sendViaMyService($envelope, $apiKey, $apiSecret, $host);
// Return the result
return new SendResult(true, $messageId);
}
private function sendViaMyService(Envelope $envelope, string $apiKey, string $apiSecret, string $host): string
{
// Implement your email sending logic here
// This might involve:
// - Making API calls
// - Formatting the email data
// - Handling attachments
// - Error handling
return 'message-id-123';
}
}
Implementing the Schema Method
The schema() method defines which URI schemes your wrapper handles. Return an array of scheme names:
public static function schema(): array
{
return ['myservice']; // Handles myservice://...
}
You can support multiple schemes:
public static function schema(): array
{
return ['myservice', 'myservice-ssl', 'myservice-tls'];
}
Implementing the Send Method
The send() method must:
- Accept an
Envelopeparameter - Return a
SendResultobject - Handle the actual email delivery
public function send(Envelope $envelope): SendResult
{
// Validate the envelope (optional but recommended)
$this->validate($envelope);
try {
// Your sending logic
$messageId = $this->performSend($envelope);
// Return success
return new SendResult(true, $messageId);
} catch (\Exception $e) {
// Handle errors - you might throw an exception or return failure
throw new \ByJG\Mail\Exception\MailApiException(
'Failed to send email: ' . $e->getMessage()
);
}
}
Accessing Connection Details
The URI is available via $this->uri:
// Get URI components
$scheme = $this->uri->getScheme(); // e.g., 'myservice'
$username = $this->uri->getUsername(); // e.g., 'api-key'
$password = $this->uri->getPassword(); // e.g., 'secret'
$host = $this->uri->getHost(); // e.g., 'api.myservice.com'
$port = $this->uri->getPort(); // e.g., 443
$query = $this->uri->getQuery(); // e.g., 'region=us'
// Get query parameters
$region = $this->uri->getQueryPart('region'); // Extract specific parameter
Custom Validation
You can add custom validation by overriding the validate() method:
public function validate(Envelope $envelope): void
{
// Call parent validation
parent::validate($envelope);
// Add your custom validation
if (empty($envelope->getSubject())) {
throw new \ByJG\Mail\Exception\InvalidMessageFormatException(
'Subject is required'
);
}
if (!$envelope->isHtml() && !empty($envelope->getAttachments())) {
throw new \ByJG\Mail\Exception\InvalidMessageFormatException(
'Attachments require HTML mode'
);
}
}
The base validate() method checks:
- At least one recipient exists
- From address is set
Handling Attachments
Process attachments from the envelope:
private function processAttachments(Envelope $envelope): array
{
$processed = [];
foreach ($envelope->getAttachments() as $name => $attachment) {
$filePath = $attachment['content'];
$mimeType = $attachment['content-type'];
$disposition = $attachment['disposition']; // 'attachment' or 'inline'
// Read file content
$content = file_get_contents($filePath);
$encoded = base64_encode($content);
$processed[] = [
'name' => $name,
'type' => $mimeType,
'content' => $encoded,
'disposition' => $disposition,
];
}
return $processed;
}
Registering Your Wrapper
Register your custom wrapper with the factory:
\ByJG\Mail\MailerFactory::registerMailer(\MyApp\Mail\MyCustomWrapper::class);
// Now you can use it
$mailer = \ByJG\Mail\MailerFactory::create('myservice://api-key:[email protected]');
Complete Example
Here's a complete example implementing a simple HTTP API wrapper:
<?php
namespace MyApp\Mail;
use ByJG\Mail\Envelope;
use ByJG\Mail\SendResult;
use ByJG\Mail\Wrapper\BaseWrapper;
use ByJG\Mail\Exception\MailApiException;
class HttpApiWrapper extends BaseWrapper
{
public static function schema(): array
{
return ['httpapi'];
}
public function send(Envelope $envelope): SendResult
{
$this->validate($envelope);
$apiUrl = sprintf(
'https://%s/send',
$this->uri->getHost()
);
$payload = [
'from' => $envelope->getFrom(),
'to' => $envelope->getTo(),
'subject' => $envelope->getSubject(),
'html' => $envelope->getBody(),
'api_key' => $this->uri->getUsername(),
];
if (!empty($envelope->getCC())) {
$payload['cc'] = $envelope->getCC();
}
if (!empty($envelope->getBCC())) {
$payload['bcc'] = $envelope->getBCC();
}
$ch = curl_init($apiUrl);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
throw new MailApiException(
sprintf('API returned error: %d - %s', $httpCode, $response)
);
}
$data = json_decode($response, true);
return new SendResult(true, $data['message_id'] ?? null);
}
}
Usage:
\ByJG\Mail\MailerFactory::registerMailer(\MyApp\Mail\HttpApiWrapper::class);
$mailer = \ByJG\Mail\MailerFactory::create('httpapi://[email protected]');
$mailer->send($envelope);
Best Practices
- Error Handling: Always handle errors gracefully and throw appropriate exceptions
- Validation: Call
parent::validate()before your custom validation - Return Values: Always return a
SendResultobject with appropriate success status - Message IDs: Include message IDs when available for tracking
- Testing: Create unit tests for your wrapper using the FakeSender as a reference
- Documentation: Document your wrapper's connection string format and requirements