Send email (Corex Mail)
Corex Mail is the addons/corex-email add-on plus a neutral Corex\Mail\Mailer seam in
the core. Consumers (Forms, Newsletter, Careers…) send through the seam; if the add-on is
active it handles delivery, otherwise the framework falls back to wp_mail — no add-on is
a hard dependency.
Send a message
Section titled “Send a message”use Corex\Email\Mail;
Mail::to('user@example.com') ->template('contact-notification') ->with(['submission' => [ 'name' => $name, 'email' => $email, 'message' => $message, ]]) ->send();The builder also offers toRole(), toDynamic(), cc(), bcc(), replyTo(),
subject(), and body().
Delivery is best-effort and never throws — failures are logged, not fatal.
What the add-on provides
Section titled “What the add-on provides”- Templates —
TemplateRenderermerges{{ path }}placeholders into an escaped, brand-aware layout (pulled fromtheme.json/brand.json). - Security —
HeaderGuardrejects CR/LF/control characters in headers; recipients are resolved + validated. - Driver —
WpMailDriversends viawp_mailwith the configured from-identity. A message that carries aplainTextis sent with it as the plain-text alternative of the HTML. - Design —
Corex\Email\Designholds what a designed email is drawn from:EmailPalette(the colours, light and dark, the one place an email’s colours are written),EmailBrand(the site’s name, address, logo, direction and language), andStandardEmailDesign, which draws a message as a whole document. A reply from the Submissions inbox is the first email drawn this way; the others still go through the layout above. The inbox’s Preview asksPOST corex/v1/submissions/{id}/reply/previewfor that document and its plain text, built by the code that builds the sent one. It sends nothing and keeps nothing of the reply; a site’s email design that fails there is reported as it is when a reply is sent. - Log — each send is recorded to the
corex_email_logCPT. - Conversation — an email sent by Email Studio, or through
Corex\Mail\Mailer, leaves under aMessage-IDCoreX chose (Corex\Mail\MessageId), on a site whose address has a host name to make one of. A reply from the Submissions inbox can name the email it answers (In-Reply-To,References), which is what a mail program reads to show it under that one.
Replies that continue your own confirmation
Section titled “Replies that continue your own confirmation”The Submissions inbox offers to send a reply “in the conversation they already have” when CoreX has a record of an email it sent that person for that submission. A flow’s own confirmation is recorded by the flow. A site that sends its confirmation itself tells CoreX so, where it sends it:
use Corex\Config\Submissions\SubmissionConversation;use Corex\Mail\Mailer;use Corex\Mail\MailRequest;
final class SendLeadConfirmation{ public function __construct( private readonly Mailer $mailer, private readonly SubmissionConversation $conversation, ) { }
public function handle(int $submissionId, string $email): void { $request = new MailRequest( to: [$email], subject: 'We received your enquiry', body: '<p>Thank you. We will be in touch.</p>', ); $this->mailer->send($request);
// Replies to this submission can now be sent under this email. $this->conversation->began($submissionId, $request); }}The email leaves under an id made of the request’s own, whenever it leaves: at once, or later
from the mail queue. began() records that id, and not what became of the email, which
send() does not say. It records an email only where the submission’s sender is one of the
request’s recipients: called for the notice you send your own team, it records nothing.
What this cannot promise:
- Gmail keeps emails together by their subject as well as by what they answer. A reply whose subject was rewritten may stand alone there; the reply box says so when that is the case.
- A sending service that replaces an email’s
Message-IDwith its own breaks the link: the reply arrives, and stands alone. This is not yet checked for an SMTP plugin’s Gmail connection. - Through PHP’s own
mail(), WordPress’s mailer rewrites a header longer than 47 characters, so a reply names the one email it answers and not the ones before it; and on a site whose host name is longer than 34 characters it names none, and arrives as an email of its own though the inbox lists it as sent in the conversation. Where a plugin chooses a mail server on thephpmailer_inithook, a reply names up to the whole conversation, on any host. FluentSMTP chooses after that hook, for its SMTP and its Gmail connections alike, so on it a reply is written as it is formail(). - The id is made of the host of the address the request is handled on. A site with one domain per language, sending its confirmation later from the mail queue, may record one host and send under another: the reply then names an id that was never sent.
- Which emails a person was sent is recognised with the site’s own secret keys. After a move to a host with new keys, earlier emails are not recognised and the next reply is a new email.
- An email sent before CoreX chose ids (before the release that carries this) cannot be continued.
Inspect email state in wp-admin
Section titled “Inspect email state in wp-admin”Corex → Email Studio is available when the CoreX Email add-on is active. Its Overview, Templates, Layouts,
Partials, and Variables tabs inspect the real engine state. Registered-template detail provides Edit, Preview,
Plain text, Test send, Routing, and Delivery logs. Preview and Plain text use the real renderer; Layouts previews
the active brand wrapper; Variables lists only detectable {{ path }} placeholders from registered template
output; Delivery logs are real site-wide attempts and are labelled as not yet filterable by template.
The studio does not mutate code-defined templates or routing. Test Send stays disabled until the neutral Mailer
seam returns a per-send result that a capability + nonce-gated action can report truthfully.
The sender of a reply
Section titled “The sender of a reply”mail.reply.from_name, mail.reply.from_address and mail.reply.reply_to name who a reply from
the Submissions inbox is from and where an answer to it goes. Each falls back to its general
counterpart (mail.from.name, mail.from.address, mail.reply_to). They are set under
CoreX → Settings → Mail, or as any config value is. No other email reads them.
EmailMessage has a fromName for this. It is not for a caller to vary per message: a sender’s
name that changes with the message is how a spoof reads. A name is cleaned by one rule,
Corex\Email\Message\SenderName::of(): one line, <, > and " taken out, no =? sequence,
at most 100 characters. The gateway that sends a reply applies it and WpMailDriver applies it
again, to the general From name too. The mail service refuses a name with a line break in it. A
driver of your own is handed the reply’s name already cleaned, and has to clean a name it reads
from anywhere else.
A name with no address to stand in front of (none set for replies and none set for the site) is
given to WordPress’s mailer directly, where no plugin has chosen a name. A wp_mail_from or
wp_mail_from_name filter, which is how a transport plugin forces a sender, is applied by
WordPress after the header this driver writes and replaces it.
Configure
Section titled “Configure”CoreX has no SMTP driver and reads no mail host, port, username or password. It hands each
message to WordPress’s wp_mail(), and the site’s SMTP plugin or mail server holds those. What
CoreX reads is who a message is from and whether it may leave the site, set in CoreX → Settings →
Mail or in .env (.env wins):
| Config key | WordPress option | .env name |
|---|---|---|
mail.from.name | corex_mail_from_name | MAIL_FROM_NAME |
mail.from.address | corex_mail_from_address | MAIL_FROM_ADDRESS |
mail.reply_to | corex_mail_reply_to | MAIL_REPLY_TO |
mail.provider | corex_mail_provider | MAIL_PROVIDER |
mail.live_delivery | corex_mail_live_delivery | MAIL_LIVE_DELIVERY |
Until 2026-10-13 this page named COREX_MAIL_DRIVER, and .env.example named it with
COREX_MAIL_HOST, COREX_MAIL_PORT, COREX_MAIL_USERNAME and COREX_MAIL_PASSWORD. Nothing reads
them; a .env that has them can drop them.
The mail queue is gated behind the mail_queue
feature flag. With it on, a send is deferred so the request that
asked for it does not wait for the mail server: through Action Scheduler where that is
installed, and through WP-Cron where it is not. On WP-Cron the message leaves on the site’s next
cron run, so it suits the few messages a form sends; install Action Scheduler to send to a long
list.