VOOZH about

URL: https://deepwiki.com/friendsofhyperf/components/8.2-mail-component

⇱ Mail Component | friendsofhyperf/components | DeepWiki


Loading...
Last indexed: 14 February 2026 (15d5ca)
Menu

Mail Component

The mail component provides Laravel-style email functionality for Hyperf, including support for multiple transport drivers, Mailable classes with fluent API, and Markdown template support. The component wraps Symfony Mailer and integrates with Hyperf's view engine and dependency injection system.

Scope:

  • MailManager factory and mailer instances
  • Transport creation (SMTP, SES, Mailgun, Postmark, etc.)
  • Mailable classes and message building
  • Markdown template support
  • Configuration structure
  • Integration with the notification system

Out of Scope:

  • Notification system architecture (see Notification System)
  • View engine internals
  • Symfony Mailer internals
  • Queue integration for deferred sending

Architecture Overview

The mail component consists of four main layers:

  1. MailManager: Factory that creates and caches Mailer instances based on configuration
  2. Mailer: Handles message sending through a configured transport
  3. Mailable: Base class for defining mail messages with fluent API
  4. Transport Layer: Symfony Mailer transports for various delivery methods

Mail Component Architecture


Sources:


MailManager

The MailManager class is the central factory that creates and manages Mailer instances. It resolves mailer configurations and creates the appropriate Symfony Mailer transports.

Key Classes

Core Classes:

  • FriendsOfHyperf\Mail\MailManager - Main factory class implementing Factory contract
  • FriendsOfHyperf\Mail\Mailer - Mailer instance that sends messages
  • Symfony\Component\Mailer\Transport\TransportInterface - Transport abstraction
  • Hyperf\ViewEngine\Contract\FactoryInterface - View engine for rendering templates

Mailer Resolution Flow

The MailManager uses a resolution and caching pattern to manage mailer instances:

Mailer Resolution Sequence


Sources:

Transport Creation

The MailManager creates Symfony Mailer transports based on the transport key in the configuration. Transport creation is delegated to driver-specific factory methods:

Transport Factory Methods:

Transport TypeMethodTransport Class
smtpcreateSmtpTransport()EsmtpTransport
sescreateSesTransport()SesTransport
ses_v2createSesV2Transport()SesV2Transport
mailguncreateMailgunTransport()MailgunApiTransport
postmarkcreatePostmarkTransport()PostmarkApiTransport
sendmailcreateSendmailTransport()SendmailTransport
logcreateLogTransport()LogTransport
arraycreateArrayTransport()ArrayTransport
failovercreateFailoverTransport()FailoverTransport
roundrobincreateRoundRobinTransport()RoundRobinTransport

Transport Resolution:

Lines 95-111 in MailManager.php:
- Check for custom creator in $this->customCreators array
- Build method name: 'create' + ucfirst(Str::camel($transport)) + 'Transport'
- Call method dynamically with config array
- Throw InvalidArgumentException if transport not supported

Sources:

SMTP Transport with Scheme Requirement

The SMTP transport creation was updated in v3.1.69 to require a scheme configuration parameter. This is necessary for properly configuring the underlying Symfony EsmtpTransport:

SMTP Configuration Structure:

Lines 204-254 in MailManager.php:
- host: SMTP server hostname
- port: SMTP server port (default 25)
- scheme: Connection scheme (required) - 'smtp', 'smtps', or 'ssl'
- username: Authentication username (optional)
- password: Authentication password (optional)
- encryption: Legacy parameter, prefer 'scheme'
- local_domain: EHLO domain (optional)
- restart_threshold: Restart after N messages (optional)
- restart_threshold_sleep: Sleep duration on restart (optional)
- ping_threshold: Ping after N seconds idle (optional)

Scheme Handling:

Lines 234-245 in MailManager.php:
- If scheme is null, falls back to encryption config
- Maps 'tls' encryption to 'smtp' scheme
- Creates SocketStream with TLS options if scheme is 'smtps' or 'ssl'
- Passes stream to EsmtpTransport constructor

Sources:

AWS SES Transports

The mail component supports both AWS SES v1 and v2 client libraries:

SES v1 Transport:

Lines 256-266 in MailManager.php:
- Creates SesClient with credentials and region
- Wraps in SesTransport
- Supports options: key, secret, region

SES v2 Transport:

Lines 268-278 in MailManager.php:
- Creates SesV2Client with credentials and region
- Wraps in SesV2Transport
- Supports options: key, secret, region

Sources:

Third-Party Service Transports

Mailgun:

Lines 280-298 in MailManager.php:
- Uses MailgunTransportFactory from Symfony Mailer Bridge
- Requires domain and API key (secret)
- Optionally supports custom endpoint
- Creates DSN: mailgun+https://{domain}?key={secret}

Postmark:

Lines 300-313 in MailManager.php:
- Creates Postmark API transport
- Requires API token (id parameter)
- Uses Symfony HttpClient

Sources:


Mailable Classes

The Mailable class provides a fluent API for defining mail messages. Applications extend this class to create specific email types.

Mailable Structure and Lifecycle

Mailable Send Flow


Sources:

Recipient Methods

The Mailable class provides fluent methods for setting recipients:

Recipient Configuration:

MethodPropertyDescription
from($address, $name)$fromSet sender address
to($address, $name)$toAdd primary recipients
cc($address, $name)$ccAdd CC recipients
bcc($address, $name)$bccAdd BCC recipients
replyTo($address, $name)$replyToSet reply-to address

Address Normalization:

Lines 874-921 in Mailable.php:
- Accepts string, array, or collection
- Converts to [['address' => ..., 'name' => ...]] format
- Handles named vs indexed arrays
- Supports Address objects from Symfony
- Can accept Enums (uses enum value)

Sources:

Content Methods

The Mailable class supports multiple ways to specify email content:

Content Configuration:

MethodPropertyDescription
view($view, $data)$viewSet Blade template for HTML
text($view, $data)$textViewSet plain text template
html($html)$htmlSet raw HTML content
markdown($view, $data)$markdownSet Markdown template
subject($subject)$subjectSet email subject line
with($key, $value)$viewDataAdd view data

View Data Building:

Lines 600-654 in Mailable.php:
- Merge constructor params into viewData
- Extract public properties using ReflectionClass
- Skip properties that are DeferringDisplayableValue
- Execute static::$viewDataCallback if set
- Return combined array for rendering

Sources:

Attachment Methods

The Mailable class supports multiple attachment types:

Attachment Methods:

MethodStorageParameters
attach($file, $options)FilesystemFile path, name, mime type
attachFromStorage($path, $name, $options)Storage diskPath on disk, name, options
attachFromStorageDisk($disk, $path, $name, $options)Specific diskDisk name, path, name, options
attachData($data, $name, $options)MemoryRaw data, name, mime type
attachMany($files)MultipleArray of file paths

Attachment Structure:

Lines 451-512 in Mailable.php:
- Each attachment stored in $attachments, $rawAttachments, or $diskAttachments array
- File attachments: ['file' => path, 'options' => [...]]
- Raw attachments: ['data' => data, 'name' => name, 'options' => [...]]
- Disk attachments: ['disk' => disk, 'path' => path, 'options' => [...]]

Attachable Interface:

Lines 514-521 in Mailable.php:
- Supports Attachable contract objects
- Converts to Attachment via toMailAttachment()
- Allows custom attachment types

Sources:


Message Building

The Message class wraps Symfony's Email class and provides methods for building the actual email message.

Message Class

The Message class in src/mail/src/Message.php1-341 wraps Symfony\Component\Mime\Email and provides helper methods for building messages.

Key Methods:

MethodPurpose
from($address, $name)Set From address
to($address, $name)Add To recipient
cc($address, $name)Add CC recipient
bcc($address, $name)Add BCC recipient
replyTo($address, $name)Set Reply-To address
subject($subject)Set subject line
priority($level)Set priority (1-5)
html($html)Set HTML body
text($text)Set text body
attach($file, $options)Attach file
attachFromPath($path, $name, $contentType)Attach from filesystem
attachData($data, $name, $contentType)Attach from memory
embed($file)Embed file and get CID
embedData($data, $name, $contentType)Embed data and get CID

Address Conversion

Both Mailable and Message classes need to convert addresses to Symfony's Address objects:

Address Conversion Process:

Lines 923-954 in Mailable.php:
- String: new Address(string)
- Array with 'email' key: new Address(email, name ?? null)
- Array with 'address' key: new Address(address, name ?? null)
- Array with numeric keys [email, name]: new Address(email, name)
- Symfony Address object: passed through
- BackedEnum: uses ->value

Sources:

SpanContext Configuration

The SpanContext class provides a fluent API for configuring span attributes:

Key Methods:

MethodPurposeExample Value
setOp(string)Operation type'queue.publish'
setDescription(string)Human-readable description'Hyperf\Amqp\Producer::produceMessage()'
setOrigin(string)Instrumentation origin'auto.amqp' or 'auto.kafka'
setData(array)Span attributes/tagsMessaging metadata
setStartTimestamp(float)Start timemicrotime(true)
setStatus(SpanStatus)Final statusSpanStatus::ok()

Sources:


Integration with Async Queue

While AMQP and Kafka are external brokers, Hyperf's built-in async-queue component also receives similar instrumentation through AsyncQueueJobMessageAspect:

Comparison Table:

FeatureAMQPKafkaAsync Queue
BrokerRabbitMQ/AMQPKafka ClusterRedis
Producer AspectAmqpProducerAspectKafkaProducerAspectAsyncQueueJobMessageAspect
Interception PointsProducer::produceMessage()Producer::sendAsync(), sendBatchAsync()Driver::push(), JobMessage::__serialize()
Carrier InjectionAMQPTable headersRecordHeader headersJob message payload
System Identifiermessaging.system = 'amqp'messaging.system = 'kafka'messaging.system = 'async_queue'
Config Keysentry.tracing.amqpsentry.tracing.kafkasentry.tracing.async_queue

Async Queue Specifics:

Lines 140-152 in AsyncQueueJobMessageAspect.php:
- Injects carrier during JobMessage::__serialize()
- Carrier stored in serialized array
- Can be array list format or associative format
- Uses Constants::TRACE_CARRIER key

Sources:


Dispatch Helper Integration

Unified Dispatch API

The dispatch() helper function from friendsofhyperf/helpers provides a unified API for dispatching messages to various queue systems. This function is documented in detail in Dispatch API and Job Routing.

Pending Dispatch Classes:

The dispatch helper creates pending dispatch objects that defer execution until destruction:

ClassTarget SystemPackage
PendingAmqpProducerMessageDispatchAMQP/RabbitMQfriendsofhyperf/support
PendingKafkaProducerMessageDispatchKafkafriendsofhyperf/support
PendingAsyncQueueDispatchAsync Queuefriendsofhyperf/support

Usage Pattern:

// These create pending objects that execute on destruction
dispatch($amqpMessage); // AMQP
dispatch($kafkaMessage); // Kafka 
dispatch($job); // Async Queue

// With fluent configuration
dispatch($message)
 ->onPool('high-priority')
 ->delay(60);

Sources:

Package Dependencies

The integration requires several packages:

AMQP:


Kafka:


Sources:


Configuration Reference

Sentry Tracing Configuration

The aspects can be enabled/disabled through the Sentry configuration file:

Configuration Structure:

config/autoload/sentry.php:
- tracing.amqp.enabled: bool
- tracing.kafka.enabled: bool
- tracing.async_queue.enabled: bool

AOP Aspect Registration

The aspects are registered via Sentry's ConfigProvider:

Aspect Classes:

FriendsOfHyperf\Sentry\Tracing\Aspect\AmqpProducerAspect
FriendsOfHyperf\Sentry\Tracing\Aspect\KafkaProducerAspect
FriendsOfHyperf\Sentry\Tracing\Aspect\AsyncQueueJobMessageAspect

Interception Targets:

AmqpProducerAspect:
- Hyperf\Amqp\Producer::produceMessage

KafkaProducerAspect:
- Hyperf\Kafka\Producer::sendAsync
- Hyperf\Kafka\Producer::sendBatchAsync

AsyncQueueJobMessageAspect:
- Hyperf\AsyncQueue\Driver\DriverFactory::get
- Hyperf\AsyncQueue\Driver\*Driver::push
- Hyperf\AsyncQueue\JobMessage::__serialize
- Hyperf\AsyncQueue\JobMessage::__unserialize

Sources:


Summary

The AMQP and Kafka integration provides:

  1. Automatic Producer Instrumentation: AOP aspects intercept message publishing and create Sentry spans with semantic attributes
  2. Trace Context Propagation: The Carrier utility serializes trace context (trace ID, span ID, baggage) into message headers
  3. End-to-End Tracing: Consumers can extract carriers and continue traces, enabling distributed tracing across async boundaries
  4. Runtime Control: Feature flags allow enabling/disabling tracing per messaging system
  5. Consistent API: The trace() helper and SpanContext provide a uniform interface for span creation

Key Files:

Related Documentation: