Hive Mail is built from registries. Its own features use them, and so can your code. A registry is read the first time something needs it; at that moment Hive Mail fires the action hivemail_register_{name} with the registry object, and you call $registry->register( $id, $definition ). Reading the whole list applies the filter hivemail_{name} (see Hooks: actions and Hooks: filters).
Hooks can be added at file load, in a plugin or a must-use plugin. A class that implements a Hive Mail interface or extends a Hive Mail class must be declared after Hive Mail has loaded: declare it inside a hook callback or in a file you require from one. At the top of your file it is a fatal error (Interface "HiveMailSendingTransportInterface" not found) whenever your code loads before Hive Mail.
What you can use #
| Extension point | How | Definition |
|---|---|---|
| Delivery methods | Registry transports, action hivemail_register_transports |
HiveMail\Sending\TransportInterface: label(), send( array $email ): array. Appears in Settings > Sending > Delivery method |
| Flow triggers | Registry triggers, action hivemail_register_triggers |
HiveMail\Automation\Trigger, or data with Triggers\SimpleTrigger. Fire with Dispatcher::fire() |
| Flow steps | Registry nodes, action hivemail_register_nodes |
HiveMail\Automation\NodeType. The canvas is drawn from its describe() |
| Segment rule types | Registry segment_conditions, action hivemail_register_segment_conditions |
HiveMail\Audience\Segments\SegmentCondition: label(), group(), describe(), sanitize(), sql() |
| Plugin integrations | Registry integrations, action hivemail_register_integrations |
HiveMail\Automation\Integrations\Integration (adds triggers, rule types, merge tags when detect() is true) |
| Email blocks | Registry blocks, action hivemail_register_blocks |
HiveMail\Design\BlockType (usually extend Design\Block) |
| Bounce adapters | Registry bounce_providers, action hivemail_register_bounce_providers |
HiveMail\Compliance\BounceProvider. The id is the last part of /webhooks/{provider} |
| Delivery channels | Registry channels, action hivemail_register_channels |
HiveMail\Queue\ChannelInterface |
| Background jobs | Filter hivemail_job_handlers |
Job type => class implementing HiveMail\Jobs\JobHandler (type(), step(), download(), cleanup()) |
| REST routes | Filter hivemail_rest_controllers |
A class extending HiveMail\Api\BaseController with a no-argument constructor and register_routes(); use $this->route() and $this->can( $capability ) |
| Settings | Filter hivemail_settings_schema |
Add a group or fields; the form is generated |
| Admin menu | Filter hivemail_admin_nav |
Items group, path, label, icon, desc, cap |
| Admin screens | Action hivemail_admin_scripts, then window.hivemail.registerPage( path, Component, { title } ) or registerSettingsPanel( key, Component ) |
A React component in your own script |
| Recipes, starter designs, import sources | Filters hivemail_recipes, hivemail_template_starters, hivemail_migration_sources |
Arrays described in the filters page |
| Capabilities | Filter hivemail_capabilities |
Name more capabilities |
| Visitor data (read) | HiveMail\Intelligence\Reader |
visitor(), visitors_of_contact(), sessions(), hits(), contact_ids_sql(). Validated arguments, nothing when tracking is off |
| Visitor events | Actions hivemail_session_started, hivemail_hit_recorded, hivemail_intel_goal_reached |
See Hooks: actions |
The registries actions, conditions and abilities exist in code but nothing reads them. Do not register into them.
What is supported and what is internal #
Supported: the REST routes and their JSON, the action and filter hooks, the registries and interfaces above, Dispatcher::fire(), Reader, Settings::get(), the shortcodes and the hm_ tables for reading. Internal, and free to change in any release: other classes and methods, the React components and CSS, option names, the exact SQL, stored JSON formats (flow graphs, designs) except through the API, and the AI assistant's code. For writes use the API or a service call such as ContactService::create(), never SQL (Database tables).
Example 1: a delivery method #
This sends every email through your own HTTP service. The email array has to, subject, html, text, from_name, from_email, reply_to and headers. An empty html means a plain-text email. Return sent, retry (Hive Mail tries again later) or failed.
<?php
// my-gateway.php
add_action( 'hivemail_register_transports', function ( $registry ) {
require_once __DIR__ . '/class-my-gateway-transport.php';
$registry->register( 'my_gateway', new My_Gateway_Transport() );
} );
<?php
// class-my-gateway-transport.php
final class My_Gateway_Transport implements HiveMailSendingTransportInterface {
public function label(): string {
return 'My gateway';
}
public function send( array $email ): array {
$response = wp_remote_post( 'https://mail.example.com/send', array(
'timeout' => 20,
'headers' => array( 'Authorization' => 'Bearer ' . MY_GATEWAY_KEY ),
'body' => wp_json_encode( $email ),
) );
if ( is_wp_error( $response ) ) {
return array( 'status' => 'retry', 'error' => $response->get_error_message() );
}
$code = wp_remote_retrieve_response_code( $response );
if ( $code >= 200 && $code < 300 ) {
return array( 'status' => 'sent' );
}
return array( 'status' => $code >= 500 || 429 === $code ? 'retry' : 'failed', 'error' => "HTTP $code" );
}
}
Then choose My gateway in Settings > Sending > Delivery method (it is listed with WordPress mail, Mailgun and SendGrid). Never put a key in an error string; Hive Mail logs and shows it.
Example 2: a flow trigger #
This adds the trigger A coupon is used, under the group "Coupons" (the group name is capitalised), to the flow editor and starts the flows that use it. SimpleTrigger takes the key, label, group, settings fields, hooks it listens to (none here: we fire it ourselves) and which settings must agree with the event's context (an empty setting means any).
<?php
add_action( 'hivemail_register_triggers', function ( $registry ) {
$registry->register( 'coupon_used', new \HiveMail\Automation\Triggers\SimpleTrigger(
'coupon_used', 'A coupon is used', 'coupons',
array( array( 'key' => 'code', 'kind' => 'text', 'label' => 'Coupon code (empty = any)' ) ),
array(),
array( 'code' => 'code' )
) );
} );
// Wherever your code knows a coupon was used by an email address:
function my_site_coupon_used( string $email, string $code, int $order_id ): void {
$contact = \HiveMail\Audience\Contacts\ContactRepository::find_by_email( $email );
if ( $contact ) {
// Returns how many flows the contact was enrolled in.
\HiveMail\Automation\Dispatcher::fire( 'coupon_used', (int) $contact['id'], array( 'code' => $code ), 'order:' . $order_id );
}
}
The last argument is the thing the event is about; a flow set to re-enter "once per thing" enters once per order. fire() does nothing for a contact id below 1, and a person who is not a contact gets no flow. Here a flow set to code SPRING answers 1 for SPRING, and 0 for OTHER or a repeat of the same order. The context (code) is available to the flow's steps.
Example 3: a settings tab #
This adds a Support tab under Settings with one checkbox and one text field. The form is generated from the schema, and values are validated by type.
<?php
add_filter( 'hivemail_settings_schema', function ( array $schema ): array {
$schema['support'] = array(
'label' => 'Support',
'description' => 'Options for my support tools.',
'fields' => array(
'notify' => array( 'type' => 'bool', 'default' => true, 'label' => 'Tell support when someone unsubscribes' ),
'address' => array( 'type' => 'email', 'default' => '', 'label' => 'Support address', 'show_if' => array( 'notify' => true ) ),
),
);
return $schema;
} );
add_filter( 'hivemail_admin_nav', function ( $items ) {
$items[] = array(
'group' => 'settings',
'path' => '/settings/support',
'label' => 'Support',
'icon' => 'settings',
'desc' => 'Options for my support tools',
'cap' => 'hivemail_manage_settings',
);
return $items;
} );
// Read it anywhere:
$address = \HiveMail\Core\Settings::get( 'support', 'address', '' );
The tab appears in the Settings menu as Support and the generated form saves with Save changes. Field types are string, email, text, bool, int, enum (with options), color, secret, multi and lines. A secret is never sent back to the browser. Save from code or from the API with PUT /settings/support (REST API); each save fires hivemail_settings_updated.
Warning: Hooks, interfaces and registries are the contract. If you call an internal class and it changes, your code breaks. When in doubt use the REST API from PHP with
rest_do_request().
Next: Hooks: actions, Hooks: filters, Integrations overview.