You have your website with a contact form. Now you need to send the data from your form to the email. There are many ways on how you can achieve that. In this tutorial, we’ll cover one quite flexible and efficient solution. But it requires some work to do.

If you have a static (HTML, CSS and JavaScript) website, you can host it, even for free. Unfortunately, there is no way to send emails from the browser. It requires a back-end server that can handle the email sending process.

We’ll implement the “serverless” approach. It is very cheap (price starts from a few cents) and a scalable solution.

To do so, we’ll use the following services:

  1. Google Cloud Functions. Here we’ll create a PHP script. It will handle all the necessary email sending preparation logic. Also, in this function, we’ll store sensitive data to hide it from the public view.
  2. SMTP2GO. Email service provider, it will be responsible for sending email to your inbox.
  3. Templid. Every email must contain a subject and body (HTML or plain text). This tool allows us to create, store and manage different email templates for each form.

Let’s start.

HTML form

HTML forms can contain fields of various types.

The only requirement from this tutorial – form should have two hidden inputs.

One hidden field must be with the name “templateId”, for example:

<input name="templateId" id="template-id" value="6" type="hidden" />

Later in this guide, we’ll cover email template creation using Templid. You will need to replace the value with the ID of the template. This way, you’ll “connect” your form data with the email template.

Another input is a so-called “honeypot” to protect your form. In this example, we are using the “puzzle” name for it:

<div style="display: none; visibility: hidden;">
   <input type="text" autocomplete="off" name="puzzle" id="puzzle">
</div>

You can check our guide on how to protect your form in this article: How to protect website HTML contact form from spam without captcha.

So, your final form will look like this:

<form action="URL_FROM_GOOGLE_CLOUD_FUNCTION" method="POST">
   <input type="text" name="name" id="name">
   <input type="email" name="email" id="email">
   <textarea name="question" id="question"></textarea>
   <input type="hidden" name="templateId" id="template-id" value="6" />

   <div style="display: none; visibility: hidden;">
       <input type="text" autocomplete="off" name="puzzle" id="puzzle">
   </div>

   <input type="submit" value="Send!">
</form>

Setup email service provider – SMTP2GO

To send emails, we will use the SMTP2GO service. It is free for up to 1000 email sends per month.

Go to https://www.smtp2go.com and create your account if you don’t have one.

Next, you’ll need to add your domain and verify it via DNS. We’ve created a step-by-step video guide on how to do that: https://youtu.be/Dvm72eA72To.

After your domain is set up and verified, you have to create SMTP credentials.

In your dashboard, go to “Sending → SMTP users”.

Setup email service provider - SMTP2GO

Click “Continue”.

Setup email service provider - SMTP2GO

After the page will be loaded, click “Add SMTP user”.

Add SMTP user in SMTP2GO

Now you need to create your username and password. Also, you can add a description if you like. Click “Add SMTP user” to save the data.

Add SMTP user in SMTP2GO

We will use these credentials in our PHP script later.

Store your email templates in Templid

We will use Templid to manage email templates. You can create templates with dynamic variables ({{variable_name}}) that will be replaced with the input values from the form. For example, if you have input with the name “company”, you can add {{company}} anywhere int the template, and it will be substituted with the value from your form.

Go to https://templid.com and click “Register”.

Register on the templid.com

Fill the form with your details and click “Register”.

Fill the registration form on Templid

A confirmation email will be sent to your address, click the link to approve the registration.

In the dashboard, click “Templates → Add new template”.

Add new template button in the Templid dashboard

Here you’ll need to create the template. You can type any name in the “Template name”, this is only visible to you.

Name the template in the Templid

The subject can also contain dynamic variables like {{some_variable}}, so it will be replaced with data from your form.

Email subject with dynamic variable in the templid.com dashboard

We recommend using some unique value in the subject, so these emails will not be merged in the email client like Gmail.

Recommended title type in the Gmail

As shown in the example above, you can use the following subject:

Website contact form - {{"now"|date("U")}}

You don’t need to add any specific field to your form, {{“now”|date(“U”)}} will be replaced with the timestamp value automatically, which is changing every second.

For the HTML template, you can use any email template builder (we recommend MJML). Or you can write your HTML from scratch.

Plain text is optional, it is needed if your email client does not support HTML.

After you create your template, click “Save new template”.

Email HTML content with the dynamic variables

Now copy the template ID and add hidden input to your form with the name “templateId” as shown in the example above.

Template ID
<input name="templateId" id="template-id" value="6" type="hidden" />

Next, click on the “Tokens” in the left bar.

After the page is loaded, click “Add new token”, this will generate an API token for you. You will need this in your Google Cloud Function script later.

API token in the Templid dashboard

If you have multiple forms on your site with different inputs, you can create templates for each form.

Google cloud function code in PHP

Why do we need this?

All your website HTML, JavaScript and CSS are publicly visible in the browser. This means that anyone can access it and even manipulate it though the inspect element.

To protect your form and sensitive data, we need a “back-end” solution. Something that can be hidden from the public view. For this purpose, we’ll use Google Cloud Functions.

In this guide, we’ll write code with PHP language. The script will:

  1. validate form data;
  2. build an email content;
  3. send it via SMTP.

Inside this function, we also will store sensitive data, like recipient email, SMTP credentials and other information that must be hidden from public view.

This will be the only part that will cost you a few cents from the beginning (~$0.05/month).

You will be able to create different forms across your website and use the same script to send the data to your email. Please leave a comment below, if you want to see an example with multiple forms.

Let’s build our first Google Cloud Function.

Go to https://cloud.google.com and login with your Google account.

Sign in to the Google Cloud console

After you log in, you should be able to view your console dashboard at https://console.cloud.google.com. You might be asked to add your billing details before you can start.

In the top search bar, type “cloud functions” and click the product called “Cloud functions”.

Navigate to the Cloud Functions in the Google Cloud console

If you log in for the first time, you will be asked to create a new project. Click the button “CREATE PROJECT” and continue with the instructions.

Create new project in the Google Cloud console

Next, click the “CREATE FUNCTION” button.

Create new function in the Google Cloud console

For the environment, choose option “2nd gen”.

In the “Function name” type any name you want, this part will be visible only to you.

For the “Region” choose the “us-central1” option.

Basic settings for the Function in the Google Cloud console

Under the “Authentication” select the “Allow unauthenticated invocations” option.

Authentication settings for the function in the Google Cloud console

Click on “Runtime, build, connections and security settings” to expand more options.

Expand more settings in the Google Cloud Function

Type 30 In the “Timeout” input.

Timeout setting for the function in the Google Cloud console

Under the “Auto-scaling” in the “Maximum number of instances” type 10. This one is important. Because you can call this function up to 2 million times per month at no extra fees. In case you will have a spike in form submissions, a tight limit of instances will prevent you from unexpected costs.

Google Cloud function Auto-scaling settings

Next, click “+ ADD VARIABLE” button under the “Runtime environment variables”.

Custom variables in the Google Cloud Function

We need to add the following variables:

NameValue
SMTP_HOSTmail.smtp2go.com
SMTP_USERYour username created earlier
SMTP_PASSWORDPassword for the user
SMTP_PORT2525
SMTP_TYPELOGIN
SMTP_ENCRYPTIONtls
FROM_EMAILAny email with your domain verified in the SMTP2GO
FROM_NAMEAny name
TO_EMAILThis can be any email address because it will receive the message
TO_NAMEAny name
TEMPLID_TOKENToken generated in the Templid dashboard
REDIRECT_URL(optional)
Set this value to redirect the visitor to your “Thank you” page after successful form submission.

If you are using AJAX (JavaScript) to submit the form, do not include this variable.

The function will return string {“status”: “ok”} if no REDIRECT_URL is set.
List of custom variables in the Google Cloud function

Click “Next”.

Click "Next" to go the code editing

Select “PHP 8.1” under the “Runtime”.

Change the name of the “Entry point” to “email”.

PHP 8.1 runtime and entry point name in the Google Cloud function

Now let’s edit the code. Select the file “composer.json” and paste the following code:

{
  "autoload": {
    "psr-4": {
      "App\\": "app/"
    }
  },
  "require": {
    "google/cloud-functions-framework": "^1.1",
    "guzzlehttp/guzzle": "^7.2",
    "phpmailer/phpmailer": "^6.7.1"
  }
}

Go to the “index.php” file and paste the following code:

<?php

header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');

use App\Mailer;
use App\Fetcher;
use App\RequestDataValidator;
use Psr\Http\Message\ServerRequestInterface;
use Google\CloudFunctions\FunctionsFramework;

FunctionsFramework::http('email', 'email');

/**
* @param ServerRequestInterface $request
*
* @return string
*/
function email(ServerRequestInterface $request): string
{
  if ($request->getMethod() !== 'POST') {
    header('Access-Control-Allow-Methods: POST,OPTIONS');
    http_response_code(204);
    exit;
  }

  $data = RequestDataValidator::validate($request);

  if (!empty($data['puzzle'])) {
    return '{"status": "ok"}';
  }

  $templateId = (int) $data['templateId'];

  $template = Fetcher::fetch($templateId, $data);

  Mailer::send($template);

  $redirectUrl = getenv('REDIRECT_URL') ?? null;

  if ($redirectUrl) {
    header('Location: ' . $redirectUrl);

    die();
  }

  return '{"status": "ok"}';
}

The index.php is the main file where we “orchestrate” all the flow after the form is submitted.

First, it’ll check if the request has a valid method. This script will continue to execute only with the POST method. Otherwise, it will return an empty response.

Next, we call the class RequestDataValidator with the method validate to check if the data from the form has all the needed values.

After that, we check for the “honeypot” field. In case this field is not empty, we return a success message and do not execute further code. This will prevent sending emails if the form was submitted by a spambot. You can read more about this in our article on how to protect your HTML form.

Next, we render and fetch the template for the email subject and body.

And finally, we send the email.

All the files below must be created in the “app” directory.

Click the “+” icon.

Add new file in the Google Cloud function

Ttype “app/RequestDataValidator.php”.

Rename the file in the Google Cloud Function

Inside this file, paste the following code:

<?php

declare(strict_types=1);

namespace App;

use Exception;
use Psr\Http\Message\ServerRequestInterface;

class RequestDataValidator
{
  /**
   * @param ServerRequestInterface $request
   *
   * @return array
   */
  public static function validate(ServerRequestInterface $request): array
  {
    $data = $request->getParsedBody() ?? [];

    if (!isset($data['templateId'])
      || (int) $data['templateId'] === 0
    ) {
           throw new Exception('Missing or invalid template ID');
    }

    return $data;
  }
}

In this file, we are validating the form data. We only check if the template ID is set because we need this to fetch the template from Templid.

You can add any additional data validation here if you need.

Again, click the “+” icon and type “app/Fetcher.php” and paste the following code:

<?php

declare(strict_types=1);

namespace App;

use Exception;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Request;
use Psr\Http\Message\ResponseInterface;

class Fetcher
{
  protected const TEMPLID_API_URL = 'https://api.templid.com/v1/';
  protected const MAX_RETRIES     = 3;

  /**
   * @param array $data
   *
   * @return TemplateDto
   */
  public static function fetch(
    int $templateId,
    array $data = []
  ): TemplateDto {
    $request = new Request(
      'POST',
      sprintf('templates/%s/render', $templateId),
      [
        'Content-Type'  => 'application/json',
        'Authorization' => sprintf('Bearer %s', getenv('TEMPLID_TOKEN')),
      ],
      json_encode($data),
    );

    $response = self::sendRequest($request);

    $template = json_decode($response->getBody()->getContents());

    return new TemplateDto(
      subject: $template->subject,
      html:    $template->html,
      text:    $template->text
    );
  }

  /**
   * @param Request $request
   *
   * @return ResponseInterface
   */
  protected static function sendRequest(
    Request $request
  ): ResponseInterface {
    $client = new Client(['base_uri' => self::TEMPLID_API_URL]);

    $attempts = 0;

    do {
      try {
        $response = $client->send($request);

        ResponseValidator::validate($response);

        return $response;
      } catch (RateLimitException $e) {
        sleep(1);

        $attempts++;
      }
    } while ($attempts < self::MAX_RETRIES);

    throw new Exception('Unable to fetch template');
  }
}

Here we are rendering our template. We send the form data to the Templid using API and get the email subject, HTML and plain text with all the fields set in the template.

Create file “app/ResponseValidator.php” with code:

<?php

declare(strict_types=1);

namespace App;

use Exception;
use Psr\Http\Message\ResponseInterface;

class ResponseValidator
{
  protected const ERROR_MESSAGES = [
    401 => 'Unauthorized',
    404 => 'Template not found',
    429 => 'Too many requests',
  ];

  /**
   * @param Response $response
   *
   * @return void
   */
  public static function validate(
    ResponseInterface $response
  ): void {
    $statusCode = $response->getStatusCode();

    if ($statusCode === 200) {
      return;
    }

    $errorMessage = self::ERROR_MESSAGES[$statusCode] ?? 'API error';

    $statusCode === 429
      ? throw new RateLimitException($errorMessage)
      : throw new Exception($errorMessage);
  }
}

In this file, we are validating the response from the Templid API and throwing appropriate exceptions if needed.

Create file “app/RateLimitException.php” with code:

<?php

declare(strict_types=1);

namespace App;

use Exception;

class RateLimitException extends Exception
{}

We need this file to retry the fetching template. Templid API has rate limits, so there might be cases when we need to repeat the request. This exception will be our indicator on when we need to retry the request.

Create the file “app/TemplateDto.php” with code:

<?php

declare(strict_types=1);

namespace App;

class TemplateDto
{
  /**
   * @param string $subject
   * @param string $html
   * @param string $text
   */
  public function __construct(
    protected string $subject = '',
    protected string $html    = '',
    protected string $text    = '',
  ) {}

  /**
   * @return string
   */
  public function getSubject(): string
  {
    return $this->subject;
  }

  /**
   * @return string
   */
  public function getHtml(): string
  {
    return $this->html;
  }

  /**
   * @return string
   */
  public function getText(): string
  {
    return $this->text;
  }
}

This is our Data Transfer Object that will contain all the email content. This class takes values from the Templid API and transfers it to the email sending class below.

Create one more file: “app/Mailer.php” and paste the following code:

<?php

declare(strict_types=1);

namespace App;

use PHPMailer\PHPMailer\PHPMailer;

class Mailer
{
  /**
   * @param TemplateDto $template
   *
   * @return void
   */
  public static function send(
    TemplateDto $template
  ): void {
    $mail = new PHPMailer(true);

    $mail->isSMTP();
    $mail->Host       = getenv('SMTP_HOST');
    $mail->SMTPAuth   = true;
    $mail->AuthType   = getenv('SMTP_TYPE') ?? 'LOGIN';
    $mail->Username   = getenv('SMTP_USER');
    $mail->Password   = getenv('SMTP_PASSWORD');
    $mail->SMTPSecure = getenv('SMTP_ENCRYPTION') ?? 'ssl';
    $mail->Port       = getenv('SMTP_PORT') ?? 465;

    $mail->setFrom(getenv('FROM_EMAIL'), getenv('FROM_NAME'));

    //Recipients
    $mail->addAddress(getenv('TO_EMAIL'), getenv('TO_NAME'));

    //Content
    $mail->isHTML(true);
    $mail->Subject = $template->getSubject();
    $mail->Body    = $template->getHtml();
    $mail->AltBody = $template->getText();

    $mail->send();
  }
}

This file will prepare and send email using SMTP.

Now click “Deploy” and wait a few minutes until your function will be ready.

Deploy your Google Cloud Function

After your function is deployed, copy the link and paste it as your form’s action parameter.

Copy function URL to the clipboard in the Google Cloud console
<form action="https://some-function-name.a.run.app" method="POST">

Your form will now submit the data and email will be sent to your inbox.

Conclusion

Respect to you, if you’ve made this tutorial to the end! We hope this guide was helpful.

Please leave your comment below if you have any questions.