Can’t send emails through REST API

You didn’t include the code for your send_email() function, but I looked at the original source (revision 1) of your question, and it seems to me that your send_email() function is good, but if this answer doesn’t help solve the issue, then add the function code into your question body.

So I don’t know why are you setting your (REST) API endpoint’s (HTTP) methods to POST and PUT, i.e. 'methods' => ['POST','PUT'], because I think POST is enough (i.e. 'methods' => 'POST'), but it seems to me that you are not properly sending your form data to the API endpoint.

And note that, when you use FormData (in JavaScript) with the PUT method, both $request->get_json_params() and the superglobal $_POST are empty, so if you must use the PUT method, then you would want to send your form data as a JSON-encoded string.

Secondly, regardless the (HTTP) method you use, if you send the data as JSON, then make sure the Content-Type header is set with application/json being the value.

So here are two examples using JavaScript, one for each HTTP method, and these examples worked fine for me on WordPress 5.6.0:

Using fetch(), FormData and the POST method

const formData = new FormData();

formData.append( 'contact_name', 'John Doe' );
formData.append( 'contact_email', '[email protected]' );
formData.append( 'contact_message', 'Just testing' );

fetch( 'https://example.com/wp-json/contact/v1/send', {
    method: 'POST',
    body: formData
} )
    .then( res => res.json() )
    .then( data => console.log( data ) )
    .catch( error => console.log( error ) );

Using fetch(), JSON.stringify() and the PUT method

const formData = {
    contact_name: 'John Doe',
    contact_email: '[email protected]',
    contact_message: 'Just testing'
};

fetch( 'https://example.com/wp-json/contact/v1/send', {
    method: 'PUT',
    body: JSON.stringify( formData ),
    headers: {
        'Content-Type': 'application/json'
    }
} )
    .then( res => res.json() )
    .then( data => console.log( data ) )
    .catch( error => console.log( error ) );

So I hope that helps, and you don’t have to use fetch() or even JavaScript, but the point in my answer is, make sure your request uses the proper body/content and content type header.

UPDATE

How to access the request parameters

Please check the Extending the REST API → Adding Custom Endpoints → Arguments section in the REST API handbook for more details, but here are two of the most common ways in accessing the parameters:

  • Use $request->get_params() to get all the parameters (URL-encoded POST body, JSON payload, URL query string, etc.). So in your case:

    // Instead of this:
    $parameters = $request->get_json_params();
    if (count($_POST) > 0) {
      $parameters = $_POST;
    }
    
    // You could simply do:
    $parameters = $request->get_params();
    
  • Use direct array access on the object passed as the first parameter to your endpoint callback, i.e. the $request variable above. E.g.

    $contactName    = $request['contact_name'];
    $contactEmail   = $request['contact_email'];
    $contactMessage = $request['contact_message'];
    

And in your case, you should probably just use the second option above.

Have you tested your send_email() function via direct function calls (i.e. not through the REST API)?

Although I don’t know what’s the code in your send_email() function, you should test the function by passing test data and see if the function actually works, e.g. does it return true, false, null, is there any errors/notices, etc. And if it’s the function that doesn’t work, then the problem is not with the REST API, i.e. even on normal requests like the homepage, that function would not work.

So test your email sending function, confirm that it receives all the necessary parameters and that it actually properly sends the email (e.g. use the correct headers like “From” and “Content-Type”).

Always set permission_callback, and utilize validate_callback and sanitize_callback for validating/sanitizing the request parameters

So basically, except for the send_email() function, your code does work in that the endpoint gets registered and it returns a good response. But here are some things that you should bear in mind when adding custom endpoints:

  1. Make sure to always set a permission_callback for your endpoint, because if you don’t set it, then the following would happen (but by default, only if debugging is enabled on your site):

    As of WordPress
    5.5, if a permission_callback is not provided, the REST API will issue a _doing_it_wrong notice.

    Sample notice that would be issued:

    The REST API route definition for contact/v1/send is missing the
    required permission_callback argument. For REST API routes that are
    intended to be public, use __return_true as the permission callback.

  2. The third parameter for register_rest_route() accepts an argument named args which is an array of parameters for your endpoint, and for each parameter, you can set a validation callback (validate_callback) and a sanitization callback (sanitize_callback), so you should use that instead of doing the validation/sanitization in your endpoint’s main callback. And here’s one of the good reasons why would you want to use those callbacks:

    Using sanitize_callback and validate_callback allows the main
    callback to act only to process the request, and prepare data to be
    returned using the WP_REST_Response class. By using these two
    callbacks, you will be able to safely assume your inputs are valid
    and safe when processing
    .

    That was a (slightly reformatted) excerpt taken from the API handbook, BTW.

Try my code

Note: I originally tested your code with a dummy send_email() function, but here, I’m using wp_mail() directly in the main callback.

// The code is basically based on yours.

function sendContactMail( WP_REST_Request $request ) {
    $response = array(
        'status'  => 304,
        'message' => 'There was an error sending the form.'
    );

    $siteName = wp_strip_all_tags( trim( get_option( 'blogname' ) ) );
    $contactName = $request['contact_name'];
    $contactEmail = $request['contact_email'];
    $contactMessage = $request['contact_message'];

    $subject = "[$siteName] (testing) New message from $contactName";
//  $body = "<h3>$subject</h3><br/>";
    $body = "<p><b>Name:</b> $contactName</p>";
    $body .= "<p><b>Email:</b> $contactEmail</p>";
    $body .= "<p><b>Message:</b> $contactMessage</p>";

    $to = get_option( 'admin_email' );
    $headers = array(
        'Content-Type: text/html; charset=UTF-8',
        "Reply-To: $contactName <$contactEmail>",
    );

    if ( wp_mail( $to, $subject, $body, $headers ) ) {
        $response['status'] = 200;
        $response['message'] = 'Form sent successfully.';
        $response['test'] = $body;
    }

    return new WP_REST_Response( $response );
}

add_action( 'rest_api_init', function () {
    register_rest_route( 'contact/v1', 'send', array(
        'methods'             => 'POST',
        'callback'            => 'sendContactMail',
        'permission_callback' => '__return_true',
        'args'                => array(
            'contact_name'    => array(
                'required'          => true,
                'validate_callback' => function ( $value ) {
                    return preg_match( '/[a-z0-9]{2,}/i', $value ) ? true :
                        new WP_Error( 'invalid_contact_name', 'Your custom error.' );
                },
                'sanitize_callback' => 'sanitize_text_field',
            ),
            'contact_email'   => array(
                'required'          => true,
                'validate_callback' => 'is_email',
                'sanitize_callback' => 'sanitize_email',
            ),
            'contact_message' => array(
                'required'          => true,
                'sanitize_callback' => 'sanitize_textarea_field',
            ),
        ),
    ) );
} );

Leave a Comment