Discord Webhook Powered Contact Form

Randall - Jan 27 '22 - - Dev Community

Recently more and more people have been discovering the power of personal Discord servers. You can use them to store files, write notes, mess around with bots, and more.

In this article, I would like to show you how to hook up a contact form on your website to send mail to your Discord server. It's free, it's easy, and it does not even require a backend server.

Before getting started, you should know basic HTML and JavaScript, and you should have a Discord account and a private Discord server (use the plus button in the bottom left of the Discord desktop client to create one).

Creating a Webhook

First, we need to create a webhook in Discord. Decide which channel in your private server you want to receive mail in, and click the settings button. I'm going to use the #general channel:

Location of the settings button

In the settings window, go to the Integrations section, and click Create Webhook:

Location of the Create Webhook button

After the webhook has been created, give it a name (I chose Contacts), and click Copy Webhook URL. This will copy the webhook URL to your clipboard. We'll need it in a little bit.

Making the Contact Form

This article is going to focus on how to call the webhook via JavaScript, so I'm going to gloss over the HTML part a bit. If you want to follow along, you can copy and paste this code into a file called contact.html:

<html>
  <head>
    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
  </head>
  <body class="container mt-5">
    <form onsubmit="sendContact(event)">
      <div class="mb-3">
        <label for="emailInput" class="form-label">Enter your email address</label>
        <input type="email" class="form-control" id="emailInput">
      </div>
      <div class="mb-3">
        <label for="messageInput" class="form-label">Enter your message</label>
        <textarea class="form-control" id="messageInput" rows="3"></textarea>
      </div>
      <button type="submit" class="btn btn-primary">Submit</button>
    </form>
    <script>
      async function sendContact(ev) {
      }
    </script>
  </body>
</html>

Enter fullscreen mode Exit fullscreen mode

It's just really basic HTML boilerplate plus bootstrap to make things look slightly non-gross.

If you open the contact.html file in your browser, this is what you will see:

Basic HTML contact form

And if you click the Submit button, it will call the sendContact function, which does nothing!

So let's make it do something. Let's start writing code in the sendContact() function.

First, no surprises, let's prevent the default form submit action, and let's get the email address and message that the user input:

ev.preventDefault();

const senderEmail = document
  .getElementById('emailInput').value;
const senderMessage = document
  .getElementById('messageInput').value;
Enter fullscreen mode Exit fullscreen mode

Next let's craft the body that we're going to send to the webhook. The body should be a Discord message object, which is clearly documented in the Discord API documentation.

In our case, we just want a message with a title and two sub-sections: Sender and Message. That's going to look like this:

const webhookBody = {
  embeds: [{
    title: 'Contact Form Submitted',
    fields: [
      { name: 'Sender', value: senderEmail },
      { name: 'Message', value: senderMessage }
    ]
  }],
};
Enter fullscreen mode Exit fullscreen mode

Now we just use fetch to send the webhook. Remember that webhook URL you copied earlier? You'll need it here. Paste it in as the value of the webhookUrl variable:

const webhookUrl = 'YOUR URL HERE';

const response = await fetch(webhookUrl, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(webhookBody),
});
Enter fullscreen mode Exit fullscreen mode

Then let's show an alert and tell the user whether the request was successful:

if (response.ok) {
  alert('I have received your message!');
} else {
  alert('There was an error! Try again later!');
}
Enter fullscreen mode Exit fullscreen mode

That's it! Refresh the page, type in an email and message, and click Submit.

If you did everything right, you should hear a satisfying little ting sound from your Discord client telling you that there has been a new message in your server. Check it out:

The content of the contact form sent to my Discord server

With just a little bit of frontend code we now officially have our contact form sending mail to our private Discord server.

Full Code

Here's the full code that I used for this demo. Remember to replace YOUR URL HERE with your webhook URL.

<html>
  <head>
    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
  </head>
  <body class="container mt-5">
    <form onsubmit="sendContact(event)">
      <div class="mb-3">
        <label for="emailInput" class="form-label">Enter your email address</label>
        <input type="email" class="form-control" id="emailInput">
      </div>
      <div class="mb-3">
        <label for="messageInput" class="form-label">Enter your message</label>
        <textarea class="form-control" id="messageInput" rows="3"></textarea>
      </div>
      <button type="submit" class="btn btn-primary">Submit</button>
    </form>
    <script>
      async function sendContact(ev) {
        ev.preventDefault();

        const senderEmail = document
          .getElementById('emailInput').value;
        const senderMessage = document
          .getElementById('messageInput').value;

        const webhookBody = {
          embeds: [{
            title: 'Contact Form Submitted',
            fields: [
              { name: 'Sender', value: senderEmail },
              { name: 'Message', value: senderMessage }
            ]
          }],
        };

        const webhookUrl = 'YOUR URL HERE';

        const response = await fetch(webhookUrl, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify(webhookBody),
        });

        if (response.ok) {
          alert('I have received your message!');
        } else {
          alert('There was an error! Try again later!');
        }
      }
    </script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

A Word of Caution

It's important to understand that putting your webhook link into your frontend code means that a malicious actor could take it and use it in a script to spam you or even send you nasty images.

Fortunately, that's about the worst they could do. The link only allows sending messages in your server, and does not allow reading messages, taking any sort of administrative action, or really anything else.

So while embedding the webhook link into your small, personal website is likely going to be fine, I would not do this for anything major, or if I had a lot of tech-savvy enemies. I also would not do this in a Discord channel that many other people have read access to.

If you are concerned about these risks but you still want to use a Discord webhook for your contact form, you would need some sort of backend to be a middleman for the requests. In fact, I use that approach for my own personal site.

Blocked TLDs

It has come to my attention that this method does not work for *.tk domains. Discord seems to selectively not return the needed CORS headers for TLDs that it does not like. Besides getting a new domain, your only option involves proxying the request through a backend server.

Conclusion

Getting contact forms to work well (for free) can actually be harder than it sounds. I used to use Gmail's SMTP server via my personal website's backend for this, but it would frequently stop working for "security reasons" until I went into my account and reminded Google that it's legit traffic. I ended up swapping in a Discord webhook instead and haven't looked back. It's super convenient and easy to set up, and has worked very reliably.

. . . . . . . . . . . . . . . . . . . . . . .