Coding ยท ยท

Sending emails

Unlike in Bob's B&B task where you mock-sent emails, here you'll learn how to send real emails.

Sending email must happen from your server-side rather than client-side code, as you'll need to send email with an API key which you must keep private/secret and not expose in client-side code.

This assumes that you have a repo with an existing website in it, which you run locally and in production with bun via a server.js like this:

let server = Bun.serve({
  routes: {
    '/*': () => new Response(`Welcome to CMS! ${Math.random()}`),
    // ... other routes
  }
})
console.log(`Listening on ${server.url}`)

Getting your sendgrid API key

You'll be making an account with SendGrid and sending emails via their API.

Note that sendgrid's free tier is limited to 100 emails/day.

Test by sending an email from your PC via Bun

export async function sendEmail ({ to, from, subject, body }) {
  let SENDGRID_API_KEY = process.env.SENDGRID_API_KEY
  if (!SENDGRID_API_KEY) {
    console.error('Environment variable SENDGRID_API_KEY not set')
    console.error('Create a .env file with SENDGRID_API_KEY=your_key_here in the root of your repo')
    return
  }

  let result = await fetch('https://api.sendgrid.com/v3/mail/send', {
    method: 'POST',
    body: JSON.stringify({
      'personalizations': [{ 'to': [{ 'email': to }] }],
      'from': { 'email': from },
      'subject': subject,
      'content': [{ 'type': 'text/plain', 'value': body.trim().replace(/\n +/gm, '\n') }]
    }),
    headers: {
      'Authorization': `Bearer ${SENDGRID_API_KEY}`,
      'content-type': 'application/json',
    },
  })
  console.log(result)
}

import { sendEmail } from './email.js'

sendEmail({
  to: 'jgordon@cms.tela.org.uk',
  from: 'jgordon@cms.tela.org.uk',
  subject: 'This is a test email',
  body: `
    This is the body of my email.
    This is another line of my email.

    Lots of Love,

    Mr Gordon
  `
})

Adding an server-side POST route

You probably want sending the email to be triggered by something that happens on your website. For that, something in your client-side code needs to trigger a POST request to your server, which then runs the sendEmail() function.

Most request to your server will be GET requests. When you want to trigger an action such as sending an email or saving something to a database, you should use POST requests instead, which means using Hono's app.post() instead of app.get(). You're going to make a POST route for /email by first importing email.js at the top of your server.js:

import { sendEmail } from './email.js'

Then, add the following route to Bun.serve({ routes: { ... } }):

'/email': {
  POST: () => {
    sendEmail({
      to: '_your_email_',
      from: '_your_email_', // must be the email address you verified
      subject: 'This is the email subject',
      body: 'This is the body of the email'
    })
    return new Response('ok')
  }
}

You'll need to make a POST request to /email to see if it actually works, which we'll do in the next step.

POSTing from a web page

Create a send-email.html or other client-side HTML file with this, probably in your ./public folder:

<!doctype html>
<title>Email test</title>
<button id="send">Email me</button>
<p id="sent" style="display: none;">Email sent!</p>
<script>
send.onclick = () => {
  fetch(`/email`, { method: 'POST' })
  send.style.display = 'none'
  sent.style.display = 'block'
}
</script>

Then:

Putting it live

Assuming it works locally, it should work on your actual online website too. This comes with a caveat/warning that you should probably be doing a fair amount of error handling, rate limiting and other things if you were to do this for real-real.

Increasing security

Ideally you shouldn't add the .env file to git, and instead ignore it with a .gitignore file. However, this isn't currently possible with how I'm hosting your website.

Next steps

You probably want the user to be able to customise some aspects of the emails that get sent, such as the title, body, to address and/or from address. To do that, you'll need to send some extra data (the 'body') through with the fetch() POST request from the client-side code, then do something with that data in the server-side code for the route.

You may also want to check the docs on sendgrid for how you can customise the emails.