Contact Form Mail Setup

1. Overview

The contact form (functions/api/contact.ts) supports multiple delivery backends. It tries them in order of priority:

  1. Chatwoot — creates a conversation in your Chatwoot instance (primary)

  2. Email — sends an email via the configured mail provider (fallback)

If no delivery method is configured, form submissions are logged to the Cloudflare Pages Function console.

All environment variables are configured as production secrets in the Cloudflare Pages project settings — never commit them to source control.

2. Option 1: Chatwoot (Primary)

Chatwoot is an open-source customer engagement platform. When configured, form submissions create a new conversation with the contact’s details.

2.1. Setup Steps

  1. Create a Chatwoot account or self-host an instance

  2. Create an API inbox in Chatwoot

  3. Generate an API access token under Settings > Account Settings > Access Token

  4. Note the Account ID from the URL (e.g., app.chatwoot.com/app/accounts/42/…​)

  5. Note the Inbox ID from the inbox settings

2.2. Environment Variables

Variable Description

CHATWOOT_BASE_URL

Base URL of your Chatwoot instance (e.g., https://app.chatwoot.com)

CHATWOOT_ACCOUNT_ID

Your Chatwoot account ID (numeric)

CHATWOOT_INBOX_ID

The inbox ID to create conversations in (numeric)

CHATWOOT_API_TOKEN

API access token for authentication

2.3. Configure in Cloudflare

  1. Go to your Cloudflare Pages project in the dashboard

  2. Navigate to Settings > Environment variables

  3. Add each variable above as a Production secret

  4. Redeploy the site for the changes to take effect

3. Option 2: SMTP via Microsoft Exchange Online Connector

Use a Microsoft 365 Exchange Online SMTP connector to relay contact form emails through your organisation’s mail infrastructure.

3.1. Prerequisites

  • A Microsoft 365 subscription with Exchange Online

  • Administrative access to the Exchange Admin Centre

  • A verified sender domain in Microsoft 365

3.2. Step 1: Create an SMTP Connector in Exchange Online

  1. Sign in to the Exchange Admin Centre

  2. Navigate to Mail flow > Connectors

  3. Click Add a connector

  4. Configure:

    • Connection from: Your organisation’s email server

    • Connection to: Microsoft 365

  5. Set the connector name (e.g., Cloudflare Pages Contact Form)

  6. Under Authenticating sent email, choose one of:

    • By verifying the sending IP address — add the Cloudflare Pages Function egress IPs

    • By verifying the certificate — if using a TLS client certificate

3.3. Step 2: Configure SMTP Relay

The connector endpoint is typically:

smtp.office365.com:587

You will need a dedicated mailbox or shared mailbox to authenticate SMTP submissions:

  1. Create or designate a mailbox (e.g., [email protected])

  2. Ensure the mailbox has a licence assigned

  3. If using Modern Authentication (OAuth 2.0), register an application in Azure AD:

    1. Go to Azure Portal > Azure Active Directory > App registrations

    2. Register a new application

    3. Grant SMTP.Send permission

    4. Create a client secret

  4. If using Basic Authentication (legacy, not recommended):

    1. Enable SMTP AUTH for the specific mailbox in Exchange Admin Centre

    2. Navigate to Users > Active users > [mailbox] > Mail > Manage email apps

    3. Enable Authenticated SMTP

3.4. Step 3: Update the Contact Form Function

The current contact.ts function uses the MailChannels API for email delivery. To use Exchange SMTP, you need to replace the email sending logic with an SMTP client compatible with Cloudflare Workers.

Cloudflare Workers do not support raw TCP sockets, so direct SMTP connections are not possible. Instead, use the Microsoft Graph API to send mail:
// Send via Microsoft Graph API
const graphResponse = await fetch(
  'https://graph.microsoft.com/v1.0/users/{sender-email}/sendMail',
  {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${accessToken}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      message: {
        subject: `New Contact Form Submission from ${data.name}`,
        body: {
          contentType: 'Text',
          content: formatMessage(data),
        },
        toRecipients: [
          { emailAddress: { address: env.TO_EMAIL } },
        ],
      },
    }),
  }
);

3.5. Environment Variables

Variable Description

MS_TENANT_ID

Azure AD tenant ID

MS_CLIENT_ID

Application (client) ID from Azure AD app registration

MS_CLIENT_SECRET

Client secret from the Azure AD app registration

MS_SENDER_EMAIL

The mailbox to send from (e.g., [email protected])

FROM_EMAIL

Display sender address (can match MS_SENDER_EMAIL)

TO_EMAIL

Recipient address (e.g., [email protected])

3.6. Obtaining an Access Token

The Pages Function must obtain an OAuth 2.0 token using the client credentials flow:

async function getMsGraphToken(env: Env): Promise<string> {
  const tokenUrl = `https://login.microsoftonline.com/${env.MS_TENANT_ID}/oauth2/v2.0/token`;

  const response = await fetch(tokenUrl, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      client_id: env.MS_CLIENT_ID,
      client_secret: env.MS_CLIENT_SECRET,
      scope: 'https://graph.microsoft.com/.default',
      grant_type: 'client_credentials',
    }),
  });

  const data = await response.json();
  return data.access_token;
}
The Azure AD application must have the Mail.Send application permission (not delegated) granted with admin consent.

4. Option 3: AWS Simple Email Service (SES)

Amazon SES is a scalable email sending service. It integrates well with Cloudflare Workers via the AWS SES v2 HTTP API.

4.1. Prerequisites

  • An AWS account

  • A verified domain or email address in SES

  • SES moved out of the sandbox (for production sending)

  • An IAM user or role with ses:SendEmail permission

4.2. Step 1: Verify Your Domain in SES

  1. Sign in to the AWS SES Console

  2. Navigate to Verified identities > Create identity

  3. Choose Domain and enter myriadevents.co.za

  4. Add the provided DNS records (DKIM, SPF) to your domain’s DNS configuration in Cloudflare:

    • Add the three CNAME records for DKIM verification

    • Add or update the TXT record for SPF: v=spf1 include:amazonses.com ~all

  5. Wait for verification to complete (typically a few minutes to hours)

4.3. Step 2: Move Out of the SES Sandbox

New SES accounts are in sandbox mode, which restricts sending to verified addresses only.

  1. In the SES console, go to Account dashboard

  2. Click Request production access

  3. Fill in the required details about your use case

  4. AWS typically reviews requests within 24 hours

4.4. Step 3: Create IAM Credentials

  1. Go to the IAM Console

  2. Create a new IAM user (e.g., cloudflare-ses-sender)

  3. Attach a policy with the following permissions:

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": [
            "ses:SendEmail",
            "ses:SendRawEmail"
          ],
          "Resource": "*"
        }
      ]
    }
  4. Create an access key and note the Access Key ID and Secret Access Key

4.5. Step 4: Update the Contact Form Function

Replace the email sending logic in contact.ts to call the SES v2 API:

async function sendViaSes(data: ContactFormData, env: Env): Promise<{ success: boolean }> {
  const region = env.AWS_REGION || 'eu-west-1';
  const endpoint = `https://email.${region}.amazonaws.com/v2/email/outbound-emails`;

  const body = JSON.stringify({
    Content: {
      Simple: {
        Subject: { Data: `New Contact Form Submission from ${data.name}` },
        Body: {
          Text: { Data: formatMessage(data) },
        },
      },
    },
    Destination: {
      ToAddresses: [env.TO_EMAIL],
    },
    FromEmailAddress: env.FROM_EMAIL,
    ReplyToAddresses: [data.email],
  });

  // Sign the request with AWS Signature V4
  // (use a library like aws4fetch for Cloudflare Workers)
  const response = await signedFetch(endpoint, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body,
  }, env);

  return { success: response.ok };
}
Use the aws4fetch library for AWS Signature V4 signing in Cloudflare Workers. Install it with npm install aws4fetch.

4.6. Environment Variables

Variable Description

AWS_ACCESS_KEY_ID

IAM user access key ID

AWS_SECRET_ACCESS_KEY

IAM user secret access key

AWS_REGION

AWS region for SES (e.g., eu-west-1, us-east-1)

FROM_EMAIL

Verified sender address (e.g., [email protected])

TO_EMAIL

Recipient address (e.g., [email protected])

4.7. DNS Records for SES

Add these records in the Cloudflare DNS dashboard for myriadevents.co.za:

Type Name Value

CNAME

[selector1]._domainkey

Provided by SES during domain verification

CNAME

[selector2]._domainkey

Provided by SES during domain verification

CNAME

[selector3]._domainkey

Provided by SES during domain verification

TXT

@

v=spf1 include:amazonses.com ~all

TXT

_dmarc

v=DMARC1; p=quarantine; rua=mailto:[email protected]

5. Comparison of Options

Feature Chatwoot Exchange / Graph API AWS SES

Cost

Free tier available

Included with M365

Free tier: 62K emails/month

Setup complexity

Low

Medium-High

Medium

Conversation tracking

Built-in

Via Outlook

None (email only)

Best for

Customer support workflow

Microsoft-centric organisations

High-volume or API-first setups