Starting a Blog with Hugo and Adding a Contact Form

Starting a Blog with Hugo and Adding a Contact Form

Embarking on the journey of creating a personal blog or website often begins with the quest for simplicity and speed. In my pursuit, I stumbled upon a Reddit comment that said, “Just use Hugo!” This led me to discover the world of Hugo. This post marks the beginning of my Hugo-powered site.

Getting Started with Hugo

Despite having zero experience with Hugo, I managed to set it up in just one evening. Special thanks to Hugoplate from Zeon Studio for this theme.

Setting Up a Contact Form with Email Delivery

While the initial setup was swift, the most time-consuming aspect turned out to be configuring email delivery for the contact form.

What I could not do was Send it with ajax with my email API Keys in the wild , so here is how I did it.

Sending Emails in Static Generated Sites

Despite still utilizing Ajax (with Alpine.js) to submit forms, the actual email sending is now handled server-side through a compact PHP script.

Likely just as simple in most languages but the only thing harder than hosting index.php on a server is index.html.

HTML Form

Regular HTML form that submits via ajax

<form id="contact-form" action="/sendemail" 
      method="POST" x-data="contactForm()" 
      x-cloak @submit.prevent="submitData">
  <div class="mb-6" x-show="submitted">
    <p class="text-left font-medium tracking-normal text-emerald-600">Thank you!! I will get back as soon as possible</p>
  </div>
  <div class="mb-6">
    <label for="name" class="form-label">
      Message <span class="text-red-500">*</span>
    </label>
    <textarea
      x-model="formData.message"
      id="message"
      name="message"
      class="form-input"
      placeholder="Message goes here..."
      required
      rows="8"></textarea>
  </div>
</form>

<!-- Alpine.js -->
<script src="https://cdn.jsdelivr.net/npm/alpinejs@2.8.2/dist/alpine.min.js" defer></script>

Ajax Script

<script>
  function contactForm() {
    return {
      formData: {
        message: '',
      },
      submitted: false,

      submitData() {
        let form = document.getElementById('contact-form');

          fetch('/sendemail', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify(this.formData)
          })
            .then(() => {
              this.submitted = true;
            })
            .catch(() => {
              this.submitted = true
            })
      }
    }
  }
</script>

PHP Email Sending Script

The goal was a script with zero dependency, but I had difficulties reading environmental variables that stored the API keys, and so I opted to use Symfony’s DotEnv Component .

I have used SendGrid, but it can be easily adapted to any provider. Full code available here https://github.com/ktarila/sendgrid-email

require '../vendor/autoload.php';
use Symfony\Component\Dotenv\Dotenv;

$dotenv = new Dotenv();
$dotenv->load('../.env');

$allowedDomains = ['localhost'];
$currentDomain = $_SERVER['HTTP_HOST'];
$referrer = $_SERVER['REFERER'] ?? "http://{$currentDomain}/contact";
$sendgridApiKey = $_ENV['SENDGRID_API_KEY'] ?? '';


if ($_SERVER['REQUEST_METHOD'] === 'POST'
    && in_array($currentDomain, $allowedDomains)
) {
    $recipientEmail = 'recipient@example.com';
    $senderEmail = 'sender@example.com';
    $subject = 'Contact form';
    $json = file_get_contents('php://input');

    $sendgridApiUrl = 'https://api.sendgrid.com/v3/mail/send';
    if (!empty($json)) {
        $data = json_decode($json, true);
        $message = $data['message'] ?? '';
        $email = $data['email'] ?? '';
        $name = $data['name'] ?? '';
        $content = "Email: {$email} \nName: {$name} \nMessage: {$message}";
    }

    if (!empty($message) && !empty($email) && !empty($name)) {
        $data = [
            'personalizations' => [
                [
                    'to' => [
                        ['email' => $recipientEmail]
                    ]
                ]
            ],
            'from' => [
                'email' => $senderEmail
            ],
            'subject' => $subject,
            'content' => [
                [
                    'type' => 'text/plain',
                    'value' => $content
                ]
            ]
        ];

        $dataString = json_encode($data);

        $ch = curl_init($sendgridApiUrl);

        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
        curl_setopt($ch, CURLOPT_POSTFIELDS, $dataString);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt(
            $ch,
            CURLOPT_HTTPHEADER,
            [
            'Authorization: Bearer ' . $sendgridApiKey,
            'Content-Type: application/json',
        ]
        );

        $response = curl_exec($ch);
        curl_close($ch);
        
    }
} else {
    echo 'Not allowed';
}

header("Location: {$referrer}");
exit();

Nginx Configuration To complete the setup, configuring Nginx is required. The following snippet demonstrates how to process /sendemail with PHP while serving other paths directly:

server {
    listen 80;
    server_name severname.local;

    location /sendemail {
        root /full/path/send_email/public;
        index index.php;
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ ^/index\.php(/|$) {
        root /full/path/send_email/public;
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;

        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
        
        internal;
    }
   
    location / {
        root /full/path/hugo/public;
        index index.html;
        try_files $uri $uri/ =404;
    }

    error_page 404 /404.html;
    location = /404.html {
        root /full/path/hugo/public;
        internal;
    }

    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
        root /full/path/hugo/public;
        internal;
    }
}

Feel free to use this Markdown content for your blog or website. If you have any more requests or questions, let me know!

comments powered by Disqus

Related Posts

Faster queues with Symfony and Go: When PHP isn't Fast Enough

Faster queues with Symfony and Go: When PHP isn't Fast Enough

For most applications PHP is more than capable, and I’ve never run into a situation where it has been too slow, especially as I usually process long-running tasks in the background with Symfony Messenger.

Read More