admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'woocow_admin' ),
] );
}
// ── Page: Dashboard ──────────────────────────────────────────────────────
public function page_dashboard(): void {
global $wpdb;
$servers = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}woocow_servers WHERE active=1" );
$assignments = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}woocow_assignments" );
$customers = (int) $wpdb->get_var( "SELECT COUNT(DISTINCT customer_id) FROM {$wpdb->prefix}woocow_assignments" );
?>
WooCow – Servers
Add your Mailcow server instances here. The API key must be a read-write key from Configuration → Access → Edit administrator details → API .
+ Add Server
WooCow – Domain Assignments
Assign one or more Mailcow domains to a WooCommerce customer. The customer can then manage mailboxes for those domains from My Account → Email Hosting .
Current Assignments
Loading…
get_results( "SELECT id, name FROM {$wpdb->prefix}woocow_servers WHERE active=1 ORDER BY name" );
?>
WooCow – Mailboxes
— Select server —
name ); ?>
— Select domain —
Load Mailboxes
+ Create Mailbox
Create Mailbox
Create
Cancel
get_results( "SELECT id, name, url FROM {$wpdb->prefix}woocow_servers WHERE active=1 ORDER BY name" );
?>
WooCow – Domains
Add and manage domains on your Mailcow servers. After adding a domain, DNS records (including DKIM) are generated automatically.
— Select server —
name ); ?>
Load Domains
+ Add Domain
Edit Domain:
Save Changes
Cancel
DNS Records
Close
Add these records to your DNS provider for .
get_results( "SELECT id, name FROM {$wpdb->prefix}woocow_servers WHERE active=1 ORDER BY name" );
?>
WooCow – Sender-Dependent Transports
Configure relay hosts for outbound mail delivery. Each transport can be associated with one or more domains in Mailcow.
— Select server —
name ); ?>
Load Transports
+ Add Transport
get_results( "SELECT id, name FROM {$wpdb->prefix}woocow_servers WHERE active=1 ORDER BY name" );
$log_types = [
'postfix' => 'Postfix',
'dovecot' => 'Dovecot',
'rspamd-history' => 'Rspamd',
'ratelimited' => 'Rate Limit',
'api' => 'API',
'acme' => 'ACME',
'autodiscover' => 'Autodiscover',
'sogo' => 'SOGo',
'netfilter' => 'Netfilter',
'watchdog' => 'Watchdog',
];
?>
WooCow – Server Logs
— Select server —
name ); ?>
$label ) : ?>
Load Logs
Select a server and log type above.
get_results( "SELECT id, name FROM {$wpdb->prefix}woocow_servers WHERE active=1 ORDER BY name" );
?>
WooCow – Quarantine
View and manage quarantined messages across all servers. You can permanently delete messages or blacklist senders by domain.
— Select server —
name ); ?>
Load Quarantine
Select a server above to view quarantined messages.
get_row( $wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}woocow_servers WHERE id = %d",
$id
) );
}
private function json_ok( $data = null ): void {
wp_send_json_success( $data );
}
private function json_err( string $msg ): void {
wp_send_json_error( $msg );
}
// ── AJAX: Servers ─────────────────────────────────────────────────────────
public function ajax_woocow_servers_list(): void {
$this->verify();
global $wpdb;
$rows = $wpdb->get_results( "SELECT id, name, url, active, created_at FROM {$wpdb->prefix}woocow_servers ORDER BY name" );
$this->json_ok( $rows );
}
public function ajax_woocow_server_save(): void {
$this->verify();
global $wpdb;
$id = absint( $_POST['id'] ?? 0 );
$name = sanitize_text_field( $_POST['name'] ?? '' );
$url = esc_url_raw( $_POST['url'] ?? '' );
$key = sanitize_text_field( $_POST['api_key'] ?? '' );
$active = absint( $_POST['active'] ?? 1 );
if ( ! $name || ! $url || ! $key ) {
$this->json_err( 'Name, URL, and API key are required.' );
}
$data = compact( 'name', 'url', 'active' ) + [ 'api_key' => $key ];
if ( $id ) {
$wpdb->update( "{$wpdb->prefix}woocow_servers", $data, [ 'id' => $id ] );
$this->json_ok( [ 'id' => $id ] );
} else {
$wpdb->insert( "{$wpdb->prefix}woocow_servers", $data );
$this->json_ok( [ 'id' => $wpdb->insert_id ] );
}
}
public function ajax_woocow_server_delete(): void {
$this->verify();
global $wpdb;
$id = absint( $_POST['id'] ?? 0 );
$wpdb->delete( "{$wpdb->prefix}woocow_servers", [ 'id' => $id ] );
$wpdb->delete( "{$wpdb->prefix}woocow_assignments", [ 'server_id' => $id ] );
$this->json_ok();
}
public function ajax_woocow_server_test(): void {
$this->verify();
$id = absint( $_POST['id'] ?? 0 );
// Allow testing before saving (pass url+key directly)
if ( $id ) {
$server = $this->get_server( $id );
if ( ! $server ) {
$this->json_err( 'Server not found.' );
}
$api = WooCow_API::from_server( $server );
} else {
$url = esc_url_raw( $_POST['url'] ?? '' );
$key = sanitize_text_field( $_POST['api_key'] ?? '' );
if ( ! $url || ! $key ) {
$this->json_err( 'URL and API key required.' );
}
$api = new WooCow_API( $url, $key );
}
$result = $api->test_connection();
if ( $result['success'] ) {
$version = $result['data']['version'] ?? $result['data'][0]['version'] ?? 'unknown';
$this->json_ok( [ 'version' => $version ] );
} else {
$this->json_err( $result['error'] ?? 'Connection failed.' );
}
}
public function ajax_woocow_server_domains(): void {
$this->verify();
$id = absint( $_POST['server_id'] ?? 0 );
$server = $this->get_server( $id );
if ( ! $server ) {
$this->json_err( 'Server not found.' );
}
$api = WooCow_API::from_server( $server );
$result = $api->get_domains();
if ( ! $result['success'] ) {
$this->json_err( $result['error'] ?? 'Failed to fetch domains.' );
}
$domains = array_map( function ( $d ) {
$rl = is_array( $d['rl'] ?? false ) ? $d['rl'] : [];
return [
'domain' => $d['domain_name'],
'active' => $d['active'],
'description' => $d['description'] ?? '',
'mailboxes' => $d['max_num_mboxes_for_domain'] ?? 0,
'mboxes_in' => $d['mboxes_in_domain'] ?? 0,
'aliases' => $d['max_num_aliases_for_domain'] ?? 0,
'aliases_in' => $d['aliases_in_domain'] ?? 0,
'quota' => (int) round( ( $d['max_quota_for_domain'] ?? 0 ) / 1024 / 1024 ),
'defquota' => (int) round( ( $d['def_new_mailbox_quota'] ?? 0 ) / 1024 / 1024 ),
'quota_used' => (int) round( ( $d['quota_used_in_domain'] ?? 0 ) / 1024 / 1024 ),
'relayhost' => $d['relayhost'] ?? '0',
'rl_value' => $rl['value'] ?? 0,
'rl_frame' => $rl['frame'] ?? 's',
'gal' => $d['gal'] ?? '0',
'backupmx' => $d['backupmx'] ?? '0',
];
}, (array) $result['data'] );
$this->json_ok( $domains );
}
// ── AJAX: Assignments ─────────────────────────────────────────────────────
public function ajax_woocow_assignments_list(): void {
$this->verify();
global $wpdb;
$rows = $wpdb->get_results( "
SELECT a.id, a.customer_id, a.domain, a.created_at,
s.name AS server_name, s.url AS server_url,
u.display_name, u.user_email
FROM {$wpdb->prefix}woocow_assignments a
JOIN {$wpdb->prefix}woocow_servers s ON s.id = a.server_id
JOIN {$wpdb->users} u ON u.ID = a.customer_id
ORDER BY u.display_name, a.domain
" );
$this->json_ok( $rows );
}
public function ajax_woocow_assignment_save(): void {
$this->verify();
global $wpdb;
$customer_id = absint( $_POST['customer_id'] ?? 0 );
$server_id = absint( $_POST['server_id'] ?? 0 );
$domain = sanitize_text_field( $_POST['domain'] ?? '' );
if ( ! $customer_id || ! $server_id || ! $domain ) {
$this->json_err( 'Customer, server, and domain are all required.' );
}
$existing = $wpdb->get_var( $wpdb->prepare(
"SELECT id FROM {$wpdb->prefix}woocow_assignments WHERE customer_id=%d AND domain=%s",
$customer_id, $domain
) );
if ( $existing ) {
$this->json_err( 'This domain is already assigned to this customer.' );
}
$wpdb->insert( "{$wpdb->prefix}woocow_assignments", compact( 'customer_id', 'server_id', 'domain' ) );
$this->json_ok( [ 'id' => $wpdb->insert_id ] );
}
public function ajax_woocow_assignment_delete(): void {
$this->verify();
global $wpdb;
$id = absint( $_POST['id'] ?? 0 );
$wpdb->delete( "{$wpdb->prefix}woocow_assignments", [ 'id' => $id ] );
$this->json_ok();
}
// ── AJAX: Customer search ─────────────────────────────────────────────────
public function ajax_woocow_customers_search(): void {
$this->verify();
$term = sanitize_text_field( $_POST['term'] ?? '' );
if ( strlen( $term ) < 2 ) {
$this->json_ok( [] );
}
$users = get_users( [
'search' => '*' . $term . '*',
'search_columns' => [ 'user_login', 'user_email', 'display_name' ],
'role__in' => [ 'customer', 'subscriber', 'administrator', 'shop_manager' ],
'number' => 15,
] );
$out = array_map( fn( $u ) => [
'id' => $u->ID,
'label' => sprintf( '%s (%s)', $u->display_name, $u->user_email ),
], $users );
$this->json_ok( $out );
}
// ── AJAX: Admin Mailboxes ─────────────────────────────────────────────────
public function ajax_woocow_admin_mailboxes(): void {
$this->verify();
$server_id = absint( $_POST['server_id'] ?? 0 );
$domain = sanitize_text_field( $_POST['domain'] ?? '' );
$server = $this->get_server( $server_id );
if ( ! $server ) {
$this->json_err( 'Server not found.' );
}
$api = WooCow_API::from_server( $server );
$result = $domain
? $api->get_domain_mailboxes( $domain )
: $api->get_all_mailboxes();
if ( ! $result['success'] ) {
$this->json_err( $result['error'] ?? 'Failed to fetch mailboxes.' );
}
$this->json_ok( [
'mailboxes' => $result['data'] ?? [],
'webmail_url' => $api->get_webmail_url(),
] );
}
public function ajax_woocow_admin_mailbox_create(): void {
$this->verify();
$server_id = absint( $_POST['server_id'] ?? 0 );
$domain = sanitize_text_field( $_POST['domain'] ?? '' );
$local_part = sanitize_text_field( $_POST['local_part'] ?? '' );
$name = sanitize_text_field( $_POST['name'] ?? '' );
$password = $_POST['password'] ?? '';
$password2 = $_POST['password2'] ?? '';
$quota = absint( $_POST['quota'] ?? 1024 );
if ( ! $domain || ! $local_part || ! $password ) {
$this->json_err( 'Domain, local part, and password are required.' );
}
if ( $password !== $password2 ) {
$this->json_err( 'Passwords do not match.' );
}
$server = $this->get_server( $server_id );
if ( ! $server ) {
$this->json_err( 'Server not found.' );
}
$api = WooCow_API::from_server( $server );
$result = $api->create_mailbox( [
'local_part' => $local_part,
'domain' => $domain,
'name' => $name ?: $local_part,
'password' => $password,
'password2' => $password2,
'quota' => $quota,
'active' => 1,
'force_pw_update' => 0,
'tls_enforce_in' => 0,
'tls_enforce_out' => 0,
] );
if ( ! $result['success'] ) {
$this->json_err( $result['error'] ?? 'Failed to create mailbox.' );
}
$this->json_ok( [ 'email' => $local_part . '@' . $domain ] );
}
public function ajax_woocow_admin_mailbox_delete(): void {
$this->verify();
$server_id = absint( $_POST['server_id'] ?? 0 );
$email = sanitize_email( $_POST['email'] ?? '' );
$server = $this->get_server( $server_id );
if ( ! $server ) {
$this->json_err( 'Server not found.' );
}
$api = WooCow_API::from_server( $server );
$result = $api->delete_mailbox( $email );
if ( ! $result['success'] ) {
$this->json_err( $result['error'] ?? 'Failed to delete mailbox.' );
}
$this->json_ok();
}
public function ajax_woocow_admin_mailbox_edit(): void {
$this->verify();
$server_id = absint( $_POST['server_id'] ?? 0 );
$email = sanitize_email( $_POST['email'] ?? '' );
$type = sanitize_text_field( $_POST['type'] ?? '' ); // 'password' or 'quota'
if ( ! $email ) {
$this->json_err( 'Email address is required.' );
}
$server = $this->get_server( $server_id );
if ( ! $server ) {
$this->json_err( 'Server not found.' );
}
$api = WooCow_API::from_server( $server );
$attr = [];
if ( $type === 'password' ) {
$pass = $_POST['password'] ?? '';
$pass2 = $_POST['password2'] ?? '';
if ( ! $pass ) {
$this->json_err( 'Password cannot be empty.' );
}
if ( $pass !== $pass2 ) {
$this->json_err( 'Passwords do not match.' );
}
$attr = [ 'password' => $pass, 'password2' => $pass2 ];
} elseif ( $type === 'quota' ) {
$quota = absint( $_POST['quota'] ?? 0 );
if ( $quota < 1 ) {
$this->json_err( 'Quota must be at least 1 MB.' );
}
$attr = [ 'quota' => $quota ];
} else {
$this->json_err( 'Unknown edit type.' );
}
$result = $api->edit_mailbox( [ $email ], $attr );
if ( ! $result['success'] ) {
$this->json_err( $result['error'] ?? 'Failed to update mailbox.' );
}
$this->json_ok();
}
// ── AJAX: Domains ─────────────────────────────────────────────────────────
public function ajax_woocow_admin_domain_add(): void {
$this->verify();
$server_id = absint( $_POST['server_id'] ?? 0 );
$domain = sanitize_text_field( $_POST['domain'] ?? '' );
$desc = sanitize_text_field( $_POST['description'] ?? '' );
$mailboxes = absint( $_POST['mailboxes'] ?? 10 );
$aliases = absint( $_POST['aliases'] ?? 400 );
$quota = absint( $_POST['quota'] ?? 10240 );
$defquota = absint( $_POST['defquota'] ?? 3072 );
$dkim_size = absint( $_POST['dkim_size'] ?? 2048 );
if ( ! $domain ) {
$this->json_err( 'Domain name is required.' );
}
$server = $this->get_server( $server_id );
if ( ! $server ) {
$this->json_err( 'Server not found.' );
}
$api = WooCow_API::from_server( $server );
// Add domain
$result = $api->create_domain( [
'domain' => $domain,
'description' => $desc,
'mailboxes' => $mailboxes,
'aliases' => $aliases,
'quota' => $quota,
'defquota' => $defquota,
'maxquota' => $quota,
'active' => 1,
'restart_sogo'=> 1,
] );
if ( ! $result['success'] ) {
$this->json_err( $result['error'] ?? 'Failed to add domain.' );
}
// Auto-generate DKIM
$api->generate_dkim( $domain, 'dkim', $dkim_size );
$this->json_ok( [ 'domain' => $domain ] );
}
public function ajax_woocow_admin_domain_dns(): void {
$this->verify();
$server_id = absint( $_POST['server_id'] ?? 0 );
$domain = sanitize_text_field( $_POST['domain'] ?? '' );
$server = $this->get_server( $server_id );
if ( ! $server ) {
$this->json_err( 'Server not found.' );
}
$api = WooCow_API::from_server( $server );
$mail_host = parse_url( $server->url, PHP_URL_HOST );
// Fetch DKIM
$dkim_result = $api->get_dkim( $domain );
$dkim_txt = '';
$dkim_sel = 'dkim';
if ( $dkim_result['success'] && ! empty( $dkim_result['data']['dkim_txt'] ) ) {
$dkim_txt = $dkim_result['data']['dkim_txt'];
$dkim_sel = $dkim_result['data']['dkim_selector'] ?? 'dkim';
}
$this->json_ok( [
'domain' => $domain,
'mail_host' => $mail_host,
'dkim_sel' => $dkim_sel,
'dkim_txt' => $dkim_txt,
'records' => [
[ 'type' => 'MX', 'host' => $domain, 'value' => $mail_host . '.', 'prio' => '10', 'ttl' => '3600' ],
[ 'type' => 'TXT', 'host' => $domain, 'value' => 'v=spf1 mx ~all', 'prio' => '', 'ttl' => '3600', 'note' => 'SPF' ],
[ 'type' => 'TXT', 'host' => '_dmarc.' . $domain, 'value' => 'v=DMARC1; p=quarantine; rua=mailto:postmaster@' . $domain, 'prio' => '', 'ttl' => '3600', 'note' => 'DMARC' ],
[ 'type' => 'TXT', 'host' => $dkim_sel . '._domainkey.' . $domain, 'value' => $dkim_txt ?: '(generate DKIM first)', 'prio' => '', 'ttl' => '3600', 'note' => 'DKIM' ],
[ 'type' => 'CNAME','host' => 'autoconfig.' . $domain, 'value' => $mail_host . '.', 'prio' => '', 'ttl' => '3600', 'note' => 'Autoconfig' ],
[ 'type' => 'CNAME','host' => 'autodiscover.' . $domain, 'value' => $mail_host . '.', 'prio' => '', 'ttl' => '3600', 'note' => 'Autodiscover' ],
],
] );
}
public function ajax_woocow_admin_domain_edit(): void {
$this->verify();
$server_id = absint( $_POST['server_id'] ?? 0 );
$domain = sanitize_text_field( $_POST['domain'] ?? '' );
if ( ! $domain ) {
$this->json_err( 'Domain name is required.' );
}
$server = $this->get_server( $server_id );
if ( ! $server ) {
$this->json_err( 'Server not found.' );
}
$attr = [];
if ( isset( $_POST['description'] ) ) {
$attr['description'] = sanitize_text_field( $_POST['description'] );
}
if ( isset( $_POST['mailboxes'] ) ) {
$attr['mailboxes'] = absint( $_POST['mailboxes'] );
}
if ( isset( $_POST['aliases'] ) ) {
$attr['aliases'] = absint( $_POST['aliases'] );
}
if ( isset( $_POST['quota'] ) ) {
$attr['quota'] = absint( $_POST['quota'] );
$attr['maxquota'] = absint( $_POST['quota'] );
}
if ( isset( $_POST['defquota'] ) ) {
$attr['defquota'] = absint( $_POST['defquota'] );
}
if ( isset( $_POST['rl_value'] ) ) {
$attr['rl_value'] = absint( $_POST['rl_value'] );
}
if ( isset( $_POST['rl_frame'] ) ) {
$frame = sanitize_text_field( $_POST['rl_frame'] );
if ( in_array( $frame, [ 's', 'm', 'h', 'd' ], true ) ) {
$attr['rl_frame'] = $frame;
}
}
if ( isset( $_POST['relayhost'] ) ) {
$attr['relayhost'] = absint( $_POST['relayhost'] );
}
if ( isset( $_POST['active'] ) ) {
$attr['active'] = absint( $_POST['active'] );
}
$api = WooCow_API::from_server( $server );
$result = $api->edit_domain( [ $domain ], $attr );
if ( ! $result['success'] ) {
$this->json_err( $result['error'] ?? 'Failed to update domain.' );
}
$this->json_ok();
}
public function ajax_woocow_admin_domain_delete(): void {
$this->verify();
$server_id = absint( $_POST['server_id'] ?? 0 );
$domain = sanitize_text_field( $_POST['domain'] ?? '' );
if ( ! $domain ) {
$this->json_err( 'Domain name is required.' );
}
$server = $this->get_server( $server_id );
if ( ! $server ) {
$this->json_err( 'Server not found.' );
}
$api = WooCow_API::from_server( $server );
$result = $api->delete_domain( $domain );
if ( ! $result['success'] ) {
$this->json_err( $result['error'] ?? 'Failed to delete domain.' );
}
$this->json_ok();
}
// ── AJAX: Transports ──────────────────────────────────────────────────────
public function ajax_woocow_admin_relayhosts_list(): void {
$this->verify();
$server_id = absint( $_POST['server_id'] ?? 0 );
$server = $this->get_server( $server_id );
if ( ! $server ) {
$this->json_err( 'Server not found.' );
}
$api = WooCow_API::from_server( $server );
$result = $api->get_relayhosts();
if ( ! $result['success'] ) {
$this->json_err( $result['error'] ?? 'Failed to fetch transports.' );
}
$this->json_ok( $result['data'] ?? [] );
}
public function ajax_woocow_admin_relayhost_save(): void {
$this->verify();
$server_id = absint( $_POST['server_id'] ?? 0 );
$hostname = sanitize_text_field( $_POST['hostname'] ?? '' );
$username = sanitize_text_field( $_POST['username'] ?? '' );
$password = $_POST['password'] ?? '';
$active = absint( $_POST['active'] ?? 1 );
if ( ! $hostname || ! $username ) {
$this->json_err( 'Hostname and username are required.' );
}
$server = $this->get_server( $server_id );
if ( ! $server ) {
$this->json_err( 'Server not found.' );
}
$api = WooCow_API::from_server( $server );
$result = $api->create_relayhost( compact( 'hostname', 'username', 'password', 'active' ) );
if ( ! $result['success'] ) {
$this->json_err( $result['error'] ?? 'Failed to add transport.' );
}
$this->json_ok();
}
public function ajax_woocow_admin_relayhost_delete(): void {
$this->verify();
$server_id = absint( $_POST['server_id'] ?? 0 );
$id = absint( $_POST['id'] ?? 0 );
$server = $this->get_server( $server_id );
if ( ! $server ) {
$this->json_err( 'Server not found.' );
}
$api = WooCow_API::from_server( $server );
$result = $api->delete_relayhost( $id );
if ( ! $result['success'] ) {
$this->json_err( $result['error'] ?? 'Failed to delete transport.' );
}
$this->json_ok();
}
// ── AJAX: Logs ────────────────────────────────────────────────────────────
public function ajax_woocow_admin_logs(): void {
$this->verify();
$server_id = absint( $_POST['server_id'] ?? 0 );
$log_type = sanitize_key( $_POST['log_type'] ?? 'postfix' );
$allowed = [ 'postfix', 'dovecot', 'rspamd-history', 'ratelimited', 'api', 'acme', 'autodiscover', 'sogo', 'netfilter', 'watchdog' ];
if ( ! in_array( $log_type, $allowed, true ) ) {
$this->json_err( 'Invalid log type.' );
}
$server = $this->get_server( $server_id );
if ( ! $server ) {
$this->json_err( 'Server not found.' );
}
$api = WooCow_API::from_server( $server );
$result = $api->request( 'GET', '/api/v1/get/logs/' . $log_type . '/100' );
if ( ! $result['success'] ) {
$this->json_err( $result['error'] ?? 'Failed to fetch logs.' );
}
$this->json_ok( $result['data'] ?? [] );
}
// ── AJAX: Admin Quarantine ─────────────────────────────────────────────────
public function ajax_woocow_admin_quarantine(): void {
$this->verify();
$server_id = absint( $_POST['server_id'] ?? 0 );
$server = $this->get_server( $server_id );
if ( ! $server ) {
$this->json_err( 'Server not found.' );
}
$api = WooCow_API::from_server( $server );
$result = $api->get_quarantine();
if ( ! $result['success'] ) {
$this->json_err( $result['error'] ?? 'Failed to fetch quarantine.' );
}
$this->json_ok( $result['data'] ?? [] );
}
public function ajax_woocow_admin_quarantine_delete(): void {
$this->verify();
$server_id = absint( $_POST['server_id'] ?? 0 );
$qid = absint( $_POST['qid'] ?? 0 );
if ( ! $qid ) {
$this->json_err( 'Message ID is required.' );
}
$server = $this->get_server( $server_id );
if ( ! $server ) {
$this->json_err( 'Server not found.' );
}
$api = WooCow_API::from_server( $server );
$result = $api->delete_quarantine( $qid );
if ( ! $result['success'] ) {
$this->json_err( $result['error'] ?? 'Failed to delete quarantine message.' );
}
$this->json_ok();
}
public function ajax_woocow_admin_quarantine_block(): void {
$this->verify();
$server_id = absint( $_POST['server_id'] ?? 0 );
$domain = sanitize_text_field( $_POST['domain'] ?? '' );
$object_from = sanitize_text_field( $_POST['object_from'] ?? '' );
if ( ! $domain || ! $object_from ) {
$this->json_err( 'Domain and sender address are required.' );
}
$server = $this->get_server( $server_id );
if ( ! $server ) {
$this->json_err( 'Server not found.' );
}
$api = WooCow_API::from_server( $server );
$result = $api->add_domain_policy( $domain, $object_from, 'bl' );
if ( ! $result['success'] ) {
$this->json_err( $result['error'] ?? 'Failed to blacklist sender.' );
}
$this->json_ok();
}
}