use WPForms\Helpers\Templates;
use WPForms\Tasks\Actions\EntryEmailsTask;
* This class handles all (notification) emails sent by WPForms.
* Heavily influenced by the great AffiliateWP plugin by Pippin Williamson.
* https://github.com/AffiliateWP/AffiliateWP/blob/master/includes/emails/class-affwp-emails.php
* Note that this mailer class is no longer in active use and has been replaced with the "WPForms\Emails\Notifications" class.
* Please refer to the new mailer wrapper extension to extend or add further customizations.
class WPForms_WP_Emails {
* Store the from address.
* Store the reply-to address.
private $reply_to = false;
* Store the reply-to name.
private $reply_to_name = false;
* Store the carbon copy addresses.
* Store the email content type.
* Store the email headers.
* Whether to send email in HTML.
* The email template to use.
* Form data and settings.
* Fields, formatted, and sanitized.
* Notification ID that is currently being processed.
public $notification_id = '';
* Context data to be passed to the tag.
private $context_data = [];
public function __construct() {
if ( 'none' === $this->get_template() ) {
add_action( 'wpforms_email_send_before', [ $this, 'send_before' ] );
add_action( 'wpforms_email_send_after', [ $this, 'send_after' ] );
* @param string $key Object property key.
* @param mixed $value Object property value.
public function __set( $key, $value ) {
* Get the email from name.
* @return string The email from name
public function get_from_name() {
if ( ! empty( $this->from_name ) ) {
$this->from_name = $this->process_tag( $this->from_name );
$this->from_name = get_bloginfo( 'name' );
return apply_filters( 'wpforms_email_from_name', wpforms_decode_string( $this->from_name ), $this );
* Get the email from address.
* @return string The email from address.
public function get_from_address() {
if ( ! empty( $this->from_address ) ) {
$this->from_address = $this->process_tag( $this->from_address );
$this->from_address = get_option( 'admin_email' );
return apply_filters( 'wpforms_email_from_address', wpforms_decode_string( $this->from_address ), $this );
* Get the email reply-to.
* @return string The email reply-to address.
public function get_reply_to() {
if ( ! empty( $this->reply_to ) ) {
$email = $this->reply_to;
// Optional custom format with a Reply-to Name specified: John Doe <john@doe.com>
// - starts with anything,
// - ends with <anything> (expected to be an email, validated later).
$regex = '/^(.+) (<.+>)$/';
if ( preg_match( $regex, $this->reply_to, $matches ) ) {
$this->reply_to_name = wpforms_decode_string( $this->process_tag( $matches[1] ) );
$email = trim( $matches[2], '<> ' );
$this->reply_to = $this->process_tag( $email );
if ( ! is_email( $this->reply_to ) ) {
$this->reply_to_name = false;
return apply_filters( 'wpforms_email_reply_to', wpforms_decode_string( $this->reply_to ), $this );
* Get the email carbon copy addresses.
* @return string The email reply-to address.
public function get_cc() {
if ( is_array( $this->cc ) ) {
$this->cc = implode( ',', $this->cc );
if ( ! empty( $this->cc ) ) {
$this->cc = $this->process_tag( $this->cc );
$addresses = array_map( 'trim', explode( ',', $this->cc ) );
foreach ( $addresses as $key => $address ) {
if ( ! is_email( $address ) ) {
unset( $addresses[ $key ] );
$this->cc = implode( ',', $addresses );
return apply_filters( 'wpforms_email_cc', wpforms_decode_string( $this->cc ), $this );
* Get the email content type.
* @return string The email content type.
public function get_content_type() {
if ( ! $this->content_type && $this->html ) {
$this->content_type = apply_filters( 'wpforms_email_default_content_type', 'text/html', $this );
} elseif ( ! $this->html ) {
$this->content_type = 'text/plain';
return apply_filters( 'wpforms_email_content_type', $this->content_type, $this );
* @return string The email headers.
public function get_headers() {
if ( ! $this->headers ) {
$this->headers = "From: {$this->get_from_name()} <{$this->get_from_address()}>\r\n";
if ( $this->get_reply_to() ) {
$this->headers .= $this->reply_to_name ?
"Reply-To: {$this->reply_to_name} <{$this->get_reply_to()}>\r\n" :
"Reply-To: {$this->get_reply_to()}\r\n";
$this->headers .= "Cc: {$this->get_cc()}\r\n";
$this->headers .= "Content-Type: {$this->get_content_type()}; charset=utf-8\r\n";
return apply_filters( 'wpforms_email_headers', $this->headers, $this );
* @param string $message The email message.
public function build_email( $message ) {
// Plain text email shortcut.
if ( false === $this->html ) {
$message = $this->process_tag( $message );
$message = str_replace( '{all_fields}', $this->wpforms_html_field_value( false ), $message );
return apply_filters( 'wpforms_email_message', wpforms_decode_string( $message ), $this );
* Generate an HTML email.
$this->get_template_part( 'header', $this->get_template(), true );
// Hooks into the email header.
do_action( 'wpforms_email_header', $this );
$this->get_template_part( 'body', $this->get_template(), true );
// Hooks into the email body.
do_action( 'wpforms_email_body', $this );
$this->get_template_part( 'footer', $this->get_template(), true );
// Hooks into the email footer.
do_action( 'wpforms_email_footer', $this );
$message = $this->process_tag( $message );
$message = nl2br( $message );
$message = str_replace( '{email}', $message, $body );
$message = str_replace( '{all_fields}', $this->wpforms_html_field_value( true ), $message );
$message = make_clickable( $message );
return apply_filters( 'wpforms_email_message', $message, $this );
* @param string $to The To address.
* @param string $subject The subject line of the email.
* @param string $message The body of the email.
* @param array $attachments Attachments to the email.
public function send( $to, $subject, $message, $attachments = [] ) {
if ( ! did_action( 'init' ) && ! did_action( 'admin_init' ) ) {
_doing_it_wrong( __FUNCTION__, esc_html__( 'You cannot send emails with WPForms_WP_Emails() until init/admin_init has been reached.', 'wpforms-lite' ), null );
// Don't send anything if emails have been disabled.
if ( $this->is_email_disabled() ) {
// Don't send if email address is invalid.
if ( ! is_email( $to ) ) {
$this->context_data = [ 'to_email' => (array) $to ];
// Hooks before email is sent.
do_action( 'wpforms_email_send_before', $this );
// Deprecated filter for $attachments.
$attachments = apply_filters_deprecated(
'wpforms_email_attachments',
'1.5.7 of the WPForms plugin',
'wpforms_emails_send_email_data'
* Allow to filter data on per-email basis,
* useful for localizations based on recipient email address, form settings,
* or for specific notifications - whatever available in WPForms_WP_Emails class.
'wpforms_emails_send_email_data',
'headers' => $this->get_headers(),
'attachments' => $attachments,
// Update context data, as 'to' email address could be changed by the filter above.
$this->context_data = [ 'to_email' => (array) $data['to'] ];
$entry_obj = wpforms()->obj( 'entry' );
// phpcs:ignore WPForms.Comments.PHPDocHooks.RequiredHookDocumentation, WPForms.PHP.ValidateHooks.InvalidHookName
$send_same_process = apply_filters(
'wpforms_tasks_entry_emails_trigger_send_same_process',
$entry_obj ? $entry_obj->get( $this->entry_id ) : [],
! empty( $this->form_data['settings']['disable_entries'] )
$this->get_prepared_subject( $data['subject'] ),
$this->build_email( $data['message'] ),
$result = (bool) ( new EntryEmailsTask() )
$this->get_prepared_subject( $data['subject'] ),
$this->build_email( $data['message'] ),
* Hooks after the email is sent.
* @param WPForms_WP_Emails $this Current instance of this object.
do_action( 'wpforms_email_send_after', $this ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
* Add filters/actions before the email is sent.
public function send_before() {
add_filter( 'wp_mail_from', [ $this, 'get_from_address' ] );
add_filter( 'wp_mail_from_name', [ $this, 'get_from_name' ] );
add_filter( 'wp_mail_content_type', [ $this, 'get_content_type' ] );