Contact Form Mail Setup
1. Overview
The contact form (functions/api/contact.ts) supports multiple delivery backends. It tries them in order of priority:
-
Chatwoot — creates a conversation in your Chatwoot instance (primary)
-
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
-
Create a Chatwoot account or self-host an instance
-
Create an API inbox in Chatwoot
-
Generate an API access token under Settings > Account Settings > Access Token
-
Note the Account ID from the URL (e.g.,
app.chatwoot.com/app/accounts/42/…) -
Note the Inbox ID from the inbox settings
2.2. Environment Variables
| Variable | Description |
|---|---|
|
Base URL of your Chatwoot instance (e.g., |
|
Your Chatwoot account ID (numeric) |
|
The inbox ID to create conversations in (numeric) |
|
API access token for authentication |
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
-
Sign in to the Exchange Admin Centre
-
Navigate to Mail flow > Connectors
-
Click Add a connector
-
Configure:
-
Connection from: Your organisation’s email server
-
Connection to: Microsoft 365
-
-
Set the connector name (e.g.,
Cloudflare Pages Contact Form) -
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:
-
Create or designate a mailbox (e.g.,
[email protected]) -
Ensure the mailbox has a licence assigned
-
If using Modern Authentication (OAuth 2.0), register an application in Azure AD:
-
Go to Azure Portal > Azure Active Directory > App registrations
-
Register a new application
-
Grant SMTP.Send permission
-
Create a client secret
-
-
If using Basic Authentication (legacy, not recommended):
-
Enable SMTP AUTH for the specific mailbox in Exchange Admin Centre
-
Navigate to Users > Active users > [mailbox] > Mail > Manage email apps
-
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 |
|---|---|
|
Azure AD tenant ID |
|
Application (client) ID from Azure AD app registration |
|
Client secret from the Azure AD app registration |
|
The mailbox to send from (e.g., |
|
Display sender address (can match |
|
Recipient address (e.g., |
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:SendEmailpermission
4.2. Step 1: Verify Your Domain in SES
-
Sign in to the AWS SES Console
-
Navigate to Verified identities > Create identity
-
Choose Domain and enter
myriadevents.co.za -
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
-
-
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.
-
In the SES console, go to Account dashboard
-
Click Request production access
-
Fill in the required details about your use case
-
AWS typically reviews requests within 24 hours
4.4. Step 3: Create IAM Credentials
-
Go to the IAM Console
-
Create a new IAM user (e.g.,
cloudflare-ses-sender) -
Attach a policy with the following permissions:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ses:SendEmail", "ses:SendRawEmail" ], "Resource": "*" } ] } -
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 |
|---|---|
|
IAM user access key ID |
|
IAM user secret access key |
|
AWS region for SES (e.g., |
|
Verified sender address (e.g., |
|
Recipient address (e.g., |
4.7. DNS Records for SES
Add these records in the Cloudflare DNS dashboard for myriadevents.co.za:
| Type | Name | Value |
|---|---|---|
CNAME |
|
Provided by SES during domain verification |
CNAME |
|
Provided by SES during domain verification |
CNAME |
|
Provided by SES during domain verification |
TXT |
|
|
TXT |
|
|
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 |