Skip to main content

📬 Contact Messages


📬 Cosmy Contact Worker

This Cloudflare Worker handles contact requests sent via email or API and forwards them to Discord channels and a configured email address. It supports three types of requests:

  1. Contact Messages
  2. Bug Reports
  3. Account Deletion Requests

🚀 How It Works​

The worker listens for incoming API requests at specific endpoints:

  • POST https://cosmy.app/api/v1/contact/message
  • POST https://cosmy.app/api/v1/contact/bug-report
  • POST https://cosmy.app/api/v1/contact/account-deletion

These endpoints accept JSON payloads and send an email to one of the following addresses:

  • contact@cosmy.app
  • bug-report@cosmy.app
  • account-deletion@cosmy.app

The "from" address of the email is set to website+<message|bug-report|account-deletion>@cosmy.app when composed via the API.

When an email is received (either composed via the API or sent directly to the email addresses), it is parsed and forwarded to the appropriate Discord channel and a configured email address.

🛠️ Usage​

1. Install Dependencies​

Run the following command to install the required dependencies:

yarn

2. Development​

To start developing on the worker, first make sure to create a .dev.vars file in the root directory of the project and add the required environment variables (see Environment Variables section).

Then, run the following command to start the worker in development mode:

yarn dev

This will use Cloudflare's wrangler tool to run the worker locally. As this worker use the email binding that is not available locally, the --remote flag is used to run the worker in the Cloudflare environment.

3. Deployment​

To deploy the worker to Cloudflare:

yarn deploy

This build the worker in the dist directory, upload the source maps to Sentry, and deploy the worker to Cloudflare.

4. Debugging​

To debug the published worker, you can see the logs in real-time using:

yarn debug

This will show you the logs for the worker, including any errors or debug messages.

5. Update types​

Following a change in environment variables, or bindings, you may need to update the types in types.d.ts:

yarn generate:types

6. Update JSON Schemas​

If you make changes to the TypeScript DTOs in @cosmy/shared, you can regenerate the JSON schemas using:

yarn generate:schemas

🌍 Environment Variables​

The worker relies on the following environment variables for configuration:

VariableDescription
FORWARD_EMAIL_ADRESSEmail address to forward incoming emails to.
DISCORD_BOT_TOKENDiscord bot token for API authentication.
DISCORD_MESSAGE_CHANNEL_IDDiscord channel ID for general contact messages.
DISCORD_BUG_REPORT_CHANNEL_IDDiscord channel ID for bug reports.
DISCORD_ACCOUNT_DELETION_CHANNEL_IDDiscord channel ID for account deletion requests.
SENTRY_DSNSentry DSN for error tracking.

Local Development​

To run the worker locally, create a .dev.vars file in the root directory of the project and add the following environment variables:

FORWARD_EMAIL_ADRESS=cosmy.solutions@gmail.com
DISCORD_BOT_TOKEN=<discord-bot-token>
DISCORD_MESSAGE_CHANNEL_ID=<discord-message-channel-id>
DISCORD_BUG_REPORT_CHANNEL_ID=<discord-bug-report-channel-id>
DISCORD_ACCOUNT_DELETION_CHANNEL_ID=<discord-account-deletion-channel-id>
SENTRY_DSN=<sentry-dsn>

Deployment​

After the worker is deployed, you can set the environment variables to use in production. You can do this using the Cloudflare dashboard or the wrangler CLI.

wrangler secret put FORWARD_EMAIL_ADRESS # When prompted, enter the email address to forward incoming emails to.
wrangler secret put DISCORD_BOT_TOKEN # When prompted, enter the Discord bot token.
wrangler secret put DISCORD_MESSAGE_CHANNEL_ID # When prompted, enter the Discord channel ID for general contact messages.
wrangler secret put DISCORD_BUG_REPORT_CHANNEL_ID # When prompted, enter the Discord channel ID for bug reports.
wrangler secret put DISCORD_ACCOUNT_DELETION_CHANNEL_ID # When prompted, enter the Discord channel ID for account deletion requests.
wrangler secret put SENTRY_DSN # When prompted, enter the Sentry DSN.

📂 Project Structure​

Here is an overview of the project's structure:

├─ 📁 src/
│ ├── 📄 config.ts # ⚙️ Form types and email settings
│ ├── 📄 email-handler.ts # 📬 Handles emails and forwards to Discord
│ ├── 📄 fetch-handler.ts # 🌐 Handles API requests
│ ├── 📄 helpers.ts # 🛠️ Utilities for validation and email handling
│ ├── 📄 index.ts # 🚀 Worker entry point
│ ├── 📄 schemas.json # 📝 JSON schemas for validation
│ └── 📄 schemas.ts # 🛡️ TypeScript definitions for schemas
├─ 📄 .dev.vars # 🌍 Local environment variables
├─ 📄 .gitignore # 🚫 Ignored files
├─ 📄 package.json # 📦 Dependencies and scripts
├─ 📄 README.md # 📖 Documentation
├─ 📄 tsconfig.json # ⚙️ TypeScript config
├─ 📄 tsconfig.schemas.json # ⚙️ Config for schema generation
├─ 📄 types.d.ts # 🛡️ Cloudflare types
├─ 📄 wrangler.jsonc # ☁️ Cloudflare Worker config
└─ 📄 yarn.lock # 🔒 Dependency lock file

📜 API Endpoints​

The worker exposes the following API endpoints:

1. POST /api/v1/contact/message​

Send a general contact message to contact@cosmy.app and forward it to the Discord contact channel.

Request Body​

{
"name": "John Doe",
"email": "john.doe@example.com",
"message": "Hello, I have a question about your service."
}

2. POST /api/v1/contact/bug-report​

Send a bug report to bug-report@cosmy.app and forward it to the Discord bug report channel.

Request Body​

{
"name": "Jane Doe",
"email": "jane.doe@example.com",
"title": "App crashes on login",
"severity": "high",
"stepsToReproduce": "1. Open the app\n2. Enter credentials\n3. Press login",
"message": "The app crashes with a 500 error."
}

Attachments​

You can include up to 10 attachments (max 1MB each) in the request.

Note: This endpoint supports attachments. You can include up to 10 attachments (max 1MB each) in the request. The payload should be a multipart/form-data request.


3. POST /api/v1/contact/account-deletion​

Send an account deletion request to account-deletion@cosmy.app and forward it to the Discord account deletion channel.

Request Body​

{
"name": "John Doe",
"email": "john.doe@example.com"
}

📩 Email endpoints​

The worker listens for incoming emails at the following addresses:

Email AddressDescription
contact@cosmy.appGeneral contact messages.
bug-report@cosmy.appBug reports.
account-deletion@cosmy.appAccount deletion requests.

For each email address, the worker will parse the incoming email and forward it to the appropriate Discord channel and to the email address specified in the environment variable FORWARD_EMAIL_ADRESS.

🛡️ Validation​

The worker validates incoming requests using JSON schemas defined in the schemas.json file. If validation fails, the worker responds with a 400 Bad Request following the Cosmy response format.

For attachment validation, the errors look like this:

{
"status": "error",
"message": "Attachments are not allowed for this form",
"code": "CONTACT/ATTACHMENT_NOT_ALLOWED"
}
{
"status": "error",
"message": "The attachment is too large",
"code": "CONTACT/ATTACHMENT_TOO_LARGE",
"details": {
"fileName": "4mb.jpg",
"fileSize": 4200000,
"maxSize": 1048576
}
}
{
"status": "error",
"message": "The attachment limit has been exceeded",
"code": "CONTACT/ATTACHMENT_LIMIT_EXCEEDED",
"details": {
"fileCount": 11,
"maxFiles": 10
}
}

📦 Dependencies​

The worker uses Yarn for dependency management. The dependencies are listed in the package.json file.

Main Dependencies​

  • @cfworker/json-schema: For JSON schema validation.
  • postal-mime: For parsing incoming emails.
  • discord-api-types: For interacting with the Discord API.
  • hono: For building the API endpoints.
  • @hono/sentry: For Sentry integration.
  • @cosmy/shared: For shared types and DTOs.

Development Dependencies​

  • wrangler: For deploying and managing Cloudflare Workers.
  • ts-json-schema-generator: For generating JSON schemas from the TypeScript DTO from @cosmy/shared.
  • @sentry/cli: For managing Sentry source maps.
  • eslint: For linting and code quality checks.