",
+ "headers": {
+ "cc": "pennie@a1base.com",
+ "bcc": "pasha@a1base.com"
+ }
+}'
+```
\ No newline at end of file
diff --git a/mint.json b/mint.json
index 4b255df..be8c365 100644
--- a/mint.json
+++ b/mint.json
@@ -92,8 +92,12 @@
"pages": ["api-reference/cron-jobs/introduction"]
},
{
- "group": "Email (Beta)",
- "pages": ["api-reference/email/introduction"]
+ "group": "Email",
+ "pages": [
+ "api-reference/email/create-email",
+ "api-reference/email/receiving-email",
+ "api-reference/email/sending-email"
+ ]
}
],
"footerSocials": {
From 2af4f849365273a3c9461d268a056bdebe92b7fa Mon Sep 17 00:00:00 2001
From: pasha rayan
Date: Fri, 21 Mar 2025 16:04:28 -0700
Subject: [PATCH 18/40] pushing fix
---
cron-jobs.mdx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cron-jobs.mdx b/cron-jobs.mdx
index 2218ed2..05db7d8 100644
--- a/cron-jobs.mdx
+++ b/cron-jobs.mdx
@@ -80,7 +80,7 @@ Here are some practical examples of how to configure cron jobs for different AI
### Example 1: Daily Customer Check-in Bot
-**Use Case**: Send a morning check-in message to all users asking about their day
+**Use Case**: Send a morning check-in message to all users asking about their day.
**Configuration**:
- **Endpoint URL**: `https://your-api.example.com/send-daily-checkin`
From 941ba85bb12026cbfb4698e69bae4ee5d19171c5 Mon Sep 17 00:00:00 2001
From: pasha rayan
Date: Mon, 24 Mar 2025 22:01:24 -1000
Subject: [PATCH 19/40] minfliy fix
---
mint.json | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/mint.json b/mint.json
index be8c365..063f9a4 100644
--- a/mint.json
+++ b/mint.json
@@ -32,11 +32,7 @@
}
],
"anchors": [
- {
- "name": "Documentation",
- "icon": "book-open-cover",
- "url": "https://docs.a1base.com"
- },
+
{
"name": "Support",
"icon": "envelope",
From d5055cdfcf6df78cef0fca04d19d7ed6f4494864 Mon Sep 17 00:00:00 2001
From: Pennie
Date: Tue, 25 Mar 2025 06:32:19 -0700
Subject: [PATCH 20/40] fixed docs error
---
api-reference/endpoint/create.mdx | 11 -----------
api-reference/openapi.json | 30 ------------------------------
2 files changed, 41 deletions(-)
diff --git a/api-reference/endpoint/create.mdx b/api-reference/endpoint/create.mdx
index 92a472e..9a29411 100644
--- a/api-reference/endpoint/create.mdx
+++ b/api-reference/endpoint/create.mdx
@@ -9,17 +9,6 @@ openapi: "POST /messages/individual/{accountId}/send"
* **Individual Chats**: A user must initiate the conversation with the agent's phone number before the agent can send messages.
----
-title: "Create Messages"
-openapi: "POST /messages/individual/{accountId}/send"
----
-
-
-
----
-title: "Create Messages"
-openapi: "POST /messages/individual/{accountId}/send"
----
**NPM Package**
diff --git a/api-reference/openapi.json b/api-reference/openapi.json
index 8ed1f3e..c455f69 100644
--- a/api-reference/openapi.json
+++ b/api-reference/openapi.json
@@ -95,12 +95,6 @@
"schema": {
"type": "object",
"properties": {
- "messageId": {
- "type": "string"
- },
- "accountId": {
- "type": "string"
- },
"to": {
"type": "string"
},
@@ -118,12 +112,6 @@
"type": "string",
"format": "date-time"
},
- "direction": {
- "type": "string"
- },
- "attachment_uri": {
- "type": "string"
- }
}
}
}
@@ -214,12 +202,6 @@
"schema": {
"type": "object",
"properties": {
- "messageId": {
- "type": "string"
- },
- "accountId": {
- "type": "string"
- },
"chat_group_id": {
"type": "string"
},
@@ -236,18 +218,6 @@
"date_created": {
"type": "string",
"format": "date-time"
- },
- "direction": {
- "type": "string"
- },
- "attachment_uri": {
- "type": "string"
- },
- "participants": {
- "type": "array",
- "items": {
- "type": "string"
- }
}
}
}
From 50bb20b4f5b0e12af077e462b75c930c460465d0 Mon Sep 17 00:00:00 2001
From: Pennie
Date: Fri, 28 Mar 2025 17:01:15 -0700
Subject: [PATCH 21/40] updated docs for multimedia
---
api-reference/endpoint/create.mdx | 89 ++++++++++++++++++++++++-
api-reference/group-messages/create.mdx | 77 ++++++++++++++++++++-
2 files changed, 164 insertions(+), 2 deletions(-)
diff --git a/api-reference/endpoint/create.mdx b/api-reference/endpoint/create.mdx
index 9a29411..8525e8a 100644
--- a/api-reference/endpoint/create.mdx
+++ b/api-reference/endpoint/create.mdx
@@ -12,4 +12,91 @@ openapi: "POST /messages/individual/{accountId}/send"
**NPM Package**
-[**https://www.npmjs.com/package/a1base-api**](https://www.npmjs.com/package/a1base-api)
\ No newline at end of file
+[**https://www.npmjs.com/package/a1base-api**](https://www.npmjs.com/package/a1base-api)
+
+### Sending Text Messages
+
+```json
+POST /individual/{accountId}/send
+Headers:
+ x-api-key: your_api_key
+ x-api-secret: your_api_secret
+ Content-Type: application/json
+
+Body:
+{
+ "from": "+14155552671",
+ "to": "+14155551234",
+ "service": "whatsapp",
+ "message": "Hello, this is a test message!"
+}
+```
+
+### Sending Media Messages
+
+#### Individual Media Message
+To send an image to an individual:
+
+```json
+POST /individual/{accountId}/send
+Headers:
+ x-api-key: your_api_key
+ x-api-secret: your_api_secret
+ Content-Type: application/json
+
+Body:
+{
+ "from": "+14155552671",
+ "to": "+14155551234",
+ "service": "whatsapp",
+ "message_type": "media",
+ "media_url": "https://example.com/images/sample.jpg",
+ "media_type": "image",
+ "caption": "Check out this image!"
+}
+```
+
+### Using the Universal Endpoint
+You can also use the newer universal endpoint for both text and media messages:
+
+```json
+POST /send/{accountId}
+Headers:
+ x-api-key: your_api_key
+ x-api-secret: your_api_secret
+ Content-Type: application/json
+
+Body:
+{
+ "from": "+14155552671",
+ "to": "+14155551234",
+ "service": "whatsapp",
+ "type": "individual",
+ "content": {
+ "type": "media",
+ "media_url": "https://example.com/videos/demo.mp4",
+ "media_type": "video",
+ "caption": "Product demo video"
+ }
+}
+```
+
+### Supported Media Types
+The API supports these media types:
+
+* **image**: For images (JPG, PNG, etc.)
+* **video**: For video files
+* **audio**: For audio files
+* **document**: For documents (PDF, DOC, etc.)
+
+### Important Notes
+* The `media_url` must be a publicly accessible URL where the media file can be downloaded.
+* The `caption` field is optional. You can send media without a caption if desired.
+* Make sure your media files are in formats supported by WhatsApp.
+* There are size limits for different media types:
+ * Images: Generally up to 5MB
+ * Videos: Generally up to 16MB
+ * Audio: Generally up to 16MB
+ * Documents: Generally up to 100MB
+* The `from` number must be a WhatsApp-enabled number that your account has permission to use.
+* The `to` number for individual messages must be a valid WhatsApp number.
\ No newline at end of file
diff --git a/api-reference/group-messages/create.mdx b/api-reference/group-messages/create.mdx
index a7e35f1..120fdc2 100644
--- a/api-reference/group-messages/create.mdx
+++ b/api-reference/group-messages/create.mdx
@@ -9,4 +9,79 @@ openapi: "POST /messages/group/{accountId}/send"
* You'll need the thread\_id of the group chat to send messages. This is provided when you create a group or in webhook events.
-### Example Request
\ No newline at end of file
+### Example Request
+
+#### Text Message to Group
+
+```json
+POST /group/{accountId}/send
+Headers:
+ x-api-key: your_api_key
+ x-api-secret: your_api_secret
+ Content-Type: application/json
+
+Body:
+{
+ "from": "+14155552671",
+ "thread_id": "group-thread-id-123",
+ "service": "whatsapp",
+ "message": "Hello everyone in the group!"
+}
+```
+
+#### Media Message to Group
+To send a document to a group:
+
+```json
+POST /group/{accountId}/send
+Headers:
+ x-api-key: your_api_key
+ x-api-secret: your_api_secret
+ Content-Type: application/json
+
+Body:
+{
+ "from": "+14155552671",
+ "thread_id": "group-thread-id-123",
+ "service": "whatsapp",
+ "message_type": "media",
+ "media_url": "https://example.com/documents/report.pdf",
+ "media_type": "document",
+ "caption": "Here's the quarterly report"
+}
+```
+
+### Using the Universal Endpoint for Groups
+You can also use the newer universal endpoint for group messages:
+
+```json
+POST /send/{accountId}
+Headers:
+ x-api-key: your_api_key
+ x-api-secret: your_api_secret
+ Content-Type: application/json
+
+Body:
+{
+ "from": "+14155552671",
+ "thread_id": "group-thread-id-123",
+ "service": "whatsapp",
+ "type": "group",
+ "content": {
+ "type": "media",
+ "media_url": "https://example.com/videos/demo.mp4",
+ "media_type": "video",
+ "caption": "Product demo video for the team"
+ }
+}
+```
+
+### Important Notes for Group Media Messages
+* For group messages, you need a valid `thread_id` instead of a `to` number.
+* The `caption` field is optional. You can send media without a caption if desired.
+* The same media type and size restrictions apply as with individual messages:
+ * Images: Generally up to 5MB
+ * Videos: Generally up to 16MB
+ * Audio: Generally up to 16MB
+ * Documents: Generally up to 100MB
+* The agent must be a member of the group before sending messages.
\ No newline at end of file
From 6a5528dd1e9843a3791ecd4c6d4a4221e31d7c72 Mon Sep 17 00:00:00 2001
From: pasha rayan
Date: Sun, 30 Mar 2025 13:26:54 -0700
Subject: [PATCH 22/40] added webhook docs
---
mint.json | 6 +-
troubleshooting.mdx | 29 +++
troubleshooting/local-testing-with-ngrok.mdx | 132 ++++++++++++
troubleshooting/webhook-issues.mdx | 216 +++++++++++++++++++
4 files changed, 382 insertions(+), 1 deletion(-)
create mode 100644 troubleshooting.mdx
create mode 100644 troubleshooting/local-testing-with-ngrok.mdx
create mode 100644 troubleshooting/webhook-issues.mdx
diff --git a/mint.json b/mint.json
index 063f9a4..68cfcf1 100644
--- a/mint.json
+++ b/mint.json
@@ -47,7 +47,11 @@
{
"group": "Features",
- "pages": ["cron-jobs"]
+ "pages": ["cron-jobs", "troubleshooting"]
+ },
+ {
+ "group": "Troubleshooting",
+ "pages": ["troubleshooting/webhook-issues", "troubleshooting/local-testing-with-ngrok"]
},
{
diff --git a/troubleshooting.mdx b/troubleshooting.mdx
new file mode 100644
index 0000000..4aa3e38
--- /dev/null
+++ b/troubleshooting.mdx
@@ -0,0 +1,29 @@
+---
+title: "Troubleshooting"
+description: "Solutions to common issues with A1Base"
+---
+
+This section provides solutions to common issues you might encounter when using A1Base. Select a specific area below to find targeted troubleshooting guidance.
+
+## Common Issues
+
+
+
+ Troubleshoot webhook delivery, timeouts, and configuration problems
+
+
+ Coming soon: Solutions for API connectivity problems
+
+
+
+## Getting Additional Help
+
+If you can't find a solution to your problem in our troubleshooting guides, please reach out to our support team at [pennie@a1base.com](mailto:pennie@a1base.com).
diff --git a/troubleshooting/local-testing-with-ngrok.mdx b/troubleshooting/local-testing-with-ngrok.mdx
new file mode 100644
index 0000000..2bcabe6
--- /dev/null
+++ b/troubleshooting/local-testing-with-ngrok.mdx
@@ -0,0 +1,132 @@
+---
+title: "Local Testing with Ngrok"
+description: "How to use Ngrok to test A1Base webhooks in your local development environment"
+---
+
+# Testing Webhooks Locally with Ngrok
+
+When developing applications that use A1Base webhooks, you'll need a way to receive webhook events on your local development machine. This guide explains how to use Ngrok to create a secure tunnel to your local server.
+
+## What is Ngrok?
+
+Ngrok is a tool that creates secure tunnels from public URLs to your local machine, allowing external services like A1Base to send webhooks to your local development environment.
+
+## Setting Up Ngrok
+
+### Step 1: Install Ngrok
+
+1. Visit [ngrok.com](https://ngrok.com/) and sign up for a free account
+2. Download and install Ngrok for your operating system
+3. Authenticate your Ngrok installation with your auth token:
+
+```bash
+ngrok config add-authtoken YOUR_AUTH_TOKEN
+```
+
+### Step 2: Start Your Local Server
+
+First, make sure your local webhook server is running. For A1Framework or NextJS you'll run "npm run dev". When you do this you'll find out what port
+your app is running on (e.g. localhost:3000).
+
+
+### Step 3: Connect Ngrok to Your Local Port
+
+Once your local server is running, use Ngrok to create a tunnel to your local port:
+
+```bash
+ngrok http 3000
+```
+
+This will start Ngrok and display output similar to:
+
+```
+Session Status online
+Account Your Name (Plan: Free)
+Version 3.3.1
+Region United States (us)
+Latency 24ms
+Web Interface http://127.0.0.1:4040
+Forwarding https://a1b2-203-0-113-42.ngrok-free.app -> http://localhost:3000
+```
+
+The `https://a1b2-203-0-113-42.ngrok-free.app` URL is your public webhook URL that you'll configure in the A1Base dashboard.
+
+
+ Ngrok free tier URLs change every time you restart Ngrok. You get one free consistent URL with Ngrok. For consistent URLs, consider upgrading to a paid plan.
+
+
+## Configuring A1Base with Your Ngrok URL
+
+To configure your Ngrok URL in the A1Base dashboard, follow these steps:
+
+1. Log in to the A1Base dashboard at https://www.a1base.com/dashboard/phone-numbers.
+2. Navigate to the "Phone Numbers" section and locate the phone number you wish to configure (e.g., 14155356190).
+3. In the "Webhook URL" column for that phone number, enter your Ngrok HTTPS URL combined with your endpoint path (e.g., https://a1b2-203-0-113-42.ngrok-free.app/whatsapp/incoming).
+4. Click the "Save" button next to the webhook URL field to apply the changes.
+ Refer to the A1Base dashboard screenshot for a visual guide on configuring webhook URLs: [Phone Numbers Dashboard](link-to-screenshot).
+
+## Development vs. Production Phone Numbers
+
+
+ For a smooth development workflow, we recommend purchasing two separate phone numbers on A1Base: one for development and one for production.
+
+
+### Why Use Separate Phone Numbers?
+
+Using separate phone numbers for development and production environments offers several advantages:
+
+1. **Isolated Testing**: Test new features without affecting your production users
+2. **Prevent Webhook Conflicts**: Avoid routing production messages to your development environment
+3. **Easier Debugging**: Clearly distinguish between development and production traffic
+4. **Safer Experimentation**: Experiment with new features risk-free
+
+### Setting Up Development and Production Numbers
+
+1. **Purchase Two Phone Numbers**: In your A1Base dashboard, purchase two separate phone numbers
+2. **Label Your Numbers**: Clearly label one as "Development" and one as "Production"
+3. **Configure Different Webhooks**:
+ - Development number: Point to your Ngrok URL
+ - Production number: Point to your production server URL
+4. **Use Environment Variables**: In your code, use environment variables to determine which phone number to use:
+
+```javascript
+// Example of using different phone numbers based on environment
+const phoneNumber = process.env.NODE_ENV === 'production'
+ ? process.env.A1BASE_PRODUCTION_PHONE
+ : process.env.A1BASE_DEVELOPMENT_PHONE;
+
+// Use the appropriate phone number for outgoing messages
+a1base.sendMessage({
+ to: recipientNumber,
+ from: phoneNumber,
+ message: "Hello from A1Base!"
+});
+```
+
+## Monitoring Webhook Traffic
+
+Ngrok provides a web interface at `http://127.0.0.1:4040` where you can inspect:
+
+- All incoming webhook requests
+- Request and response headers
+- Request and response bodies
+- Timing information
+
+This interface is invaluable for debugging webhook issues during development.
+
+## Best Practices for Local Testing
+
+1. **Keep Ngrok Running**: Maintain your Ngrok session while testing to avoid URL changes
+2. **Log All Webhook Data**: Implement comprehensive logging in your development environment
+3. **Simulate Different Scenarios**: Test various message types and edge cases
+4. **Verify Webhook Signatures**: Even in development, validate webhook signatures if available
+5. **Test Error Handling**: Ensure your application handles webhook failures gracefully
+
+## Limitations and Considerations
+
+- **Free Tier Restrictions**: Ngrok's free tier has limitations on connections and features
+- **URL Changes**: Free Ngrok URLs change each time you restart Ngrok
+- **Latency**: There may be slight additional latency when using Ngrok
+- **Security**: Be cautious about sensitive data passing through your development environment
+
+By following this guide, you can effectively test A1Base webhooks in your local development environment using Ngrok, ensuring a smooth transition to production.
diff --git a/troubleshooting/webhook-issues.mdx b/troubleshooting/webhook-issues.mdx
new file mode 100644
index 0000000..679cc7b
--- /dev/null
+++ b/troubleshooting/webhook-issues.mdx
@@ -0,0 +1,216 @@
+---
+title: "Webhook Issues"
+description: "Troubleshooting common webhook problems and how to resolve them"
+---
+
+
+
+## Common Webhook Problems
+
+
+
+ ### Webhook Not Receiving Messages
+
+ If your webhook isn't receiving expected messages, check the following:
+
+ 1. **Verify the webhook URL** in your A1Base dashboard is correct and publicly accessible.
+ 2. **Use the right URL route** - sometimes people forget to add the specific route of the website (e.g. /apu/receive-message)
+ 2. **Check your server logs** for any incoming requests that might be failing
+ 3. **Ensure your server is accepting POST requests** with JSON content
+ 4. **Confirm your firewall settings** allow incoming webhook requests
+ 5. **Verify SSL/TLS certificates** if you're using HTTPS (required for production)
+
+ **Quick Test:**
+ ```bash
+ # Test if your endpoint is publicly accessible
+ curl -X POST https://your-webhook-url.com/path \
+ -H "Content-Type: application/json" \
+ -d '{"test":"payload"}'
+ ```
+
+
+
+ ### Vercel Timeout Issues
+
+ Vercel has a default function timeout of 10 seconds for hobby plans and 60 seconds for pro plans. If your webhook processing takes longer, you might experience timeouts.
+
+ **Solutions:**
+
+ 1. **Acknowledge webhooks immediately** and process them asynchronously:
+
+ ```typescript
+ app.post('/whatsapp/incoming', async (req, res) => {
+ // Immediately acknowledge receipt to prevent timeout
+ res.status(200).json({ success: true });
+
+ // Then process the webhook asynchronously
+ try {
+ await processWebhookAsync(req.body);
+ } catch (error) {
+ console.error('Error processing webhook:', error);
+ }
+ });
+
+ async function processWebhookAsync(data) {
+ // Your time-consuming processing logic here
+ }
+ ```
+
+ 2. **Upgrade to Vercel Pro** for longer function execution times
+
+ 3. **Use a serverless queue** like AWS SQS or a background job processor
+
+ 4. **Consider moving webhook processing** to a different hosting provider without strict timeout limits
+
+
+
+ ### Using the Right Endpoint
+
+ Using incorrect webhook endpoints is a common issue that can prevent proper message delivery.
+
+ **Best Practices:**
+
+ 1. **Use dedicated endpoints** for different services (e.g., `/webhooks/a1base` instead of a generic `/webhook`)
+
+ 2. **Include version information** in your webhook paths (e.g., `/api/v1/webhooks/a1base`)
+
+ 3. **Verify the correct endpoint format** in the A1Base dashboard:
+ - Must be a complete URL including `https://`
+ - Must point to a publicly accessible server
+ - Should not include query parameters unless necessary
+
+ **Example of proper endpoint structure:**
+ ```
+ https://your-domain.com/api/webhooks/a1base
+ ```
+
+ **Not:**
+ ```
+ your-domain.com/webhooks
+ /api/webhooks
+ ```
+
+
+
+ ### Authentication and Security Issues
+
+ Securing your webhook is essential to prevent unauthorized access.
+
+ **Security Best Practices:**
+
+ 1. **Validate webhook signatures** if provided by A1Base
+
+ 2. **Implement IP whitelisting** if A1Base provides a static IP range
+
+ 3. **Use a webhook secret** to validate authentic requests:
+
+ ```typescript
+ import crypto from 'crypto';
+
+ app.post('/whatsapp/incoming', (req, res) => {
+ const signature = req.headers['x-webhook-signature'];
+ const payload = JSON.stringify(req.body);
+ const secret = process.env.WEBHOOK_SECRET;
+
+ const expectedSignature = crypto
+ .createHmac('sha256', secret)
+ .update(payload)
+ .digest('hex');
+
+ if (signature !== expectedSignature) {
+ return res.status(403).send('Invalid signature');
+ }
+
+ // Process valid webhook
+ res.status(200).send('Success');
+ });
+ ```
+
+ 4. **Don't expose sensitive information** in your webhook response
+
+
+
+ ### Network and Infrastructure Problems
+
+ Network issues can cause intermittent webhook failures.
+
+ **Troubleshooting Steps:**
+
+ 1. **Check your server's connectivity** and ensure it has stable internet access
+
+ 2. **Monitor server load** as high CPU or memory usage can cause webhook processing delays
+
+ 3. **Implement retry logic** in your webhook handler:
+
+ ```typescript
+ async function processWebhookWithRetry(data, maxRetries = 3) {
+ let retries = 0;
+
+ while (retries < maxRetries) {
+ try {
+ await processWebhook(data);
+ return; // Success
+ } catch (error) {
+ retries++;
+ console.error(`Attempt ${retries} failed:`, error);
+
+ if (retries >= maxRetries) {
+ console.error('Max retries reached. Giving up.');
+ // Consider logging to a monitoring service or error tracker
+ break;
+ }
+
+ // Exponential backoff
+ await new Promise(r => setTimeout(r, 1000 * Math.pow(2, retries)));
+ }
+ }
+ }
+ ```
+
+ 4. **Use a monitoring service** like Sentry, New Relic, or Datadog to track webhook reliability
+
+
+
+ ### Debugging Webhook Payloads
+
+ When webhooks arrive but processing fails, payload issues might be the cause.
+
+ **Debugging Techniques:**
+
+ 1. **Log the complete webhook payload** during development:
+
+ ```typescript
+ app.post('/whatsapp/incoming', (req, res) => {
+ console.log('Webhook received:', JSON.stringify(req.body, null, 2));
+
+ // Implement validation to ensure all required fields are present
+ const { thread_id, message_id, message_content } = req.body;
+
+ if (!thread_id || !message_id || !message_content) {
+ console.error('Missing required fields in webhook payload');
+ return res.status(400).send('Invalid payload');
+ }
+
+ // Process valid webhook
+ res.status(200).send('Success');
+ });
+ ```
+
+ 2. **Implement schema validation** using libraries like Joi or Zod
+
+ 3. **Create a webhook simulator** for testing your handler with various payload types
+
+ 4. **Set up alerting** for malformed payloads to catch API changes early
+
+
+
+## Advanced Troubleshooting
+
+For persistent webhook issues, consider implementing these advanced solutions:
+
+1. **Webhook queue system** to handle high volumes of incoming webhooks
+2. **Dead letter queue** for failed webhook processing attempts
+3. **Circuit breaker pattern** to prevent cascading failures when dependent services are down
+4. **Comprehensive logging and monitoring** to track webhook reliability over time
+
+If you continue experiencing webhook issues after trying these solutions, please contact our support team at [pennie@a1base.com](mailto:pennie@a1base.com) with details about your specific problem.
From b38e93a21cf2971dbb2add0a9834fa9a7fee23c1 Mon Sep 17 00:00:00 2001
From: Pennie
Date: Mon, 31 Mar 2025 01:32:47 -0700
Subject: [PATCH 23/40] subdomain guide
---
.../email/setup-custom-subdomain.mdx | 122 ++++++++++++++++++
mint.json | 5 +-
2 files changed, 125 insertions(+), 2 deletions(-)
create mode 100644 api-reference/email/setup-custom-subdomain.mdx
diff --git a/api-reference/email/setup-custom-subdomain.mdx b/api-reference/email/setup-custom-subdomain.mdx
new file mode 100644
index 0000000..04c607e
--- /dev/null
+++ b/api-reference/email/setup-custom-subdomain.mdx
@@ -0,0 +1,122 @@
+Setup your own custom subdomain on A1Mail to send and receive emails.
+
+
+
+
+# π Guide for **Namecheap**
+
+### 1. Log into your Namecheap dashboard
+
+- Go to [https://www.namecheap.com](https://www.namecheap.com/)
+- Navigate to **Domain List > Manage** next to `example.com`
+
+ 
+
+
+---
+
+### 2. Add an A Record for `mail.example.com`
+
+> This points mail.example.com to your server's IP address.
+>
+- Go to the **Advanced DNS** tab
+- Click **Add New Record**
+ - Choose `A Record`
+ - **Host**: `mail`
+ - **Value**: `110.232.112.135`
+ - **TTL**: Automatic
+- Email the subdomain to `founders@a1base.com`
+
+---
+
+### 3. Add an MX Record for email delivery
+
+> This tells other mail servers where to deliver email for example.com.
+>
+- Still in **Advanced DNS,** scroll down to MAIL SETTINGS
+- Next to "MAIL SETTINGS", select the option "Custom MX"
+- Add a new record:
+ - **Type**: `MX Record`
+ - **Host**: `@`
+ - **Value**: `mail.a101.bot`
+ - **Priority**: `10`
+- **TTL**: Automatic
+
+---
+
+### 4. Add SPF, DKIM, and DMARC
+
+> These records are for deliverability and anti-spam
+>
+
+**SPF Record**
+
+```jsx
+Type: TXT
+Host: mail
+Value: v=spf1 ip4:110.232.112.135 a:mail.a101.bot ~all
+TTL: Automatic
+```
+
+**DMARC Record**
+
+```jsx
+Type: TXT
+Host: _dmarc
+Value: v=DMARC1; p=none; rua=mailto:postmaster@subdomain.yourdomain.com
+TTL: Automatic
+```
+
+**DKIM Record**
+
+```jsx
+Type: TXT
+Host: salarycatdkim._domainkey
+Value: v=DKIM1; h=sha256; k=rsa; t=y; p=asdfgasdfasdf...
+TTL: Automatic
+```
+
+### Summary:
+
+> You should have these records in your Advanced DNS Settings
+>
+
+| **Record** | **Type** | **Host** | **Value** | **Priority** | **TTL** |
+| --- | --- | --- | --- | --- | --- |
+| SPF | TXT | mail | `v=spf1 ip4:110.232.112.135 a:mail.a101.bot ~all` | β | Automatic |
+| DMARC | TXT | _dmarc | `v=DMARC1; p=none; rua=mailto:postmaster@subdomain.yourdomain.com` | β | Automatic |
+| DKIM | TXT | the domain key we've provided e.g `subdomain._domainkey` | The value we've provided - this will look like: `v=DKIM1; h=sha256; k=rsa; t=y; p=AsDSDGGJGKA...` | β | Automatic |
+| MX | MX | @ | `mail.a101.bot` | 1 | Automatic |
+
+---
+
+# **Cloudflare**
+
+- No need for a trailing `.` in hostnames.
+- You only need to fill out **Type**, **Name** (aka Host), and **Content** (aka Value).
+- **TTL** can stay "Auto".
+
+| **Record** | **Type** | **Name (Host)** | **Content (Value)** | **Priority** | **TTL** |
+| --- | --- | --- | --- | --- | --- |
+| SPF | TXT | mail | `v=spf1 ip4:110.232.112.135 a:mail.a101.bot ~all` | β | Auto |
+| DMARC | TXT | _dmarc | `v=DMARC1; p=none; rua=mailto:postmaster.a101.bot` | β | Auto |
+| DKIM | TXT | the domain key we've provided e.g `subdomain._domainkey` | The value we've provided - this will look like: `v=DKIM1; h=sha256; k=rsa; t=y; p=AsDSDGGJGKA...` | β | Auto |
+| MX | MX | @ | `mail.a101.bot` | 1 | Auto |
+
+> π Disable the proxy (orange cloud) for mail.a101.bot and _domainkey TXT records. These should be DNS only.
diff --git a/mint.json b/mint.json
index 68cfcf1..22d1606 100644
--- a/mint.json
+++ b/mint.json
@@ -96,8 +96,9 @@
"pages": [
"api-reference/email/create-email",
"api-reference/email/receiving-email",
- "api-reference/email/sending-email"
- ]
+ "api-reference/email/sending-email",
+ "api-reference/email/setup-custom-subdomain"
+ ]
}
],
"footerSocials": {
From 47e959ba774704c299d0848e052b9ab346be6b52 Mon Sep 17 00:00:00 2001
From: Pennie
Date: Mon, 31 Mar 2025 12:56:04 -0700
Subject: [PATCH 24/40] mail docs
---
.../email/setup-custom-mail-domain.mdx | 255 ++++++++++++++++++
.../email/setup-custom-subdomain.mdx | 122 ---------
mint.json | 2 +-
3 files changed, 256 insertions(+), 123 deletions(-)
create mode 100644 api-reference/email/setup-custom-mail-domain.mdx
delete mode 100644 api-reference/email/setup-custom-subdomain.mdx
diff --git a/api-reference/email/setup-custom-mail-domain.mdx b/api-reference/email/setup-custom-mail-domain.mdx
new file mode 100644
index 0000000..40fd322
--- /dev/null
+++ b/api-reference/email/setup-custom-mail-domain.mdx
@@ -0,0 +1,255 @@
+---
+title: 'Setup your own custom mail domain on A1Mail'
+description: 'Instead of using @a1send.com or @a101.bot, setup your own custom email addresses to send and receive emails.'
+---
+
+### In this guide you will:
+
+### 1. Create a subdomain on your hosting provider for mail:
+- Log onto your hosting provider (Cloudflare, Namecheap, etc.)
+- Add a new subdomain called `mail` e.g `mail.yourdomain.com`
+
+### 2. Connect your subdomain to A1Mail
+- Go to the [email dashboard](https://www.a1base.com/dashboard/email-addresses)
+- Add in your new subdomain to generate DKIM keys
+
+### 3. Update your DNS records
+- Update the MX record
+- Add SPF, DKIM, and DMARC records for deliverability and anti-spam
+
+### 4. Warm up your new mail domain
+- Create a new email with your subdomain and send some emails to your new address
+- Start sending emails using the A1Base API
+
+
+
+### Shortcuts:
+
+- [Go to the guide for **Namecheap**](#-guide-for-namecheap)
+- [Go to the guide for **Cloudflare**](#-guide-for-cloudflare)
+
+---
+
+# **Namecheap Guide**
+
+#### 1. Log into your Namecheap dashboard
+
+- Go to [https://www.namecheap.com](https://www.namecheap.com/)
+- Navigate to **Domain List > Manage** next to `yourdomain.com`
+- Navigate to **Advanced DNS**
+
+
+
+---
+
+#### 2. Add an A Record for `mail.example.com`
+> This points your new mail subdomain, mail.example.com to the server's IP address.
+>
+- In the **Advanced DNS** tab click **Add New Record**
+- Select `A Record` and enter the following:
+```jsx
+ Host: `mail`
+ Value: `110.232.112.135`
+ TTL: Automatic
+```
+
+---
+
+#### 3. Add an MX Record for email delivery
+
+> This tells other mail servers where to deliver email for example.com.
+>
+- Still in **Advanced DNS,** scroll down to MAIL SETTINGS
+- Next to "MAIL SETTINGS", select the option "Custom MX" and enter the following:
+```jsx
+ Type: `MX Record`
+ Host: `@`
+ Value: `mail.a101.bot`
+ Priority: `10`
+ TTL: Automatic
+```
+
+---
+
+#### 4. Update A1Mail Dashboard and get your DKIM values
+
+- Go to your [A1Mail dashboard](https://www.a1base.com/dashboard/email-addresses) and navigate to the **Custom Mail Domain** section
+- Enter your new mail subdomain to get your DKIM keys
+
+#### 5. Add the TXT Records for SPF, DKIM, and DMARC
+
+> These records are for deliverability and anti-spam
+>
+- Go back to the **Advanced DNS** tab click **Add New Record**, select `TXT RECORD` for the following:
+
+**SPF Record**
+```jsx
+Type: TXT
+Host: mail
+Value: v=spf1 ip4:110.232.112.135 a:mail.a101.bot ~all
+TTL: Automatic
+```
+
+**DMARC Record**
+
+```jsx
+Type: TXT
+Host: _dmarc
+Value: v=DMARC1; p=none; rua=mailto:postmaster@subdomain.yourdomain.com
+TTL: Automatic
+```
+
+**DKIM Record**
+- Copy the host and value from the A1Mail dashboard
+
+```jsx
+Type: TXT
+Host:
+Value: e.g v=DKIM1; h=sha256; k=rsa; t=y; p=asdfgasdfasdf...
+TTL: Automatic
+```
+
+### Summary:
+
+> You should have these records in your Advanced DNS Settings
+>
+
+| **Record** | **Type** | **Host** | **Value** | **Priority** | **TTL** |
+| --- | --- | --- | --- | --- | --- |
+| SPF | TXT | mail | `v=spf1 ip4:110.232.112.135 a:mail.a101.bot ~all` | β | Automatic |
+| DMARC | TXT | _dmarc | `v=DMARC1; p=none; rua=mailto:postmaster@subdomain.yourdomain.com` | β | Automatic |
+| DKIM | TXT | the domain key we've provided e.g `subdomain._domainkey` | The value we've provided - this will look like: `v=DKIM1; h=sha256; k=rsa; t=y; p=AsDSDGGJGKA...` | β | Automatic |
+| MX | MX | @ | `mail.a101.bot` | 1 | Automatic |
+
+
+---
+
+
+# **Cloudflare Guide**
+
+#### 1. Log into your Cloudflare dashboard
+
+- Go to [https://dash.cloudflare.com](https://dash.cloudflare.com/)
+- Select your domain from the list
+- Navigate to the **DNS** tab
+
+
+---
+
+#### 2. Add an A Record for `mail.yourdomain.com`
+
+> This points your new mail subdomain to the server's IP address.
+>
+- Click **Add record**
+- Select `A` for the Type and enter the following:
+```jsx
+ Type: A
+ Name: mail
+ IPv4 address: 110.232.112.135
+ TTL: Auto
+ Proxy status: DNS only (gray cloud)
+```
+
+---
+
+#### 3. Add an MX Record for email delivery
+
+> This tells other mail servers where to deliver email for your domain.
+>
+- Click **Add record** again
+- Select `MX` for the Type and enter the following:
+```jsx
+ Type: MX
+ Name: @ (represents the root domain)
+ Mail server: mail.a101.bot
+ Priority: 10
+ TTL: Auto
+```
+
+---
+
+#### 4. Update A1Mail Dashboard and get your DKIM values
+
+- Go to your [A1Mail dashboard](https://www.a1base.com/dashboard/email-addresses) and navigate to the **Custom Mail Domain** section
+- Enter your new mail subdomain to get your DKIM keys
+
+#### 5. Add the TXT Records for SPF, DKIM, and DMARC
+
+> These records are for deliverability and anti-spam
+>
+- Click **Add record** for each of the following TXT records:
+
+**SPF Record**
+```jsx
+ Type: TXT
+ Name: mail
+ Content: v=spf1 ip4:110.232.112.135 a:mail.a101.bot ~all
+ TTL: Auto
+```
+
+**DMARC Record**
+```jsx
+ Type: TXT
+ Name: _dmarc
+ Content: v=DMARC1; p=none; rua=mailto:postmaster@yourdomain.com
+ TTL: Auto
+```
+
+**DKIM Record**
+- Copy the host and value from the A1Mail dashboard
+
+```jsx
+ Type: TXT
+ Name: (without your domain)
+ Content:
+ TTL: Auto
+ Proxy status: DNS only (gray cloud)
+```
+
+### Summary:
+
+> You should have these records in your Cloudflare DNS settings
+>
+
+| **Record** | **Type** | **Name (Host)** | **Content (Value)** | **Priority** | **TTL** | **Proxy Status** |
+| --- | --- | --- | --- | --- | --- | --- |
+| A | A | mail | `110.232.112.135` | β | Auto | DNS only |
+| SPF | TXT | mail | `v=spf1 ip4:110.232.112.135 a:mail.a101.bot ~all` | β | Auto | DNS only |
+| DMARC | TXT | _dmarc | `v=DMARC1; p=none; rua=mailto:postmaster@yourdomain.com` | β | Auto | DNS only |
+| DKIM | TXT | from dashboard | from dashboard | β | Auto | DNS only |
+| MX | MX | @ | `mail.a101.bot` | 10 | Auto | DNS only |
+
+> π **Important**: Make sure all email-related records have the proxy disabled (gray cloud). Email services require direct DNS resolution to function properly.
+
+---
+
+# **Warm up your new mail domain**
+
+After setting up your custom mail domain, you'll need to properly warm it up to ensure good deliverability. Here's how:
+
+#### 1. Create a new email with your subdomain
+
+- Create an email address using your new subdomain (e.g., `hello@mail.example.com`)
+- Follow our [Creating Email Addresses guide](api-reference/email/create-email) for detailed instructions
+
+#### 2. Send some test emails to your new address
+
+- Send emails from your personal accounts (Gmail, Outlook, etc.) to your new address
+
+> π‘ **Warming up tips**: New email domains need time to build reputation. Start with low volumes (5-10 emails/day) for the first week, then gradually increase. Avoid sending mass emails immediately after setup.
+
+#### 3. Receiving emails
+
+- Check that incoming emails are properly delivered to your new address
+- For more details on receiving and managing emails, see our [Receiving Emails guide](api-reference/email/receiving-email)
+
+#### 4. Send emails using the A1Base API
+
+- Once your domain is properly warmed up, start sending emails programmatically
+- Use the A1Base API to send emails from your custom domain address
+- Follow our [Sending Emails guide](api-reference/email/sending-email) for implementation details
+
+Note:
+- Engage with these emails by replying to them
+- This helps establish a positive sending reputation for your domain
+
diff --git a/api-reference/email/setup-custom-subdomain.mdx b/api-reference/email/setup-custom-subdomain.mdx
deleted file mode 100644
index 04c607e..0000000
--- a/api-reference/email/setup-custom-subdomain.mdx
+++ /dev/null
@@ -1,122 +0,0 @@
-Setup your own custom subdomain on A1Mail to send and receive emails.
-
-
-
-
-# π Guide for **Namecheap**
-
-### 1. Log into your Namecheap dashboard
-
-- Go to [https://www.namecheap.com](https://www.namecheap.com/)
-- Navigate to **Domain List > Manage** next to `example.com`
-
- 
-
-
----
-
-### 2. Add an A Record for `mail.example.com`
-
-> This points mail.example.com to your server's IP address.
->
-- Go to the **Advanced DNS** tab
-- Click **Add New Record**
- - Choose `A Record`
- - **Host**: `mail`
- - **Value**: `110.232.112.135`
- - **TTL**: Automatic
-- Email the subdomain to `founders@a1base.com`
-
----
-
-### 3. Add an MX Record for email delivery
-
-> This tells other mail servers where to deliver email for example.com.
->
-- Still in **Advanced DNS,** scroll down to MAIL SETTINGS
-- Next to "MAIL SETTINGS", select the option "Custom MX"
-- Add a new record:
- - **Type**: `MX Record`
- - **Host**: `@`
- - **Value**: `mail.a101.bot`
- - **Priority**: `10`
-- **TTL**: Automatic
-
----
-
-### 4. Add SPF, DKIM, and DMARC
-
-> These records are for deliverability and anti-spam
->
-
-**SPF Record**
-
-```jsx
-Type: TXT
-Host: mail
-Value: v=spf1 ip4:110.232.112.135 a:mail.a101.bot ~all
-TTL: Automatic
-```
-
-**DMARC Record**
-
-```jsx
-Type: TXT
-Host: _dmarc
-Value: v=DMARC1; p=none; rua=mailto:postmaster@subdomain.yourdomain.com
-TTL: Automatic
-```
-
-**DKIM Record**
-
-```jsx
-Type: TXT
-Host: salarycatdkim._domainkey
-Value: v=DKIM1; h=sha256; k=rsa; t=y; p=asdfgasdfasdf...
-TTL: Automatic
-```
-
-### Summary:
-
-> You should have these records in your Advanced DNS Settings
->
-
-| **Record** | **Type** | **Host** | **Value** | **Priority** | **TTL** |
-| --- | --- | --- | --- | --- | --- |
-| SPF | TXT | mail | `v=spf1 ip4:110.232.112.135 a:mail.a101.bot ~all` | β | Automatic |
-| DMARC | TXT | _dmarc | `v=DMARC1; p=none; rua=mailto:postmaster@subdomain.yourdomain.com` | β | Automatic |
-| DKIM | TXT | the domain key we've provided e.g `subdomain._domainkey` | The value we've provided - this will look like: `v=DKIM1; h=sha256; k=rsa; t=y; p=AsDSDGGJGKA...` | β | Automatic |
-| MX | MX | @ | `mail.a101.bot` | 1 | Automatic |
-
----
-
-# **Cloudflare**
-
-- No need for a trailing `.` in hostnames.
-- You only need to fill out **Type**, **Name** (aka Host), and **Content** (aka Value).
-- **TTL** can stay "Auto".
-
-| **Record** | **Type** | **Name (Host)** | **Content (Value)** | **Priority** | **TTL** |
-| --- | --- | --- | --- | --- | --- |
-| SPF | TXT | mail | `v=spf1 ip4:110.232.112.135 a:mail.a101.bot ~all` | β | Auto |
-| DMARC | TXT | _dmarc | `v=DMARC1; p=none; rua=mailto:postmaster.a101.bot` | β | Auto |
-| DKIM | TXT | the domain key we've provided e.g `subdomain._domainkey` | The value we've provided - this will look like: `v=DKIM1; h=sha256; k=rsa; t=y; p=AsDSDGGJGKA...` | β | Auto |
-| MX | MX | @ | `mail.a101.bot` | 1 | Auto |
-
-> π Disable the proxy (orange cloud) for mail.a101.bot and _domainkey TXT records. These should be DNS only.
diff --git a/mint.json b/mint.json
index 22d1606..6ca48fd 100644
--- a/mint.json
+++ b/mint.json
@@ -97,7 +97,7 @@
"api-reference/email/create-email",
"api-reference/email/receiving-email",
"api-reference/email/sending-email",
- "api-reference/email/setup-custom-subdomain"
+ "api-reference/email/setup-custom-mail-domain"
]
}
],
From a7f865111f05aed6ec6869abc0d081e4838ffbe0 Mon Sep 17 00:00:00 2001
From: Pennie
Date: Mon, 31 Mar 2025 13:19:04 -0700
Subject: [PATCH 25/40] cloduflare
---
api-reference/email/setup-custom-mail-domain.mdx | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/api-reference/email/setup-custom-mail-domain.mdx b/api-reference/email/setup-custom-mail-domain.mdx
index 40fd322..90411f8 100644
--- a/api-reference/email/setup-custom-mail-domain.mdx
+++ b/api-reference/email/setup-custom-mail-domain.mdx
@@ -121,6 +121,7 @@ TTL: Automatic
| DKIM | TXT | the domain key we've provided e.g `subdomain._domainkey` | The value we've provided - this will look like: `v=DKIM1; h=sha256; k=rsa; t=y; p=AsDSDGGJGKA...` | β | Automatic |
| MX | MX | @ | `mail.a101.bot` | 1 | Automatic |
+
---
@@ -132,7 +133,9 @@ TTL: Automatic
- Go to [https://dash.cloudflare.com](https://dash.cloudflare.com/)
- Select your domain from the list
- Navigate to the **DNS** tab
-
+
+
+
---
@@ -221,6 +224,8 @@ TTL: Automatic
> π **Important**: Make sure all email-related records have the proxy disabled (gray cloud). Email services require direct DNS resolution to function properly.
+
+
---
# **Warm up your new mail domain**
From e6efa917e916c9a1125867af151371360c3aca86 Mon Sep 17 00:00:00 2001
From: Pennie
Date: Mon, 31 Mar 2025 17:23:03 -0700
Subject: [PATCH 26/40] docs
---
.../index.mdx} | 31 ++++-
.../verify-dns-settings.mdx | 131 ++++++++++++++++++
mint.json | 10 +-
3 files changed, 166 insertions(+), 6 deletions(-)
rename api-reference/{email/setup-custom-mail-domain.mdx => custom-mail-domain/index.mdx} (88%)
create mode 100644 api-reference/custom-mail-domain/verify-dns-settings.mdx
diff --git a/api-reference/email/setup-custom-mail-domain.mdx b/api-reference/custom-mail-domain/index.mdx
similarity index 88%
rename from api-reference/email/setup-custom-mail-domain.mdx
rename to api-reference/custom-mail-domain/index.mdx
index 90411f8..7697890 100644
--- a/api-reference/email/setup-custom-mail-domain.mdx
+++ b/api-reference/custom-mail-domain/index.mdx
@@ -2,6 +2,8 @@
title: 'Setup your own custom mail domain on A1Mail'
description: 'Instead of using @a1send.com or @a101.bot, setup your own custom email addresses to send and receive emails.'
---
+
+β° Estimated setup time: 20min.
### In this guide you will:
@@ -13,9 +15,10 @@ description: 'Instead of using @a1send.com or @a101.bot, setup your own custom e
- Go to the [email dashboard](https://www.a1base.com/dashboard/email-addresses)
- Add in your new subdomain to generate DKIM keys
-### 3. Update your DNS records
+### 3. Update and test your DNS records
- Update the MX record
- Add SPF, DKIM, and DMARC records for deliverability and anti-spam
+- Verify your DNS records are properly configured
### 4. Warm up your new mail domain
- Create a new email with your subdomain and send some emails to your new address
@@ -23,7 +26,7 @@ description: 'Instead of using @a1send.com or @a101.bot, setup your own custom e
-### Shortcuts:
+### Guide Shortcuts:
- [Go to the guide for **Namecheap**](#-guide-for-namecheap)
- [Go to the guide for **Cloudflare**](#-guide-for-cloudflare)
@@ -125,7 +128,6 @@ TTL: Automatic
---
-
# **Cloudflare Guide**
#### 1. Log into your Cloudflare dashboard
@@ -226,6 +228,28 @@ TTL: Automatic
+---
+
+# **Test your DNS Settings**
+
+After setting up your DNS records, it's essential to verify that they are properly configured. Here's how:
+
+#### 1. Check your DNS records
+
+- Use a DNS lookup tool (e.g., [MxToolbox](https://mxtoolbox.com/)) to verify your DNS records
+- Check that your MX, SPF, DKIM, and DMARC records are correctly set up
+- For detailed instructions, follow our [How to Verify your DNS Settings](verify-dns-settings) guide
+
+#### 2. Test email delivery
+
+- Send an email to your new custom domain address. By default, all new mail domains will have a `postmaster@yourdomain.com` inbox
+- If the email is delivered successfully, you can move on to the next step
+
+#### Running into issues?
+
+> Feel free to reach out to [founders@a1base.com](mailto:founders@a1base.com) directly if you run into any issues during the setup process.
+
+
---
# **Warm up your new mail domain**
@@ -257,4 +281,3 @@ After setting up your custom mail domain, you'll need to properly warm it up to
Note:
- Engage with these emails by replying to them
- This helps establish a positive sending reputation for your domain
-
diff --git a/api-reference/custom-mail-domain/verify-dns-settings.mdx b/api-reference/custom-mail-domain/verify-dns-settings.mdx
new file mode 100644
index 0000000..0362b6a
--- /dev/null
+++ b/api-reference/custom-mail-domain/verify-dns-settings.mdx
@@ -0,0 +1,131 @@
+---
+title: 'How to Verify your DNS Settings'
+description: 'After setting up your custom mail domain DNS records, it is crucial to verify that they are correctly configured. This guide will walk you through the verification process to ensure your email domain is ready for use with A1Base.'
+---
+
+
+### Recommended Tools:
+
+- **Command Line (CLI):** Use `dig` or `nslookup` to verify DNS records directly
+- **Tools:**
+ - **[MxToolbox](https://mxtoolbox.com/)**: Comprehensive suite of DNS checking tools
+ - **[Google Admin Toolbox](https://toolbox.googleapps.com/apps/checkmx/)**: Simple interface for checking MX records
+
+
+## Why DNS Verification Is Important
+
+Correctly configured DNS records are essential for:
+- **Email delivery**: Ensures your emails reach their destination
+- **Sender reputation**: Helps prevent your emails from being marked as spam
+- **Security**: Protects your domain from email spoofing and phishing attacks
+
+## What Records to Verify
+
+Before using any verification tools, understand what you're looking for:
+
+### 1. MX Records
+
+Verify that your MX record points to `mail.a101.bot` with priority `10`:
+
+```
+yourdomain.com. IN MX 10 mail.a101.bot.
+```
+
+### 2. SPF Records
+
+Verify your SPF record is correctly formatted and includes the A1Mail server:
+
+```
+mail.yourdomain.com. IN TXT "v=spf1 ip4:110.232.112.135 a:mail.a101.bot ~all"
+```
+
+### 3. DKIM Records
+
+Verify your DKIM record exists and contains the public key provided by A1Mail:
+
+```
+2025_default_mail._domainkey.mail.yourdomain.com. IN TXT "v=DKIM1; h=sha256; k=rsa; t=y; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."
+```
+
+### 4. DMARC Records
+
+Verify your DMARC record is correctly formatted:
+
+```
+_dmarc.yourdomain.com. IN TXT "v=DMARC1; p=none; rua=mailto:postmaster@yourdomain.com"
+```
+
+---
+
+## Verification Tools
+
+### Shortcuts:
+- [Command Line (CLI)](#1-command-line-tools)
+- [MxToolbox](#2-mxtoolbox)
+- [Google Admin Toolbox](#3-google-admin-toolbox)
+
+## 1. Command Line Tools
+
+You can use command line tools like `dig` or `nslookup` to verify your DNS records:
+
+```bash
+# Check MX records
+dig MX yourdomain.com
+
+# Check TXT records (for SPF, DKIM, DMARC)
+dig TXT mail.yourdomain.com
+dig TXT _dmarc.yourdomain.com
+dig TXT 2025_default_mail._domainkey.mail.yourdomain.com
+```
+
+---
+
+## 2. MxToolbox
+
+[MxToolbox](https://mxtoolbox.com/) provides comprehensive DNS checking tools:
+
+- **For MX records**: Use the [MX Lookup tool](https://mxtoolbox.com/MXLookup.aspx)
+ + Enter: `mail.yourdomain.com`
+- **For SPF records**: Use the [SPF Record Lookup tool](https://mxtoolbox.com/SPFRecordLookup.aspx)
+ + Enter: `mail.yourdomain.com` (exact host)
+- **For DKIM records**: Use the [DKIM Lookup tool](https://mxtoolbox.com/dkim.aspx)
+ + Enter: `subdomain._domainkey.yourdomain.com`
+ (You can find this in the A1Mail email dashboard)
+- **For DMARC records**: Use the [DMARC Lookup tool](https://mxtoolbox.com/DMARC.aspx)
+ + Enter: `_dmarc.yourdomain.com` (exact host)
+
+---
+
+## 3. Google Admin Toolbox
+
+[Google Admin Toolbox](https://toolbox.googleapps.com/apps/checkmx/) provides a simple interface to check MX records and email delivery:
+
+1. Go to [Google Admin Toolbox](https://toolbox.googleapps.com/apps/checkmx/)
+2. Enter your domain name and click **Check MX**
+3. Review the results to ensure your MX records are correctly configured
+
+---
+
+## Troubleshooting Common Issues
+
+### DNS Propagation Delays
+
+DNS changes can take up to 48 hours to propagate worldwide. If your verification fails initially, wait a few hours and try again.
+
+### Incorrect Record Format
+
+Ensure there are no typos or formatting errors in your DNS records. Even small errors can cause verification to fail.
+
+### Missing Records
+
+Verify that all required records (MX, SPF, DKIM, DMARC) are present. Missing any one of these can affect email deliverability.
+
+### Cloudflare Proxy Enabled
+
+If using Cloudflare, ensure the proxy (orange cloud) is disabled for all email-related DNS records. Email requires direct DNS resolution.
+
+---
+
+## Need Help?
+
+If you're still having trouble verifying your DNS settings, feel free to reach out to our team at [founders@a1base.com](mailto:founders@a1base.com) for assistance.
diff --git a/mint.json b/mint.json
index 6ca48fd..08da76e 100644
--- a/mint.json
+++ b/mint.json
@@ -96,8 +96,14 @@
"pages": [
"api-reference/email/create-email",
"api-reference/email/receiving-email",
- "api-reference/email/sending-email",
- "api-reference/email/setup-custom-mail-domain"
+ "api-reference/email/sending-email"
+ ]
+ },
+ {
+ "group": "Custom Mail Domain",
+ "pages": [
+ "api-reference/custom-mail-domain/index",
+ "api-reference/custom-mail-domain/verify-dns-settings"
]
}
],
From 894f0f500cb5e46134fb07d4096fb55415bf0f6b Mon Sep 17 00:00:00 2001
From: Pennie
Date: Thu, 3 Apr 2025 17:18:04 -0700
Subject: [PATCH 27/40] done docs
---
.../email => a1mail}/create-email.mdx | 33 ++-
.../custom-mail-domain/index.mdx | 8 +-
.../verify-dns-settings.mdx | 6 +
a1mail/index.mdx | 248 ++++++++++++++++++
a1mail/receiving-email.mdx | 221 ++++++++++++++++
a1mail/sending-email.mdx | 225 ++++++++++++++++
api-reference/email/receiving-email.mdx | 48 ----
api-reference/email/sending-email.mdx | 67 -----
mint.json | 20 +-
9 files changed, 743 insertions(+), 133 deletions(-)
rename {api-reference/email => a1mail}/create-email.mdx (52%)
rename {api-reference => a1mail}/custom-mail-domain/index.mdx (96%)
rename {api-reference => a1mail}/custom-mail-domain/verify-dns-settings.mdx (93%)
create mode 100644 a1mail/index.mdx
create mode 100644 a1mail/receiving-email.mdx
create mode 100644 a1mail/sending-email.mdx
delete mode 100644 api-reference/email/receiving-email.mdx
delete mode 100644 api-reference/email/sending-email.mdx
diff --git a/api-reference/email/create-email.mdx b/a1mail/create-email.mdx
similarity index 52%
rename from api-reference/email/create-email.mdx
rename to a1mail/create-email.mdx
index 691d62c..bada507 100644
--- a/api-reference/email/create-email.mdx
+++ b/a1mail/create-email.mdx
@@ -1,18 +1,11 @@
---
title: 'Creating Email Addresses'
-description: 'Create custom email addresses through the A1Base API'
+description: 'Create an email address to send/receive from using our API'
---
-# Creating Email Addresses
+The Email API allows you to create custom email addresses on your A1Base account.
+Get started with our free domains @a101.bot or @a1send.com.
-The Email API allows you to create custom email addresses on your A1Base account with the domain a1send.com.
-To setup a custom domain (i.e jane@myapp.com) please contact the founders at pasha@a1base.com or pennie@a1base.com
-
-## Endpoint
-Email addresses must be between 5 and 30 characters long.
-```bash
-POST https://api.a1base.com/v1/emails/{account_id}/create-email
-```
### Request Parameters
@@ -21,7 +14,7 @@ POST https://api.a1base.com/v1/emails/{account_id}/create-email
| `address` | string | Yes | The local part of the email address (before the @ symbol) |
| `domain_name` | string | Yes | The domain name to use for the email address |
-### Example Request
+### Endpoint
```bash
curl --location 'https://api.a1base.com/v1/emails/{account_id}/create-email' \
@@ -33,6 +26,10 @@ curl --location 'https://api.a1base.com/v1/emails/{account_id}/create-email' \
"domain_name": "a1send.com"
}'
```
+Valid Email Addresses:
+- 5-30 characters long
+- Only contain letters, numbers, '.', '_', '-'
+- Have no consecutive dots, spaces, or commas
### Example Response
@@ -50,3 +47,17 @@ curl --location 'https://api.a1base.com/v1/emails/{account_id}/create-email' \
| 401 | Invalid API credentials |
| 403 | Domain not authorized for account |
| 409 | Email address already exists |
+
+## Next Steps
+
+After creating your email address, you can:
+
+- [Set up a webhook](/a1mail/receiving-email) to receive incoming emails
+- [Send emails](/a1mail/sending-email) from your new address
+- [Configure a custom domain](/a1mail/custom-mail-domain/index) for your email addresses
+
+
+ We'd love to hear from you!
+
+ Don't hesitate to reach out to pennie@a1base.com or pasha@a1base.com if there's any features you'd like to see or prioritised!
+
\ No newline at end of file
diff --git a/api-reference/custom-mail-domain/index.mdx b/a1mail/custom-mail-domain/index.mdx
similarity index 96%
rename from api-reference/custom-mail-domain/index.mdx
rename to a1mail/custom-mail-domain/index.mdx
index 7697890..1e7378a 100644
--- a/api-reference/custom-mail-domain/index.mdx
+++ b/a1mail/custom-mail-domain/index.mdx
@@ -14,7 +14,7 @@ description: 'Instead of using @a1send.com or @a101.bot, setup your own custom e
### 2. Connect your subdomain to A1Mail
- Go to the [email dashboard](https://www.a1base.com/dashboard/email-addresses)
- Add in your new subdomain to generate DKIM keys
-
+1
### 3. Update and test your DNS records
- Update the MX record
- Add SPF, DKIM, and DMARC records for deliverability and anti-spam
@@ -281,3 +281,9 @@ After setting up your custom mail domain, you'll need to properly warm it up to
Note:
- Engage with these emails by replying to them
- This helps establish a positive sending reputation for your domain
+
+
+ We'd love to hear from you!
+
+ Don't hesitate to reach out to pennie@a1base.com or pasha@a1base.com if there's any features you'd like to see or prioritised!
+
\ No newline at end of file
diff --git a/api-reference/custom-mail-domain/verify-dns-settings.mdx b/a1mail/custom-mail-domain/verify-dns-settings.mdx
similarity index 93%
rename from api-reference/custom-mail-domain/verify-dns-settings.mdx
rename to a1mail/custom-mail-domain/verify-dns-settings.mdx
index 0362b6a..179bad4 100644
--- a/api-reference/custom-mail-domain/verify-dns-settings.mdx
+++ b/a1mail/custom-mail-domain/verify-dns-settings.mdx
@@ -129,3 +129,9 @@ If using Cloudflare, ensure the proxy (orange cloud) is disabled for all email-r
## Need Help?
If you're still having trouble verifying your DNS settings, feel free to reach out to our team at [founders@a1base.com](mailto:founders@a1base.com) for assistance.
+
+
+ We'd love to hear from you!
+
+ Don't hesitate to reach out to pennie@a1base.com or pasha@a1base.com if there's any features you'd like to see or prioritised!
+
\ No newline at end of file
diff --git a/a1mail/index.mdx b/a1mail/index.mdx
new file mode 100644
index 0000000..c75bff8
--- /dev/null
+++ b/a1mail/index.mdx
@@ -0,0 +1,248 @@
+---
+title: 'Quickstart'
+description: 'Get your AI agents sending and receiving emails in minutes with A1Mail. Follow these simple steps to set up your email integration.'
+---
+β° Estimated setup & testing time: 15min.
+
+## 1. Sign Up and Get API Keys
+
+
+ Sign up at [A1Base Dashboard](https://www.a1base.com/) and get your API credentials.
+
+
+- Keep these credentials secure as they'll be used for all API requests
+
+## 2. Create an Email Address
+
+You have two options to create your A1Mail email address:
+
+
+
+
+
+
+
+ Use the API to programmatically create an email address:
+
+
+ ```bash
+ curl --location 'https://api.a1base.com/v1/emails/{account_id}/create-email' \
+ --header 'X-API-Key: YOUR_API_KEY' \
+ --header 'X-API-Secret: YOUR_API_SECRET' \
+ --header 'Content-Type: application/json' \
+ --data '{
+ "address": "hello",
+ "domain_name": "a1send.com"
+ }'
+ ```
+
+
+Note: Email addresses must be between 5 and 30 characters long.
+
+## 3. Test Your New Inbox - Set Up Webhook for Receiving Emails
+
+### Overview
+To receive incoming emails, you'll need to configure a webhook that will notify your application when new emails arrive. This section guides you through setting up and testing webhooks in your development environment.
+
+### 3.1. Create a Webhook Endpoint
+First, create an endpoint in your application to receive email notifications. You can use one of our example snippets below or create your own.
+
+
+
+
+ ```python
+ from flask import Flask, request, jsonify
+
+ app = Flask(__name__)
+
+ @app.route('/webhook/email', methods=['POST'])
+ def email_webhook():
+ # Get the webhook payload
+ data = request.json
+
+ # Process the incoming email
+ print(f"Received email: {data['subject']} from {data['sender_address']}")
+
+ # Access specific parts of the email
+ email_id = data.get('email_id')
+ subject = data.get('subject')
+ sender = data.get('sender_address')
+ recipient = data.get('recipient_address')
+ timestamp = data.get('timestamp')
+ raw_email = data.get('raw_email_data')
+
+ # Your logic here
+ # ...
+
+ # Return a success response
+ return jsonify({"status": "success"}), 200
+
+ if __name__ == '__main__':
+ app.run(debug=True, port=5000)
+ ```
+
+
+ ```javascript
+ const express = require('express');
+ const app = express();
+
+ app.use(express.json());
+
+ app.post('/webhook/email', (req, res) => {
+ // Get the webhook payload
+ const data = req.body;
+
+ // Process the incoming email
+ console.log(`Received email: ${data.subject} from ${data.sender_address}`);
+
+ // Access specific parts of the email
+ const emailId = data.email_id;
+ const subject = data.subject;
+ const sender = data.sender_address;
+ const recipient = data.recipient_address;
+ const timestamp = data.timestamp;
+ const rawEmail = data.raw_email_data;
+
+ // Your logic here
+ // ...
+
+ // Return a success response
+ res.status(200).json({ status: 'success' });
+ });
+
+ app.listen(3000, () => {
+ console.log('Server listening on port 3000');
+ });
+ ```
+
+
+ ```javascript
+ // Simple vanilla JavaScript webhook handler
+ // Can be used with any JavaScript framework or serverless function
+
+ async function handleEmailWebhook(request) {
+ try {
+ // Parse the incoming JSON payload
+ const data = await request.json();
+
+ console.log(`Received email: ${data.subject} from ${data.sender_address}`);
+
+ // Process email data
+ const {
+ email_id,
+ subject,
+ sender_address,
+ recipient_address,
+ timestamp,
+ service,
+ raw_email_data
+ } = data;
+
+ // Your logic here
+ // ...
+
+ // Return a success response
+ return new Response(JSON.stringify({ status: 'success' }), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' }
+ });
+ } catch (error) {
+ console.error('Error processing webhook:', error);
+ return new Response(JSON.stringify({ status: 'error', message: error.message }), {
+ status: 500,
+ headers: { 'Content-Type': 'application/json' }
+ });
+ }
+ }
+ ```
+
+
+
+
+### 3.2 Expose Your Local Webhook Endpoint
+
+To receive emails in your inbox, you need to expose your local webhook endpoint to the internet. You can use ngrok to do this.
+Run the following command in your CLI to start ngrok:
+```bash
+ngrok http 3000
+```
+After starting ngrok
+
+1. **Find the forwarding URL** in the ngrok output:
+ ```
+ Forwarding https://0fcbcda91e79.ngrok.app -> http://localhost:3000
+ ```
+
+2. **Create the full webhook URL** by appending your webhook endpoint path:
+ ```
+ https://0fcbcda91e79.ngrok.app/webhook/email
+ ```
+
+3. **Use this complete URL** in the A1Mail dashboard webhook settings.
+
+
+### 3.3 Register Your Webhook in the Dashboard
+
+Once you have your webhook URL, you need to register it in the A1Mail dashboard:
+
+1. Log in to your [A1Base Dashboard](https://www.a1base.com/)
+2. Navigate to the Email Settings section
+3. Find your email address and click "Configure Webhook"
+4. Enter your full webhook URL (e.g., `https://0fcbcda91e79.ngrok.app/webhook/email`)
+5. Click "Save Changes"
+
+### 3.4 Test Your Webhook
+
+Send a test email to your inbox and check if the webhook is receiving the email notifications.
+
+## 4. Send Your First Email
+
+Now that you've confirmed your inbox can receive emails, you can start sending emails as that email address.
+
+
+
+ Use the following API call to send your first email:
+
+ ```bash
+ curl --location 'https://api.a1base.com/v1/emails/{account_id}/send' \
+ --header 'X-API-Key: YOUR_API_KEY' \
+ --header 'X-API-Secret: YOUR_API_SECRET' \
+ --header 'Content-Type: application/json' \
+ --data '{
+ "from": "hello@a1send.com",
+ "to": "recipient@example.com",
+ "subject": "My First A1Mail Email",
+ "text": "Hello from my AI agent! This is my first email sent through A1Mail."
+ }'
+ ```
+
+
+## 5. In Summary
+
+To ensure everything is working correctly:
+
+1. Send a test email to your new A1Mail address from your personal email
+2. Check that your webhook receives the incoming email notification
+3. Respond to the email using the A1Mail API
+
+Now that you've set up basic email functionality, explore more advanced features:
+
+- [Sending HTML Emails](/a1mail/sending-email)
+- [Setting Up Custom Mail Domains](/a1mail/custom-mail-domain/index)
+- [Email Webhook Security](/api-reference/email/receiving-email)
+
+With these steps completed, your AI agent now has a fully functional email inbox! You can send and receive emails programmatically, enabling your agent to communicate with users through email.
+
+
+ We'd love to hear from you!
+
+ Don't hesitate to reach out to pennie@a1base.com or pasha@a1base.com if there's any features you'd like to see or prioritised!
+
\ No newline at end of file
diff --git a/a1mail/receiving-email.mdx b/a1mail/receiving-email.mdx
new file mode 100644
index 0000000..c2c6576
--- /dev/null
+++ b/a1mail/receiving-email.mdx
@@ -0,0 +1,221 @@
+---
+title: 'Receiving Emails'
+description: 'Receive incoming emails through webhooks'
+---
+
+A1Base can forward incoming emails to your application via webhooks. When an email is received at your A1Base email address, we'll send a POST request to your configured webhook URL with the email details.
+You can configure your webhook on the [email dashboard here](https://www.a1base.com/dashboard/email-addresses)
+
+β° Estimated setup time: 10min.
+
+### In this guide you will:
+
+### 1. Setup a webhook endpoint in your app
+- Create a simple endpoint to receive email webhooks
+- Use ngrok to expose your local endpoint to the internet
+
+### 2. Configure webhook URL in A1Mail dashboard
+- Go to the [email dashboard](https://www.a1base.com/dashboard/email-addresses)
+- Add your ngrok webhook URL to your A1Mail Dashboard
+
+### 3. Test receiving emails
+- Send a test email from your personal email address to your A1Mail inbox
+- View the incoming email data in your webhook endpoint
+- Parse and process the email contents as needed
+
+
+
+## 1. Create a Webhook Endpoint
+
+First, create an endpoint in your application to receive email notifications. You can use one of our example snippets below or create your own.
+
+
+
+
+ ```python
+ from flask import Flask, request, jsonify
+
+ app = Flask(__name__)
+
+ @app.route('/webhook/email', methods=['POST'])
+ def email_webhook():
+ # Get the webhook payload
+ data = request.json
+
+ # Process the incoming email
+ print(f"Received email: {data['subject']} from {data['sender_address']}")
+
+ # Access specific parts of the email
+ email_id = data.get('email_id')
+ subject = data.get('subject')
+ sender = data.get('sender_address')
+ recipient = data.get('recipient_address')
+ timestamp = data.get('timestamp')
+ raw_email = data.get('raw_email_data')
+
+ # Your logic here
+ # ...
+
+ # Return a success response
+ return jsonify({"status": "success"}), 200
+
+ if __name__ == '__main__':
+ app.run(debug=True, port=5000)
+ ```
+
+
+ ```javascript
+ const express = require('express');
+ const app = express();
+
+ app.use(express.json());
+
+ app.post('/webhook/email', (req, res) => {
+ // Get the webhook payload
+ const data = req.body;
+
+ // Process the incoming email
+ console.log(`Received email: ${data.subject} from ${data.sender_address}`);
+
+ // Access specific parts of the email
+ const emailId = data.email_id;
+ const subject = data.subject;
+ const sender = data.sender_address;
+ const recipient = data.recipient_address;
+ const timestamp = data.timestamp;
+ const rawEmail = data.raw_email_data;
+
+ // Your logic here
+ // ...
+
+ // Return a success response
+ res.status(200).json({ status: 'success' });
+ });
+
+ app.listen(3000, () => {
+ console.log('Server listening on port 3000');
+ });
+ ```
+
+
+ ```javascript
+ // Simple vanilla JavaScript webhook handler
+ // Can be used with any JavaScript framework or serverless function
+
+ async function handleEmailWebhook(request) {
+ try {
+ // Parse the incoming JSON payload
+ const data = await request.json();
+
+ console.log(`Received email: ${data.subject} from ${data.sender_address}`);
+
+ // Process email data
+ const {
+ email_id,
+ subject,
+ sender_address,
+ recipient_address,
+ timestamp,
+ service,
+ raw_email_data
+ } = data;
+
+ // Your logic here
+ // ...
+
+ // Return a success response
+ return new Response(JSON.stringify({ status: 'success' }), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' }
+ });
+ } catch (error) {
+ console.error('Error processing webhook:', error);
+ return new Response(JSON.stringify({ status: 'error', message: error.message }), {
+ status: 500,
+ headers: { 'Content-Type': 'application/json' }
+ });
+ }
+ }
+ ```
+
+
+
+
+## 2. Expose Your Local Webhook Endpoint
+
+To receive emails in your inbox, you need to expose your local webhook endpoint to the internet. You can use ngrok to do this.
+Run the following command in your CLI to start ngrok:
+```bash
+ngrok http 3000
+```
+After starting ngrok
+
+1. **Find the forwarding URL** in the ngrok output:
+ ```
+ Forwarding https://0fcbcda91e79.ngrok.app -> http://localhost:3000
+ ```
+
+2. **Create the full webhook URL** by appending your webhook endpoint path:
+ ```
+ https://0fcbcda91e79.ngrok.app/webhook/email
+ ```
+
+3. **Use this complete URL** in the A1Mail dashboard webhook settings.
+
+## 3. Register Your Webhook in the Dashboard
+
+Once you have your webhook URL, you need to register it in the A1Mail dashboard:
+
+1. Log in to your [A1Base Dashboard](https://www.a1base.com/)
+2. Navigate to the Email Settings section
+3. Find your email address and click "Configure Webhook"
+4. Enter your full webhook URL (e.g., `https://0fcbcda91e79.ngrok.app/webhook/email`)
+5. Click "Save Changes"
+
+## 4. Test Your Webhook
+
+Send a test email to your inbox and check if the webhook is receiving the email notifications.
+
+## Webhook Payload
+
+When an email is received, A1Base will send a POST request to your webhook endpoint with the following JSON payload:
+
+```json
+{
+ "email_id": "a82b3e6b-dc79-46ad-9284-a166629592e3",
+ "subject": "Email Subject",
+ "sender_address": "sender@example.com",
+ "recipient_address": "your-address@a1send.com",
+ "timestamp": "2025-03-19T10:24:08.46083+00:00",
+ "service": "email",
+ "raw_email_data": "Full email content including headers and body"
+}
+```
+
+### Payload Fields
+
+| Field | Type | Description |
+|-------|------|-----------|
+| `email_id` | string | Unique identifier for the received email |
+| `subject` | string | Subject line of the email |
+| `sender_address` | string | Email address of the sender |
+| `recipient_address` | string | Your A1Base email address that received the message |
+| `timestamp` | string | ISO 8601 timestamp of when the email was received |
+| `service` | string | Always "email" for email webhooks |
+| `raw_email_data` | string | Complete raw email content including headers and body |
+
+## Example Raw Email Data
+
+The `raw_email_data` field contains the complete email including headers, which you can parse to extract additional information like:
+
+- DKIM signatures
+- Message ID
+- Content type
+- Email body (plain text and HTML versions)
+- Custom headers
+
+
+ We'd love to hear from you!
+
+ Don't hesitate to reach out to pennie@a1base.com or pasha@a1base.com if there's any features you'd like to see or prioritised!
+
diff --git a/a1mail/sending-email.mdx b/a1mail/sending-email.mdx
new file mode 100644
index 0000000..92de834
--- /dev/null
+++ b/a1mail/sending-email.mdx
@@ -0,0 +1,225 @@
+---
+title: 'Sending Emails'
+description: 'Send emails programmatically through the A1Mail API with just a few lines of code'
+---
+
+## Request Parameters
+
+The following parameters are used when sending an email:
+
+| Parameter | Type | Required | Description |
+|-----------|------|----------|-------------|
+| `sender_address` | string | Yes | Email address that will appear in the "From" field |
+| `recipient_address` | string | Yes | Email address of the recipient |
+| `subject` | string | Yes | Subject line of the email |
+| `body` | string | Yes | Content of the email (plain text or HTML) |
+| `headers` | object | No | Optional email headers as key-value pairs (cc, bcc, etc.) |
+
+## Code Examples
+
+
+
+ ### Send a Simple Text Email
+
+ ```bash
+ curl --location 'https://api.a1base.com/v1/emails/{account_id}/send' \
+ --header 'X-API-Key: YOUR_API_KEY' \
+ --header 'X-API-Secret: YOUR_API_SECRET' \
+ --header 'Content-Type: application/json' \
+ --data-raw '{
+ "sender_address": "hello@a1send.com",
+ "recipient_address": "recipient@example.com",
+ "subject": "Hello from A1Base",
+ "body": "This is an example email body.",
+ "headers": {}
+ }'
+ ```
+
+ ### Send an HTML Email
+
+ ```bash
+ curl --location 'https://api.a1base.com/v1/emails/{account_id}/send' \
+ --header 'X-API-Key: YOUR_API_KEY' \
+ --header 'X-API-Secret: YOUR_API_SECRET' \
+ --header 'Content-Type: application/json' \
+ --data-raw '{
+ "sender_address": "hello@a1send.com",
+ "recipient_address": "recipient@example.com",
+ "subject": "Hello from A1Base",
+ "body": "A1Mail for AI Agents
Hey,
Welcome to A1Mail! A1Mail is an email API made for AI agents who chat, not spam.
+
+ `;
+
+ const data = {
+ sender_address: 'hello@a1send.com',
+ recipient_address: 'recipient@example.com',
+ subject: 'Hello from A1Base',
+ body: htmlBody,
+ headers: {
+ cc: 'pennie@a1base.com',
+ bcc: 'pasha@a1base.com'
+ }
+ };
+
+ axios.post(url, data, { headers })
+ .then(response => console.log(response.data))
+ .catch(error => console.error('Error:', error));
+ ```
+
+
+
+
+ We'd love to hear from you!
+
+ Don't hesitate to reach out to pennie@a1base.com or pasha@a1base.com if there's any features you'd like to see or prioritised!
+
diff --git a/api-reference/email/receiving-email.mdx b/api-reference/email/receiving-email.mdx
deleted file mode 100644
index 1d756ef..0000000
--- a/api-reference/email/receiving-email.mdx
+++ /dev/null
@@ -1,48 +0,0 @@
----
-title: 'Receiving Emails'
-description: 'Receive incoming emails through webhooks'
----
-
-# Receiving Emails
-
-A1Base can forward incoming emails to your application via webhooks. When an email is received at your A1Base email address, we'll send a POST request to your configured webhook URL with the email details.
-You can configure your webhook on the [email dashboard here](https://www.a1base.com/dashboard/email-addresses)
-
-## Webhook Payload
-
-When an email is received, A1Base will send a POST request to your webhook endpoint with the following JSON payload:
-
-```json
-{
- "email_id": "a82b3e6b-dc79-46ad-9284-a166629592e3",
- "subject": "Email Subject",
- "sender_address": "sender@example.com",
- "recipient_address": "your-address@a1send.com",
- "timestamp": "2025-03-19T10:24:08.46083+00:00",
- "service": "email",
- "raw_email_data": "Full email content including headers and body"
-}
-```
-
-### Payload Fields
-
-| Field | Type | Description |
-|-------|------|-------------|
-| `email_id` | string | Unique identifier for the received email |
-| `subject` | string | Subject line of the email |
-| `sender_address` | string | Email address of the sender |
-| `recipient_address` | string | Your A1Base email address that received the message |
-| `timestamp` | string | ISO 8601 timestamp of when the email was received |
-| `service` | string | Always "email" for email webhooks |
-| `raw_email_data` | string | Complete raw email content including headers and body |
-
-## Example Raw Email Data
-
-The `raw_email_data` field contains the complete email including headers, which you can parse to extract additional information like:
-
-- DKIM signatures
-- Message ID
-- Content type
-- Email body (plain text and HTML versions)
-- Attachments
-- Custom headers
\ No newline at end of file
diff --git a/api-reference/email/sending-email.mdx b/api-reference/email/sending-email.mdx
deleted file mode 100644
index d87744d..0000000
--- a/api-reference/email/sending-email.mdx
+++ /dev/null
@@ -1,67 +0,0 @@
----
-title: 'Sending Emails'
-description: 'Send and receive emails through the A1Base API'
----
-
-# Email API
-
-The Email API allows you to programmatically send and manage emails through A1 Base.
-
-## Base URL
-
-```bash
-https://api.a1base.com/v1/emails
-
-```
-
-## Sending Emails
-
-You can send emails using the following endpoint:
-
-```bash
-POST https://api.a1base.com/v1/emails/{account_id}/send
-```
-
-### Request Parameters
-
-| Parameter | Type | Required | Description |
-|-----------|------|----------|-------------|
-| `sender_address` | string | Yes | Email address that will appear in the "From" field |
-| `recipient_address` | string | Yes | Email address of the recipient |
-| `subject` | string | Yes | Subject line of the email |
-| `body` | string | Yes | Content of the email (plain text or HTML) |
-| `headers` | object | No | Optional email headers as key-value pairs |
-
-### Example Requests
-
-Send simple text emails
-```bash
-curl --location 'https://api.a1base.com/v1/emails/{account_id}/send' \
---header 'X-API-Key: YOUR_API_KEY' \
---header 'X-API-Secret: YOUR_API_SECRET' \
---header 'Content-Type: application/json' \
---data-raw '{
- "sender_address": "hello@a1send.com",
- "recipient_address": "recipient@example.com",
- "subject": "Hello from A1Base",
- "body": "This is an example email body.",
- "headers": {}
-}'
-```
-Send formatted html emails
-```bash
-curl --location 'https://api.a1base.com/v1/emails/{account_id}/send' \
---header 'X-API-Key: YOUR_API_KEY' \
---header 'X-API-Secret: YOUR_API_SECRET' \
---header 'Content-Type: application/json' \
---data-raw '{
- "sender_address": "hello@a1send.com",
- "recipient_address": "recipient@example.com",
- "subject": "Hello from A1Base",
- "body": "A1Mail for AI Agents
Hey,
Welcome to A1Mail! A1Mail is an email API made for AI agents who chat, not spam.
With A1Mail you can:
Create new addresses via a simple API
Send emails effortlessly
Receive messages instantly via webhooks
Protect deliverability with built-in spam filters
Integrate with any AI system
Enjoy transparent pricingβno hidden fees
Use your own subdomain for AI agents
Find out more at www.a1mail.com.",
- "headers": {
- "cc": "pennie@a1base.com",
- "bcc": "pasha@a1base.com"
- }
-}'
-```
\ No newline at end of file
diff --git a/mint.json b/mint.json
index 08da76e..58a4146 100644
--- a/mint.json
+++ b/mint.json
@@ -29,6 +29,10 @@
{
"name": "API Reference",
"url": "api-reference"
+ },
+ {
+ "name": "A1Mail",
+ "url": "a1mail"
}
],
"anchors": [
@@ -92,18 +96,22 @@
"pages": ["api-reference/cron-jobs/introduction"]
},
{
- "group": "Email",
+ "group": "A1Mail",
+ "pages": ["a1mail/index"]
+ },
+ {
+ "group": "Email API",
"pages": [
- "api-reference/email/create-email",
- "api-reference/email/receiving-email",
- "api-reference/email/sending-email"
+ "a1mail/create-email",
+ "a1mail/receiving-email",
+ "a1mail/sending-email"
]
},
{
"group": "Custom Mail Domain",
"pages": [
- "api-reference/custom-mail-domain/index",
- "api-reference/custom-mail-domain/verify-dns-settings"
+ "a1mail/custom-mail-domain/index",
+ "a1mail/custom-mail-domain/verify-dns-settings"
]
}
],
From 721397fa873a07ce1253982b99e85845f98e7621 Mon Sep 17 00:00:00 2001
From: Pennie
Date: Fri, 4 Apr 2025 17:38:03 -0700
Subject: [PATCH 28/40] removed google
---
a1mail/custom-mail-domain/verify-dns-settings.mdx | 15 +--------------
1 file changed, 1 insertion(+), 14 deletions(-)
diff --git a/a1mail/custom-mail-domain/verify-dns-settings.mdx b/a1mail/custom-mail-domain/verify-dns-settings.mdx
index 179bad4..30f47a7 100644
--- a/a1mail/custom-mail-domain/verify-dns-settings.mdx
+++ b/a1mail/custom-mail-domain/verify-dns-settings.mdx
@@ -9,7 +9,6 @@ description: 'After setting up your custom mail domain DNS records, it is crucia
- **Command Line (CLI):** Use `dig` or `nslookup` to verify DNS records directly
- **Tools:**
- **[MxToolbox](https://mxtoolbox.com/)**: Comprehensive suite of DNS checking tools
- - **[Google Admin Toolbox](https://toolbox.googleapps.com/apps/checkmx/)**: Simple interface for checking MX records
## Why DNS Verification Is Important
@@ -26,7 +25,6 @@ Before using any verification tools, understand what you're looking for:
### 1. MX Records
Verify that your MX record points to `mail.a101.bot` with priority `10`:
-
```
yourdomain.com. IN MX 10 mail.a101.bot.
```
@@ -62,7 +60,6 @@ _dmarc.yourdomain.com. IN TXT "v=DMARC1; p=none; rua=mailto:postmaster@yourdomai
### Shortcuts:
- [Command Line (CLI)](#1-command-line-tools)
- [MxToolbox](#2-mxtoolbox)
-- [Google Admin Toolbox](#3-google-admin-toolbox)
## 1. Command Line Tools
@@ -96,16 +93,6 @@ dig TXT 2025_default_mail._domainkey.mail.yourdomain.com
---
-## 3. Google Admin Toolbox
-
-[Google Admin Toolbox](https://toolbox.googleapps.com/apps/checkmx/) provides a simple interface to check MX records and email delivery:
-
-1. Go to [Google Admin Toolbox](https://toolbox.googleapps.com/apps/checkmx/)
-2. Enter your domain name and click **Check MX**
-3. Review the results to ensure your MX records are correctly configured
-
----
-
## Troubleshooting Common Issues
### DNS Propagation Delays
@@ -134,4 +121,4 @@ If you're still having trouble verifying your DNS settings, feel free to reach o
We'd love to hear from you!
Don't hesitate to reach out to pennie@a1base.com or pasha@a1base.com if there's any features you'd like to see or prioritised!
-
\ No newline at end of file
+
From 643a75ca7551855fcd33d46e8879b42ef62ce9f7 Mon Sep 17 00:00:00 2001
From: Pennie
Date: Wed, 9 Apr 2025 23:11:26 -0400
Subject: [PATCH 29/40] spf
---
a1mail/custom-mail-domain/index.mdx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/a1mail/custom-mail-domain/index.mdx b/a1mail/custom-mail-domain/index.mdx
index 1e7378a..6168455 100644
--- a/a1mail/custom-mail-domain/index.mdx
+++ b/a1mail/custom-mail-domain/index.mdx
@@ -51,7 +51,7 @@ description: 'Instead of using @a1send.com or @a101.bot, setup your own custom e
- In the **Advanced DNS** tab click **Add New Record**
- Select `A Record` and enter the following:
```jsx
- Host: `mail`
+ Host: `@ or mail if your subdomain is mail.example.com`
Value: `110.232.112.135`
TTL: Automatic
```
@@ -286,4 +286,4 @@ Note:
We'd love to hear from you!
Don't hesitate to reach out to pennie@a1base.com or pasha@a1base.com if there's any features you'd like to see or prioritised!
-
\ No newline at end of file
+
From a37ec6a46c7903f40dca7d89c069c45e9cbdd462 Mon Sep 17 00:00:00 2001
From: Pennie
Date: Thu, 10 Apr 2025 14:44:45 -0400
Subject: [PATCH 30/40] webhook for receieving;
---
a1mail/receiving-email.mdx | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/a1mail/receiving-email.mdx b/a1mail/receiving-email.mdx
index c2c6576..c9ce5f6 100644
--- a/a1mail/receiving-email.mdx
+++ b/a1mail/receiving-email.mdx
@@ -172,6 +172,20 @@ Once you have your webhook URL, you need to register it in the A1Mail dashboard:
4. Enter your full webhook URL (e.g., `https://0fcbcda91e79.ngrok.app/webhook/email`)
5. Click "Save Changes"
+## Webhook Precedence
+
+When an email is received, A1Mail will attempt to deliver it to a webhook in the following order:
+
+1. Individual email address webhook
+2. Custom domain webhook
+3. Default webhook
+
+If a webhook is not configured or returns an error, A1Mail will try the next webhook in the precedence list.
+
+
+
+
+
## 4. Test Your Webhook
Send a test email to your inbox and check if the webhook is receiving the email notifications.
From 8981d949e116e767b0896f392cf998e31b1d9c04 Mon Sep 17 00:00:00 2001
From: Pennie
Date: Sun, 11 May 2025 00:10:48 -0700
Subject: [PATCH 31/40] fixed docs
---
.DS_Store | Bin 0 -> 8196 bytes
api-reference/group-management/create.mdx | 2 +-
2 files changed, 1 insertion(+), 1 deletion(-)
create mode 100644 .DS_Store
diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 0000000000000000000000000000000000000000..1754ecc207d299659fe34aef0a1efb40d45f722e
GIT binary patch
literal 8196
zcmeHM%W4!s6us3Bu|dS3n=AwyjE{v8262&%w4tLYx*8Bf7s+Jeq-EM}l8FiUpua#w
z1ebnA_AbPg;wHFpBlrXUfu37cOjq@5vJxLvP<4Cio?CVLRQ1%{9wHJ;+k-CA91;1b
zY-f+4sVVH|S}C6HIS(rkPorVCx0WVZ1?f7R0!{&^fK$LJ;1u{%6u_Cy#cw$GmDjaS
z0jI!!sQ`aJ1gLC7ZF6PiR|guI0>F0AEeoz^`2sf3Hq?&?$yc;ph)E
zF4Q(xR)v$$$%oK03!R||IXlJ=B%DO3tZSVDPJyZdoV$1EA`K~~v6a93H(r^(B#ol}
zDD9)q?=Sv*F~9%es_h@N`m+t=-N14Al+Ms9btyp`)5w~e-aNzDmfxIz?`d-8#Ai8w
z2~%)r!dN`$9j2k9V32{s0&UVdJwRpqh}LM`X7zoc9c^;FvvcL#Tg9BzeL4E1*Vh=sRI3SN@$h~yj}BBx!6LPFd4JaQne$&?oaix$HRrDLTl3O4
zp7${J5U8xya@vo*;Phj?Z%bpDtlDrjypDm(CY0KyzIvW$
z1D{g##^5`&chRMJu$&TMat@zl_m4(
gvgkqi?JojKzZ2~9QvObwaxS~`UoM<${%Qq&0p-At*8l(j
literal 0
HcmV?d00001
diff --git a/api-reference/group-management/create.mdx b/api-reference/group-management/create.mdx
index 32424f5..349f3bb 100644
--- a/api-reference/group-management/create.mdx
+++ b/api-reference/group-management/create.mdx
@@ -1,6 +1,6 @@
---
title: "Group Management"
-openapi: "POST /group-management/{accountId}"
+openapi: "POST /whatsapp/{accountId}/group-management"
---
The group management endpoint allows you to perform various WhatsApp group operations including creating groups, managing participants, and updating group settings.
From 1ab54128f0ce1078b845fe7bd8c9f240b8c2f8cb Mon Sep 17 00:00:00 2001
From: Pennie
Date: Tue, 27 May 2025 15:05:43 -0700
Subject: [PATCH 32/40] webhook signing
---
api-reference/webhook/webhook.mdx | 63 +++++++++++++++++++++++++++++++
1 file changed, 63 insertions(+)
diff --git a/api-reference/webhook/webhook.mdx b/api-reference/webhook/webhook.mdx
index 197ce45..a0881d7 100644
--- a/api-reference/webhook/webhook.mdx
+++ b/api-reference/webhook/webhook.mdx
@@ -152,6 +152,14 @@ Think of it as setting up an automatic forwarding system - whenever someone mess
```
+
+ HMAC-SHA256 signature used to verify the authenticity of the webhook. Created using your API secret and the timestamp + request body.
+
+
+
+ Unix timestamp (in seconds) when the webhook was sent. Used to verify the request and prevent replay attacks.
+
+
### Response Codes
* `200`: Message received successfully
@@ -210,5 +218,60 @@ Think of it as setting up an automatic forwarding system - whenever someone mess
- Keep your webhook URL private
- Implement rate limiting if needed
- Add error handling for failed message processing
+ - All webhook requests from A1Base include an `x-signature` and `x-timestamp` header.
+ - You can verify the authenticity of each request using your API secret and the HMAC-SHA256 algorithm.
+ - Here's how the signature is generated on our side:
+
+ ```ts
+ const message = timestamp + JSON.stringify(body);
+ const signature = crypto
+ .createHmac('sha256', apiSecret)
+ .update(message)
+ .digest('hex');
+ ```
+
+ On your server, do the following to verify the signature:
+
+ 1. Read the raw JSON body of the request
+ 2. Get the x-timestamp header
+ 3. Recreate the message string as timestamp + rawBody
+ 4. Generate your own HMAC signature with your API secret
+ 5. Compare it with the x-signature using a constant-time comparison
+
+ **Example in Express (Node.js)**
+ ```ts
+ import crypto from 'crypto';
+ import express from 'express';
+ const app = express();
+
+ app.use(express.json({
+ verify: (req, res, buf) => {
+ req.rawBody = buf; // capture raw body for HMAC check
+ }
+ }));
+
+ app.post('/whatsapp/incoming', (req, res) => {
+ const rawBody = req.rawBody.toString();
+ const timestamp = req.headers['x-timestamp'];
+ const receivedSig = req.headers['x-signature'];
+ const secret = process.env.A1BASE_API_SECRET;
+
+ const expectedSig = crypto
+ .createHmac('sha256', secret)
+ .update(timestamp + rawBody)
+ .digest('hex');
+
+ if (receivedSig !== expectedSig) {
+ return res.status(403).send('Invalid signature');
+ }
+
+ // Continue processing the verified request
+ res.sendStatus(200);
+ });
+ ```
+
+
+ Reject any webhook requests with a timestamp older than 5 minutes to prevent replay attacks.
+
\ No newline at end of file
From accee4aa079558496f34bd4428d5e97ef746c3a5 Mon Sep 17 00:00:00 2001
From: pasha rayan
Date: Wed, 28 May 2025 23:40:46 +1000
Subject: [PATCH 33/40] updated a1cron docs
---
a1cron/create.mdx | 390 +++++++++++++++++++++++++++++++
a1cron/delete.mdx | 146 ++++++++++++
a1cron/examples.mdx | 502 ++++++++++++++++++++++++++++++++++++++++
a1cron/get-details.mdx | 238 +++++++++++++++++++
a1cron/get-logs.mdx | 289 +++++++++++++++++++++++
a1cron/index.mdx | 85 +++++++
a1cron/introduction.mdx | 202 ++++++++++++++++
a1cron/list.mdx | 217 +++++++++++++++++
a1cron/quickstart.mdx | 325 ++++++++++++++++++++++++++
a1cron/trigger.mdx | 205 ++++++++++++++++
a1cron/update.mdx | 321 +++++++++++++++++++++++++
a1cron/webhooks.mdx | 405 ++++++++++++++++++++++++++++++++
mint.json | 34 +++
13 files changed, 3359 insertions(+)
create mode 100644 a1cron/create.mdx
create mode 100644 a1cron/delete.mdx
create mode 100644 a1cron/examples.mdx
create mode 100644 a1cron/get-details.mdx
create mode 100644 a1cron/get-logs.mdx
create mode 100644 a1cron/index.mdx
create mode 100644 a1cron/introduction.mdx
create mode 100644 a1cron/list.mdx
create mode 100644 a1cron/quickstart.mdx
create mode 100644 a1cron/trigger.mdx
create mode 100644 a1cron/update.mdx
create mode 100644 a1cron/webhooks.mdx
diff --git a/a1cron/create.mdx b/a1cron/create.mdx
new file mode 100644
index 0000000..e09b4ab
--- /dev/null
+++ b/a1cron/create.mdx
@@ -0,0 +1,390 @@
+---
+title: "Create Cron Job"
+openapi: "POST /v1/cron-jobs/{accountId}/create"
+---
+
+Create a new scheduled task that will execute HTTP requests at specified intervals.
+
+## Path Parameters
+
+
+ Your A1Base account ID
+
+
+## Request Body
+
+
+ A descriptive name for your cron job
+
+
+
+ Detailed description of what this cron job does
+
+
+
+ The URL to call when the cron job executes. Must be a valid HTTP/HTTPS URL.
+
+
+
+ HTTP method to use: GET, POST, PUT, DELETE
+
+
+
+ HTTP headers to include with each request. Common headers include Authorization, Content-Type, etc.
+
+ Example:
+ ```json
+ {
+ "Authorization": "Bearer your-token",
+ "Content-Type": "application/json"
+ }
+ ```
+
+
+
+ Request body for POST/PUT methods. Must be a string (JSON string if sending JSON).
+
+
+
+ Timezone for schedule execution (e.g., "America/New_York", "UTC", "Europe/London")
+
+
+
+ Schedule configuration object
+
+
+
+ Type of repetition: `hourly`, `days`, `weeks`, `months`, `years`
+
+
+
+ Frequency of repetition (e.g., 1 = every day, 2 = every 2 days)
+
+
+
+ Time in 24-hour format "HH:MM" (e.g., "09:00", "14:30")
+
+
+
+ Required for weekly schedules. Array of day numbers:
+ - "0" = Sunday
+ - "1" = Monday
+ - "2" = Tuesday
+ - "3" = Wednesday
+ - "4" = Thursday
+ - "5" = Friday
+ - "6" = Saturday
+
+
+
+ How the schedule ends: `never`, `on`, `after`
+
+
+
+ ISO 8601 timestamp when to stop (required if end_type is "on")
+
+
+
+ Number of occurrences before stopping (required if end_type is "after")
+
+
+
+
+
+ Retry configuration for failed requests
+
+
+
+ Maximum number of retry attempts (0-10)
+
+
+
+ Seconds to wait between retries
+
+
+
+ Maximum seconds to wait for response
+
+
+
+
+
+ Webhook URLs for execution notifications
+
+
+
+ URL to call on successful execution
+
+
+
+ URL to call on failed execution
+
+
+
+
+
+ Array of tags for organization and filtering
+
+
+
+ Whether the cron job should start active
+
+
+## Response
+
+Returns the created cron job object with generated ID and calculated next run time.
+
+
+
+```bash cURL
+curl -X POST https://api.a1base.com/v1/cron-jobs/{accountId}/create \
+ -H "X-API-Key: your-api-key" \
+ -H "X-API-Secret: your-api-secret" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "name": "Daily Sales Report",
+ "description": "Generate comprehensive sales report",
+ "endpoint_url": "https://api.company.com/reports/daily",
+ "method": "POST",
+ "headers": {
+ "Authorization": "Bearer report-token",
+ "Content-Type": "application/json"
+ },
+ "body": "{\"report_type\": \"sales\", \"format\": \"pdf\"}",
+ "timezone": "America/New_York",
+ "schedule_config": {
+ "repeat_type": "days",
+ "repeat_every": 1,
+ "time": "09:00",
+ "end_type": "never"
+ },
+ "retry_config": {
+ "max_retries": 3,
+ "retry_delay_seconds": 300,
+ "timeout_seconds": 30
+ },
+ "callbacks": {
+ "success_url": "https://webhooks.company.com/cron-success",
+ "failure_url": "https://webhooks.company.com/cron-failure"
+ },
+ "tags": ["reports", "daily", "sales"],
+ "is_active": true
+ }'
+```
+
+```javascript Node.js
+const axios = require('axios');
+
+const createCronJob = async () => {
+ const cronJobData = {
+ name: "Daily Sales Report",
+ description: "Generate comprehensive sales report",
+ endpoint_url: "https://api.company.com/reports/daily",
+ method: "POST",
+ headers: {
+ "Authorization": "Bearer report-token",
+ "Content-Type": "application/json"
+ },
+ body: JSON.stringify({
+ report_type: "sales",
+ format: "pdf"
+ }),
+ timezone: "America/New_York",
+ schedule_config: {
+ repeat_type: "days",
+ repeat_every: 1,
+ time: "09:00",
+ end_type: "never"
+ },
+ retry_config: {
+ max_retries: 3,
+ retry_delay_seconds: 300,
+ timeout_seconds: 30
+ },
+ callbacks: {
+ success_url: "https://webhooks.company.com/cron-success",
+ failure_url: "https://webhooks.company.com/cron-failure"
+ },
+ tags: ["reports", "daily", "sales"],
+ is_active: true
+ };
+
+ try {
+ const response = await axios.post(
+ 'https://api.a1base.com/v1/cron-jobs/{accountId}/create',
+ cronJobData,
+ {
+ headers: {
+ 'X-API-Key': 'your-api-key',
+ 'X-API-Secret': 'your-api-secret',
+ 'Content-Type': 'application/json'
+ }
+ }
+ );
+
+ console.log('Cron job created:', response.data);
+ } catch (error) {
+ console.error('Error:', error.response.data);
+ }
+};
+
+createCronJob();
+```
+
+```python Python
+import requests
+import json
+
+def create_cron_job():
+ url = "https://api.a1base.com/v1/cron-jobs/{accountId}/create"
+
+ headers = {
+ "X-API-Key": "your-api-key",
+ "X-API-Secret": "your-api-secret",
+ "Content-Type": "application/json"
+ }
+
+ data = {
+ "name": "Daily Sales Report",
+ "description": "Generate comprehensive sales report",
+ "endpoint_url": "https://api.company.com/reports/daily",
+ "method": "POST",
+ "headers": {
+ "Authorization": "Bearer report-token",
+ "Content-Type": "application/json"
+ },
+ "body": json.dumps({
+ "report_type": "sales",
+ "format": "pdf"
+ }),
+ "timezone": "America/New_York",
+ "schedule_config": {
+ "repeat_type": "days",
+ "repeat_every": 1,
+ "time": "09:00",
+ "end_type": "never"
+ },
+ "retry_config": {
+ "max_retries": 3,
+ "retry_delay_seconds": 300,
+ "timeout_seconds": 30
+ },
+ "callbacks": {
+ "success_url": "https://webhooks.company.com/cron-success",
+ "failure_url": "https://webhooks.company.com/cron-failure"
+ },
+ "tags": ["reports", "daily", "sales"],
+ "is_active": True
+ }
+
+ response = requests.post(url, headers=headers, json=data)
+
+ if response.status_code == 201:
+ print("Cron job created:", response.json())
+ else:
+ print("Error:", response.status_code, response.json())
+
+create_cron_job()
+```
+
+
+
+
+
+```json
+{
+ "data": {
+ "id": "550e8400-e29b-41d4-a716-446655440000",
+ "name": "Daily Sales Report",
+ "description": "Generate comprehensive sales report",
+ "endpoint_url": "https://api.company.com/reports/daily",
+ "schedule": "0 9 * * *",
+ "method": "POST",
+ "headers": {
+ "Authorization": "Bearer report-token",
+ "Content-Type": "application/json"
+ },
+ "body": "{\"report_type\": \"sales\", \"format\": \"pdf\"}",
+ "is_active": true,
+ "timezone": "America/New_York",
+ "repeat_type": "days",
+ "repeat_every": 1,
+ "days_of_week": null,
+ "hours": 9,
+ "minutes": 0,
+ "end_type": "never",
+ "end_date": null,
+ "end_occurrences": null,
+ "max_retries": 3,
+ "retry_delay_seconds": 300,
+ "timeout_seconds": 30,
+ "success_callback_url": "https://webhooks.company.com/cron-success",
+ "failure_callback_url": "https://webhooks.company.com/cron-failure",
+ "tags": ["reports", "daily", "sales"],
+ "next_run_at": "2024-01-26T14:00:00Z",
+ "last_run_at": null,
+ "consecutive_failures": 0,
+ "created_at": "2024-01-25T10:30:00Z",
+ "updated_at": "2024-01-25T10:30:00Z"
+ }
+}
+```
+
+
+
+## Common Schedule Examples
+
+
+
+ ```json
+ {
+ "repeat_type": "days",
+ "repeat_every": 1,
+ "time": "09:00"
+ }
+ ```
+
+
+
+ ```json
+ {
+ "repeat_type": "hourly",
+ "repeat_every": 2,
+ "time": "00:00"
+ }
+ ```
+
+
+
+ ```json
+ {
+ "repeat_type": "weeks",
+ "repeat_every": 1,
+ "time": "18:00",
+ "days_of_week": ["1", "2", "3", "4", "5"]
+ }
+ ```
+
+
+
+ ```json
+ {
+ "repeat_type": "months",
+ "repeat_every": 1,
+ "time": "00:00"
+ }
+ ```
+
+
+
+ ```json
+ {
+ "repeat_type": "days",
+ "repeat_every": 1,
+ "time": "10:00",
+ "end_type": "after",
+ "end_occurrences": 30
+ }
+ ```
+
+
\ No newline at end of file
diff --git a/a1cron/delete.mdx b/a1cron/delete.mdx
new file mode 100644
index 0000000..d10282e
--- /dev/null
+++ b/a1cron/delete.mdx
@@ -0,0 +1,146 @@
+---
+title: "Delete Cron Job"
+openapi: "DELETE /v1/cron-jobs/{accountId}/delete/{cron_job_id}"
+---
+
+Permanently delete a cron job. This action cannot be undone.
+
+## Path Parameters
+
+
+ Your A1Base account ID
+
+
+
+ The unique identifier of the cron job to delete (UUID format)
+
+
+## Response
+
+Returns 204 No Content on successful deletion.
+
+
+Deleting a cron job is permanent and cannot be undone. All associated execution history will also be deleted.
+
+
+
+
+```bash cURL
+curl -X DELETE "https://api.a1base.com/v1/cron-jobs/{accountId}/delete/550e8400-e29b-41d4-a716-446655440000" \
+ -H "X-API-Key: your-api-key" \
+ -H "X-API-Secret: your-api-secret"
+```
+
+```javascript Node.js
+const axios = require('axios');
+
+const deleteCronJob = async (cronJobId) => {
+ try {
+ const response = await axios.delete(
+ `https://api.a1base.com/v1/cron-jobs/{accountId}/delete/${cronJobId}`,
+ {
+ headers: {
+ 'X-API-Key': 'your-api-key',
+ 'X-API-Secret': 'your-api-secret'
+ }
+ }
+ );
+
+ console.log('Cron job deleted successfully');
+ } catch (error) {
+ console.error('Error:', error.response.data);
+ }
+};
+
+deleteCronJob('550e8400-e29b-41d4-a716-446655440000');
+```
+
+```python Python
+import requests
+
+def delete_cron_job(cron_job_id):
+ url = f"https://api.a1base.com/v1/cron-jobs/{{accountId}}/delete/{cron_job_id}"
+
+ headers = {
+ "X-API-Key": "your-api-key",
+ "X-API-Secret": "your-api-secret"
+ }
+
+ response = requests.delete(url, headers=headers)
+
+ if response.status_code == 204:
+ print("Cron job deleted successfully")
+ else:
+ print("Error:", response.status_code, response.json())
+
+delete_cron_job("550e8400-e29b-41d4-a716-446655440000")
+```
+
+
+
+
+
+```text
+204 No Content
+```
+
+
+
+## Before Deleting
+
+Consider these alternatives before deleting a cron job:
+
+
+
+ Instead of deleting, you can deactivate the cron job:
+ ```json
+ PATCH /v1/cron-jobs/{accountId}/update/{cron_job_id}
+ {
+ "is_active": false
+ }
+ ```
+
+
+
+ Get the full configuration before deleting:
+ ```bash
+ GET /v1/cron-jobs/{accountId}/details/{cron_job_id}
+ ```
+ Save the response to recreate the job later if needed.
+
+
+
+ Export execution history before deletion:
+ ```bash
+ GET /v1/cron-jobs/{accountId}/logs/{cron_job_id}?limit=100
+ ```
+
+
+
+## Error Responses
+
+
+ Cron job with the specified ID does not exist
+
+ ```json
+ {
+ "detail": [
+ {
+ "loc": ["path", "cron_job_id"],
+ "msg": "Cron job not found",
+ "type": "not_found"
+ }
+ ]
+ }
+ ```
+
+
+
+ Invalid or missing API credentials
+
+ ```json
+ {
+ "detail": "Invalid API credentials"
+ }
+ ```
+
\ No newline at end of file
diff --git a/a1cron/examples.mdx b/a1cron/examples.mdx
new file mode 100644
index 0000000..fe61fb7
--- /dev/null
+++ b/a1cron/examples.mdx
@@ -0,0 +1,502 @@
+---
+title: "Examples"
+description: "Real-world examples and patterns for A1Cron"
+---
+
+# Examples
+
+Explore practical examples of how to use A1Cron for various automation scenarios.
+
+## Daily Operations
+
+### Daily Sales Report
+
+Generate a PDF report every morning at 9 AM EST:
+
+```json
+{
+ "name": "Daily Sales Report",
+ "description": "Generates comprehensive sales report for the previous day",
+ "endpoint_url": "https://api.company.com/reports/daily-sales",
+ "method": "POST",
+ "headers": {
+ "Authorization": "Bearer ${REPORT_API_KEY}",
+ "Content-Type": "application/json"
+ },
+ "body": "{\"report_type\": \"sales\", \"format\": \"pdf\", \"date\": \"yesterday\"}",
+ "timezone": "America/New_York",
+ "schedule_config": {
+ "repeat_type": "days",
+ "repeat_every": 1,
+ "time": "09:00",
+ "end_type": "never"
+ },
+ "retry_config": {
+ "max_retries": 3,
+ "retry_delay_seconds": 300,
+ "timeout_seconds": 120
+ },
+ "callbacks": {
+ "success_url": "https://api.company.com/webhooks/report-generated",
+ "failure_url": "https://api.company.com/webhooks/report-failed"
+ },
+ "tags": ["reports", "daily", "sales", "production"]
+}
+```
+
+### Database Backup
+
+Nightly database backup at 2 AM:
+
+```json
+{
+ "name": "Nightly Database Backup",
+ "description": "Backup production database to S3",
+ "endpoint_url": "https://api.company.com/operations/backup",
+ "method": "POST",
+ "headers": {
+ "X-API-Key": "${BACKUP_API_KEY}"
+ },
+ "body": "{\"database\": \"production\", \"destination\": \"s3\", \"compression\": true}",
+ "timezone": "UTC",
+ "schedule_config": {
+ "repeat_type": "days",
+ "repeat_every": 1,
+ "time": "02:00",
+ "end_type": "never"
+ },
+ "retry_config": {
+ "max_retries": 5,
+ "retry_delay_seconds": 600,
+ "timeout_seconds": 300
+ },
+ "callbacks": {
+ "failure_url": "https://alerts.company.com/critical/backup-failed"
+ },
+ "tags": ["backup", "database", "critical"]
+}
+```
+
+## Weekly Operations
+
+### Weekly Team Summary
+
+Send team performance summary every Monday at 8 AM:
+
+```json
+{
+ "name": "Weekly Team Performance",
+ "description": "Email team performance metrics to managers",
+ "endpoint_url": "https://api.company.com/reports/team-weekly",
+ "method": "POST",
+ "headers": {
+ "Content-Type": "application/json",
+ "Authorization": "Bearer ${REPORTS_TOKEN}"
+ },
+ "body": "{\"teams\": [\"sales\", \"support\", \"engineering\"], \"send_email\": true}",
+ "timezone": "America/Chicago",
+ "schedule_config": {
+ "repeat_type": "weeks",
+ "repeat_every": 1,
+ "time": "08:00",
+ "days_of_week": ["1"],
+ "end_type": "never"
+ },
+ "tags": ["reports", "weekly", "management"]
+}
+```
+
+### Weekday Data Sync
+
+Sync data between systems every weekday at 6 PM:
+
+```json
+{
+ "name": "Weekday CRM Sync",
+ "description": "Sync customer data from CRM to data warehouse",
+ "endpoint_url": "https://api.company.com/sync/crm-to-warehouse",
+ "method": "POST",
+ "headers": {
+ "X-Sync-Token": "${SYNC_TOKEN}"
+ },
+ "body": "{\"mode\": \"incremental\", \"since\": \"last_sync\"}",
+ "timezone": "America/Los_Angeles",
+ "schedule_config": {
+ "repeat_type": "weeks",
+ "repeat_every": 1,
+ "time": "18:00",
+ "days_of_week": ["1", "2", "3", "4", "5"],
+ "end_type": "never"
+ },
+ "retry_config": {
+ "max_retries": 3,
+ "retry_delay_seconds": 900,
+ "timeout_seconds": 180
+ },
+ "tags": ["sync", "crm", "data-warehouse"]
+}
+```
+
+## Hourly Operations
+
+### API Health Check
+
+Monitor API availability every hour:
+
+```json
+{
+ "name": "Production API Health Check",
+ "description": "Check if production API is responding",
+ "endpoint_url": "https://api.company.com/health",
+ "method": "GET",
+ "timezone": "UTC",
+ "schedule_config": {
+ "repeat_type": "hourly",
+ "repeat_every": 1,
+ "time": "00:00",
+ "end_type": "never"
+ },
+ "retry_config": {
+ "max_retries": 2,
+ "retry_delay_seconds": 30,
+ "timeout_seconds": 10
+ },
+ "callbacks": {
+ "failure_url": "https://alerts.pagerduty.com/webhook/api-down"
+ },
+ "tags": ["monitoring", "health", "critical"]
+}
+```
+
+### Cache Refresh
+
+Update cache every 2 hours:
+
+```json
+{
+ "name": "Product Cache Refresh",
+ "description": "Refresh product catalog cache",
+ "endpoint_url": "https://api.company.com/cache/refresh",
+ "method": "POST",
+ "headers": {
+ "X-Cache-Key": "${CACHE_KEY}"
+ },
+ "body": "{\"cache_type\": \"products\", \"force\": true}",
+ "timezone": "UTC",
+ "schedule_config": {
+ "repeat_type": "hourly",
+ "repeat_every": 2,
+ "time": "00:30",
+ "end_type": "never"
+ },
+ "tags": ["cache", "products", "performance"]
+}
+```
+
+## Monthly Operations
+
+### Monthly Billing
+
+Process monthly subscriptions on the 1st:
+
+```json
+{
+ "name": "Monthly Subscription Billing",
+ "description": "Process all active monthly subscriptions",
+ "endpoint_url": "https://api.company.com/billing/process-monthly",
+ "method": "POST",
+ "headers": {
+ "Authorization": "Bearer ${BILLING_API_KEY}",
+ "Content-Type": "application/json"
+ },
+ "body": "{\"billing_cycle\": \"monthly\", \"retry_failed\": true}",
+ "timezone": "America/New_York",
+ "schedule_config": {
+ "repeat_type": "months",
+ "repeat_every": 1,
+ "time": "04:00",
+ "end_type": "never"
+ },
+ "retry_config": {
+ "max_retries": 5,
+ "retry_delay_seconds": 3600,
+ "timeout_seconds": 300
+ },
+ "callbacks": {
+ "success_url": "https://api.company.com/webhooks/billing-complete",
+ "failure_url": "https://api.company.com/webhooks/billing-failed"
+ },
+ "tags": ["billing", "monthly", "financial", "critical"]
+}
+```
+
+### Monthly Report Archive
+
+Archive old reports on the 1st of each month:
+
+```json
+{
+ "name": "Monthly Report Archive",
+ "description": "Move reports older than 90 days to cold storage",
+ "endpoint_url": "https://api.company.com/archive/reports",
+ "method": "POST",
+ "body": "{\"older_than_days\": 90, \"destination\": \"glacier\"}",
+ "timezone": "UTC",
+ "schedule_config": {
+ "repeat_type": "months",
+ "repeat_every": 1,
+ "time": "03:00",
+ "end_type": "never"
+ },
+ "tags": ["archive", "storage", "maintenance"]
+}
+```
+
+## Limited Duration Campaigns
+
+### 30-Day Marketing Campaign
+
+Send daily campaign emails for 30 days:
+
+```json
+{
+ "name": "Summer Sale Campaign",
+ "description": "Send daily promotional emails for summer sale",
+ "endpoint_url": "https://api.company.com/campaigns/summer-sale/send",
+ "method": "POST",
+ "headers": {
+ "X-Campaign-ID": "summer-2024"
+ },
+ "body": "{\"segment\": \"all_subscribers\", \"template\": \"summer_sale_daily\"}",
+ "timezone": "America/New_York",
+ "schedule_config": {
+ "repeat_type": "days",
+ "repeat_every": 1,
+ "time": "10:00",
+ "end_type": "after",
+ "end_occurrences": 30
+ },
+ "callbacks": {
+ "success_url": "https://api.company.com/campaigns/webhook/sent"
+ },
+ "tags": ["campaign", "marketing", "summer-sale", "temporary"]
+}
+```
+
+### Trial Period Reminders
+
+Send reminders during 14-day trial:
+
+```json
+{
+ "name": "Trial Reminder Sequence",
+ "description": "Send trial reminders on days 3, 7, and 13",
+ "endpoint_url": "https://api.company.com/trials/send-reminder",
+ "method": "POST",
+ "headers": {
+ "Authorization": "Bearer ${TRIAL_API_KEY}"
+ },
+ "body": "{\"trial_id\": \"${TRIAL_ID}\", \"reminder_type\": \"scheduled\"}",
+ "timezone": "UTC",
+ "schedule_config": {
+ "repeat_type": "days",
+ "repeat_every": 1,
+ "time": "14:00",
+ "end_type": "on",
+ "end_date": "2024-12-31T23:59:59Z"
+ },
+ "tags": ["trial", "onboarding", "temporary"]
+}
+```
+
+## Complex Scheduling Patterns
+
+### Business Hours Only
+
+Run every 30 minutes during business hours (9 AM - 5 PM weekdays):
+
+```json
+{
+ "name": "Business Hours Sync",
+ "description": "Sync data every 30 minutes during business hours",
+ "endpoint_url": "https://api.company.com/sync/realtime",
+ "method": "POST",
+ "timezone": "America/New_York",
+ "schedule_config": {
+ "repeat_type": "hourly",
+ "repeat_every": 1,
+ "time": "00:00",
+ "end_type": "never"
+ },
+ "headers": {
+ "X-Sync-Mode": "business-hours"
+ },
+ "tags": ["sync", "business-hours"]
+}
+```
+
+
+For true 30-minute intervals during business hours only, you would need to create multiple cron jobs or implement the logic in your endpoint.
+
+
+### Quarterly Reports
+
+Generate reports on the first day of each quarter:
+
+```json
+{
+ "name": "Quarterly Financial Report",
+ "description": "Generate comprehensive quarterly financial report",
+ "endpoint_url": "https://api.company.com/reports/quarterly-financial",
+ "method": "POST",
+ "headers": {
+ "Authorization": "Bearer ${FINANCIAL_API_KEY}"
+ },
+ "body": "{\"report_type\": \"quarterly\", \"include_projections\": true}",
+ "timezone": "America/New_York",
+ "schedule_config": {
+ "repeat_type": "months",
+ "repeat_every": 3,
+ "time": "06:00",
+ "end_type": "never"
+ },
+ "retry_config": {
+ "max_retries": 5,
+ "retry_delay_seconds": 1800,
+ "timeout_seconds": 600
+ },
+ "callbacks": {
+ "success_url": "https://api.company.com/webhooks/quarterly-report-ready"
+ },
+ "tags": ["reports", "quarterly", "financial", "executive"]
+}
+```
+
+## Error Handling Examples
+
+### With Exponential Backoff
+
+Implement exponential backoff using retry configuration:
+
+```json
+{
+ "name": "Data Export with Backoff",
+ "description": "Export data with exponential retry delays",
+ "endpoint_url": "https://api.company.com/export/large-dataset",
+ "method": "POST",
+ "timezone": "UTC",
+ "schedule_config": {
+ "repeat_type": "days",
+ "repeat_every": 1,
+ "time": "01:00",
+ "end_type": "never"
+ },
+ "retry_config": {
+ "max_retries": 5,
+ "retry_delay_seconds": 60,
+ "timeout_seconds": 600
+ },
+ "callbacks": {
+ "failure_url": "https://api.company.com/alerts/export-failed"
+ },
+ "tags": ["export", "data", "large"]
+}
+```
+
+### With Different Failure Handling
+
+Different callbacks for different failure scenarios:
+
+```json
+{
+ "name": "Critical Payment Processing",
+ "description": "Process pending payments with comprehensive error handling",
+ "endpoint_url": "https://api.company.com/payments/process-pending",
+ "method": "POST",
+ "headers": {
+ "X-Payment-Key": "${PAYMENT_KEY}",
+ "X-Idempotency-Key": "${TIMESTAMP}"
+ },
+ "timezone": "America/New_York",
+ "schedule_config": {
+ "repeat_type": "hourly",
+ "repeat_every": 1,
+ "time": "00:15",
+ "end_type": "never"
+ },
+ "retry_config": {
+ "max_retries": 3,
+ "retry_delay_seconds": 300,
+ "timeout_seconds": 120
+ },
+ "callbacks": {
+ "success_url": "https://api.company.com/webhooks/payments-processed",
+ "failure_url": "https://alerts.company.com/critical/payment-processing-failed"
+ },
+ "tags": ["payments", "critical", "financial"]
+}
+```
+
+## Best Practices Examples
+
+### Using Tags Effectively
+
+```json
+{
+ "name": "Production Data Sync",
+ "tags": ["production", "sync", "critical", "team:data", "owner:john.doe"]
+}
+```
+
+### Idempotent Endpoints
+
+```json
+{
+ "headers": {
+ "X-Idempotency-Key": "cron-${JOB_ID}-${EXECUTION_TIME}"
+ }
+}
+```
+
+### Environment-Specific Configurations
+
+```json
+{
+ "name": "[PROD] Daily Cleanup",
+ "endpoint_url": "https://api.production.company.com/cleanup",
+ "tags": ["environment:production", "cleanup", "automated"]
+}
+```
+
+## Testing Patterns
+
+### Dry Run Mode
+
+```json
+{
+ "name": "Report Generator - Dry Run",
+ "endpoint_url": "https://api.company.com/reports/generate",
+ "headers": {
+ "X-Dry-Run": "true"
+ },
+ "body": "{\"mode\": \"test\", \"send_notifications\": false}",
+ "tags": ["test", "dry-run"]
+}
+```
+
+### Sandbox Environment
+
+```json
+{
+ "name": "[SANDBOX] Payment Test",
+ "endpoint_url": "https://sandbox.company.com/api/payments/test",
+ "headers": {
+ "X-Environment": "sandbox"
+ },
+ "tags": ["sandbox", "test", "payments"]
+}
+```
+
+## Need Help?
+
+If you need help implementing any of these patterns or have questions about your specific use case, contact our support team at [pennie@a1base.com](mailto:pennie@a1base.com).
\ No newline at end of file
diff --git a/a1cron/get-details.mdx b/a1cron/get-details.mdx
new file mode 100644
index 0000000..0d6a157
--- /dev/null
+++ b/a1cron/get-details.mdx
@@ -0,0 +1,238 @@
+---
+title: "Get Cron Job Details"
+openapi: "GET /v1/cron-jobs/{accountId}/details/{cron_job_id}"
+---
+
+Get comprehensive details about a specific cron job including full configuration and execution history.
+
+## Path Parameters
+
+
+ Your A1Base account ID
+
+
+
+ The unique identifier of the cron job (UUID format)
+
+
+## Response
+
+
+ Detailed cron job information
+
+
+
+ Unique identifier for the cron job
+
+
+
+ Name of the cron job
+
+
+
+ Description of what the cron job does
+
+
+
+ The URL that will be called when the cron job executes
+
+
+
+ Cron expression representing the schedule
+
+
+
+ HTTP method used for the request (GET, POST, PUT, DELETE)
+
+
+
+ HTTP headers to include with each request
+
+
+
+ Request body for POST/PUT methods
+
+
+
+ Whether the cron job is currently active
+
+
+
+ Timezone for the cron job execution
+
+
+
+ Type of repetition: hourly, days, weeks, months, years
+
+
+
+ Frequency of repetition (e.g., every 2 days)
+
+
+
+ Array of day numbers (0-6) for weekly schedules
+
+
+
+ Hour component of the scheduled time (0-23)
+
+
+
+ Minute component of the scheduled time (0-59)
+
+
+
+ How the schedule ends: never, on, after
+
+
+
+ ISO 8601 timestamp when the schedule ends (if end_type is "on")
+
+
+
+ Number of occurrences before ending (if end_type is "after")
+
+
+
+ Maximum number of retry attempts on failure
+
+
+
+ Seconds to wait between retry attempts
+
+
+
+ Maximum seconds to wait for endpoint response
+
+
+
+ Webhook URL to call on successful execution
+
+
+
+ Webhook URL to call on failed execution
+
+
+
+ Array of tags for organization
+
+
+
+ ISO 8601 timestamp of the next scheduled execution
+
+
+
+ ISO 8601 timestamp of the last execution
+
+
+
+ Number of consecutive failed executions
+
+
+
+ ISO 8601 timestamp of when the cron job was created
+
+
+
+ ISO 8601 timestamp of the last update
+
+
+
+
+
+
+```bash cURL
+curl -X GET "https://api.a1base.com/v1/cron-jobs/{accountId}/details/550e8400-e29b-41d4-a716-446655440000" \
+ -H "X-API-Key: your-api-key" \
+ -H "X-API-Secret: your-api-secret"
+```
+
+```javascript Node.js
+const axios = require('axios');
+
+const getCronJobDetails = async (cronJobId) => {
+ try {
+ const response = await axios.get(
+ `https://api.a1base.com/v1/cron-jobs/{accountId}/details/${cronJobId}`,
+ {
+ headers: {
+ 'X-API-Key': 'your-api-key',
+ 'X-API-Secret': 'your-api-secret'
+ }
+ }
+ );
+
+ console.log('Cron job details:', response.data);
+ } catch (error) {
+ console.error('Error:', error.response.data);
+ }
+};
+
+getCronJobDetails('550e8400-e29b-41d4-a716-446655440000');
+```
+
+```python Python
+import requests
+
+def get_cron_job_details(cron_job_id):
+ url = f"https://api.a1base.com/v1/cron-jobs/{{accountId}}/details/{cron_job_id}"
+
+ headers = {
+ "X-API-Key": "your-api-key",
+ "X-API-Secret": "your-api-secret"
+ }
+
+ response = requests.get(url, headers=headers)
+
+ if response.status_code == 200:
+ print("Cron job details:", response.json())
+ else:
+ print("Error:", response.status_code, response.json())
+
+get_cron_job_details("550e8400-e29b-41d4-a716-446655440000")
+```
+
+
+
+
+
+```json
+{
+ "data": {
+ "id": "550e8400-e29b-41d4-a716-446655440000",
+ "name": "Daily Sales Report",
+ "description": "Generate comprehensive sales report for the previous day",
+ "endpoint_url": "https://api.company.com/reports/daily",
+ "schedule": "0 9 * * *",
+ "method": "POST",
+ "headers": {
+ "Authorization": "Bearer report-api-token",
+ "Content-Type": "application/json"
+ },
+ "body": "{\"report_type\": \"sales\", \"format\": \"pdf\"}",
+ "is_active": true,
+ "timezone": "America/New_York",
+ "repeat_type": "days",
+ "repeat_every": 1,
+ "days_of_week": null,
+ "hours": 9,
+ "minutes": 0,
+ "end_type": "never",
+ "end_date": null,
+ "end_occurrences": null,
+ "max_retries": 3,
+ "retry_delay_seconds": 300,
+ "timeout_seconds": 30,
+ "success_callback_url": "https://api.company.com/webhooks/report-success",
+ "failure_callback_url": "https://api.company.com/webhooks/report-failure",
+ "tags": ["reports", "daily", "sales"],
+ "next_run_at": "2024-01-26T14:00:00Z",
+ "last_run_at": "2024-01-25T14:00:00Z",
+ "consecutive_failures": 0,
+ "created_at": "2024-01-01T00:00:00Z",
+ "updated_at": "2024-01-20T10:30:00Z"
+ }
+}
+```
+
+
\ No newline at end of file
diff --git a/a1cron/get-logs.mdx b/a1cron/get-logs.mdx
new file mode 100644
index 0000000..f25b8d5
--- /dev/null
+++ b/a1cron/get-logs.mdx
@@ -0,0 +1,289 @@
+---
+title: "Get Execution Logs"
+openapi: "GET /v1/cron-jobs/{accountId}/logs/{cron_job_id}"
+---
+
+Retrieve the execution history and logs for a specific cron job. This endpoint provides detailed information about past executions, including success/failure status, response codes, and error messages.
+
+## Path Parameters
+
+
+ Your A1Base account ID
+
+
+
+ The unique identifier of the cron job (UUID format)
+
+
+## Query Parameters
+
+
+ Page number for pagination (minimum: 1)
+
+
+
+ Number of items per page (minimum: 1, maximum: 100)
+
+
+
+ Filter by execution status: `success`, `failure`, `error`, `timeout`
+
+
+
+ Filter logs after this date (ISO 8601 format)
+
+ Example: `2024-01-01T00:00:00Z`
+
+
+
+ Filter logs before this date (ISO 8601 format)
+
+ Example: `2024-01-31T23:59:59Z`
+
+
+## Response
+
+
+ Array of execution log objects
+
+
+
+ Unique identifier for the execution
+
+
+
+ ID of the cron job that was executed
+
+
+
+ ISO 8601 timestamp of when the job was executed
+
+
+
+ Execution status: `success`, `failure`, `error`, `timeout`
+
+
+
+ HTTP response code from the endpoint (null for timeouts)
+
+
+
+ Response body from the endpoint (truncated to 1KB)
+
+
+
+ Error message if the execution failed
+
+
+
+
+
+ Pagination information
+
+
+
+ Current page number
+
+
+
+ Items per page
+
+
+
+ Total number of log entries
+
+
+
+ Total number of pages
+
+
+
+
+
+
+```bash cURL
+curl -X GET "https://api.a1base.com/v1/cron-jobs/{accountId}/logs/550e8400-e29b-41d4-a716-446655440000?page=1&limit=20&status=failure" \
+ -H "X-API-Key: your-api-key" \
+ -H "X-API-Secret: your-api-secret"
+```
+
+```javascript Node.js
+const axios = require('axios');
+
+const getCronJobLogs = async (cronJobId) => {
+ try {
+ const response = await axios.get(
+ `https://api.a1base.com/v1/cron-jobs/{accountId}/logs/${cronJobId}`,
+ {
+ params: {
+ page: 1,
+ limit: 20,
+ status: 'failure',
+ start_date: '2024-01-01T00:00:00Z'
+ },
+ headers: {
+ 'X-API-Key': 'your-api-key',
+ 'X-API-Secret': 'your-api-secret'
+ }
+ }
+ );
+
+ console.log('Execution logs:', response.data);
+ } catch (error) {
+ console.error('Error:', error.response.data);
+ }
+};
+
+getCronJobLogs('550e8400-e29b-41d4-a716-446655440000');
+```
+
+```python Python
+import requests
+from datetime import datetime, timedelta
+
+def get_cron_job_logs(cron_job_id):
+ url = f"https://api.a1base.com/v1/cron-jobs/{{accountId}}/logs/{cron_job_id}"
+
+ headers = {
+ "X-API-Key": "your-api-key",
+ "X-API-Secret": "your-api-secret"
+ }
+
+ # Get logs from the last 7 days
+ start_date = (datetime.now() - timedelta(days=7)).isoformat() + "Z"
+
+ params = {
+ "page": 1,
+ "limit": 20,
+ "status": "failure",
+ "start_date": start_date
+ }
+
+ response = requests.get(url, headers=headers, params=params)
+
+ if response.status_code == 200:
+ data = response.json()
+ print(f"Found {data['pagination']['total_items']} log entries")
+ for log in data['data']:
+ print(f"{log['executed_at']}: {log['status']} - {log['response_code']}")
+ else:
+ print("Error:", response.status_code, response.json())
+
+get_cron_job_logs("550e8400-e29b-41d4-a716-446655440000")
+```
+
+
+
+
+
+```json
+{
+ "data": [
+ {
+ "id": "exe_123456789",
+ "cron_job_id": "550e8400-e29b-41d4-a716-446655440000",
+ "executed_at": "2024-01-25T14:00:00Z",
+ "status": "success",
+ "response_code": 200,
+ "response_body": "{\"message\": \"Report generated successfully\"}",
+ "error_message": null
+ },
+ {
+ "id": "exe_987654321",
+ "cron_job_id": "550e8400-e29b-41d4-a716-446655440000",
+ "executed_at": "2024-01-24T14:00:05Z",
+ "status": "failure",
+ "response_code": 500,
+ "response_body": "{\"error\": \"Database connection failed\"}",
+ "error_message": "Endpoint returned status code 500"
+ },
+ {
+ "id": "exe_456789123",
+ "cron_job_id": "550e8400-e29b-41d4-a716-446655440000",
+ "executed_at": "2024-01-23T14:00:00Z",
+ "status": "timeout",
+ "response_code": null,
+ "response_body": null,
+ "error_message": "Request timed out after 30 seconds"
+ }
+ ],
+ "pagination": {
+ "page": 1,
+ "limit": 20,
+ "total_items": 3,
+ "total_pages": 1
+ }
+}
+```
+
+
+
+## Log Analysis Examples
+
+
+
+ ```bash
+ GET /v1/cron-jobs/{accountId}/logs/{cron_job_id}?status=failure&limit=10
+ ```
+
+ Shows the 10 most recent failed executions.
+
+
+
+ ```bash
+ GET /v1/cron-jobs/{accountId}/logs/{cron_job_id}?start_date=2024-01-25T00:00:00Z&end_date=2024-01-25T23:59:59Z
+ ```
+
+ Get all executions for a specific day to calculate success rate.
+
+
+
+ ```bash
+ GET /v1/cron-jobs/{accountId}/logs/{cron_job_id}?status=timeout
+ ```
+
+ Find all executions that timed out to identify performance issues.
+
+
+
+ ```bash
+ GET /v1/cron-jobs/{accountId}/logs/{cron_job_id}?status=error&limit=50
+ ```
+
+ Analyze error patterns to improve reliability.
+
+
+
+## Understanding Status Codes
+
+
+ The endpoint returned a 2xx status code
+
+
+
+ The endpoint returned a 4xx or 5xx status code
+
+
+
+ Network error or invalid endpoint URL
+
+
+
+ The request exceeded the configured timeout
+
+
+## Best Practices
+
+
+1. **Regular Monitoring**: Check logs daily for critical jobs
+2. **Set Up Alerts**: Use failure callbacks to get notified immediately
+3. **Analyze Patterns**: Look for time-based failure patterns
+4. **Export Important Logs**: Download logs before they expire
+5. **Calculate Metrics**: Track success rates and response times
+
+
+## Log Retention
+
+
+Execution logs are retained for 30 days. Export important logs if you need longer retention.
+
\ No newline at end of file
diff --git a/a1cron/index.mdx b/a1cron/index.mdx
new file mode 100644
index 0000000..4cc04af
--- /dev/null
+++ b/a1cron/index.mdx
@@ -0,0 +1,85 @@
+---
+title: "A1Cron"
+description: "Schedule and automate HTTP requests with A1Base's powerful cron job system"
+---
+
+# A1Cron
+
+Welcome to A1Cron, A1Base's comprehensive cron job management system that allows you to schedule and automate HTTP requests with precision and reliability.
+
+
+
+ Learn about A1Cron features and capabilities
+
+
+ Get started with your first cron job in minutes
+
+
+ Explore the complete API documentation
+
+
+ See real-world use cases and patterns
+
+
+
+## Key Features
+
+
+
+ - **Hourly**: Run tasks every N hours
+ - **Daily**: Execute at specific times each day
+ - **Weekly**: Schedule on specific days of the week
+ - **Monthly**: Run on the first of each month
+ - **Custom**: Define your own schedule patterns
+
+
+
+ - Automatic retries with configurable delays
+ - Success and failure webhook callbacks
+ - Comprehensive execution logs
+ - Real-time status monitoring
+
+
+
+ - Create, update, and delete jobs via API
+ - Tag-based organization
+ - Active/inactive status control
+ - Manual trigger capability
+
+
+
+ - RESTful API design
+ - Detailed error messages
+ - Timezone support
+ - JSON request/response format
+
+
+
+## Common Use Cases
+
+- **Automated Reports**: Generate daily, weekly, or monthly reports
+- **Data Synchronization**: Keep systems in sync with scheduled updates
+- **Health Checks**: Monitor endpoints and services at regular intervals
+- **Cleanup Tasks**: Run maintenance operations during off-peak hours
+- **Notification Systems**: Send scheduled reminders and alerts
+- **Batch Processing**: Process data in scheduled batches
+
+## Getting Started
+
+
+
+ Obtain your API key and secret from the [A1Base Dashboard](https://a1base.com)
+
+
+
+ Use our [Create endpoint](/a1cron/create) to schedule your first task
+
+
+
+ Track your cron job's performance with our [logging system](/a1cron/get-logs)
+
+
+
+## Support
+
+Need help? Contact our support team at [pennie@a1base.com](mailto:pennie@a1base.com) or check out our [examples](/a1cron/examples) for common patterns and best practices.
\ No newline at end of file
diff --git a/a1cron/introduction.mdx b/a1cron/introduction.mdx
new file mode 100644
index 0000000..991eb47
--- /dev/null
+++ b/a1cron/introduction.mdx
@@ -0,0 +1,202 @@
+---
+title: "Introduction to A1Cron"
+description: "Understanding A1Base's cron job scheduling system"
+---
+
+# Introduction to A1Cron
+
+A1Cron is A1Base's powerful cron job management system that enables you to schedule and automate HTTP requests with precision. Whether you need to run daily reports, sync data between systems, or perform regular health checks, A1Cron provides the tools you need to automate these tasks reliably.
+
+## What is A1Cron?
+
+A1Cron allows you to:
+- Schedule HTTP requests to run at specific times and intervals
+- Monitor execution status and view detailed logs
+- Configure automatic retries for failed requests
+- Receive webhook notifications for job outcomes
+- Organize jobs with tags for easy management
+
+## Core Concepts
+
+### Cron Jobs
+A cron job is a scheduled task that executes an HTTP request at specified intervals. Each job includes:
+- **Endpoint URL**: The target URL to call
+- **Schedule**: When and how often to run
+- **HTTP Configuration**: Method, headers, and body
+- **Retry Settings**: How to handle failures
+- **Callbacks**: Webhooks for success/failure notifications
+
+### Scheduling Options
+
+
+
+ Run tasks every N hours. Perfect for:
+ - Regular data syncs
+ - Cache refreshing
+ - Monitoring checks
+
+ ```json
+ {
+ "repeat_type": "hourly",
+ "repeat_every": 2,
+ "time": "00:30"
+ }
+ ```
+
+
+
+ Execute at a specific time each day. Ideal for:
+ - Daily reports
+ - Backup operations
+ - End-of-day processing
+
+ ```json
+ {
+ "repeat_type": "days",
+ "repeat_every": 1,
+ "time": "09:00"
+ }
+ ```
+
+
+
+ Run on specific days of the week. Great for:
+ - Weekly summaries
+ - Business day operations
+ - Weekend maintenance
+
+ ```json
+ {
+ "repeat_type": "weeks",
+ "repeat_every": 1,
+ "time": "08:00",
+ "days_of_week": ["1", "3", "5"]
+ }
+ ```
+
+
+
+ Execute on the first of each month. Perfect for:
+ - Monthly billing
+ - Report generation
+ - Data archival
+
+ ```json
+ {
+ "repeat_type": "months",
+ "repeat_every": 1,
+ "time": "00:00"
+ }
+ ```
+
+
+
+### Timezone Support
+
+All cron jobs run in the timezone you specify. A1Cron supports all standard timezone identifiers:
+- `America/New_York`
+- `Europe/London`
+- `Asia/Tokyo`
+- `UTC`
+
+
+Timezone support ensures your jobs run at the correct local time, automatically adjusting for daylight saving time changes.
+
+
+### Retry Configuration
+
+Configure how A1Cron handles failed requests:
+
+```json
+{
+ "max_retries": 3,
+ "retry_delay_seconds": 60,
+ "timeout_seconds": 30
+}
+```
+
+- **max_retries**: Number of retry attempts (0-10)
+- **retry_delay_seconds**: Wait time between retries
+- **timeout_seconds**: Maximum time to wait for response
+
+### Webhook Callbacks
+
+Get notified about job execution results:
+
+```json
+{
+ "success_url": "https://your-app.com/webhooks/cron-success",
+ "failure_url": "https://your-app.com/webhooks/cron-failure"
+}
+```
+
+Callbacks receive POST requests with execution details:
+```json
+{
+ "cron_job_id": "550e8400-e29b-41d4-a716-446655440000",
+ "execution_id": "exe_123456",
+ "status": "success",
+ "executed_at": "2024-01-25T14:00:00Z",
+ "response_code": 200,
+ "response_time_ms": 245
+}
+```
+
+## Authentication
+
+All API requests require authentication headers:
+
+```bash
+X-API-Key: your-api-key
+X-API-Secret: your-api-secret
+```
+
+Get your credentials from the [A1Base Dashboard](https://a1base.com).
+
+## Base URL
+
+All A1Cron endpoints use the base URL:
+```
+https://api.a1base.com
+```
+
+## Rate Limits
+
+- **API Requests**: 1000 requests per hour
+- **Cron Jobs**: Maximum 100 active jobs per account
+- **Execution Frequency**: Minimum interval of 1 hour for hourly jobs
+
+## Best Practices
+
+
+
+ Your endpoint should handle being called multiple times safely. Use unique identifiers or timestamps to prevent duplicate processing.
+
+
+
+ Set appropriate timeout values and ensure your endpoints can complete within the configured time limit.
+
+
+
+ Tag your cron jobs by environment, purpose, or team to make management easier as your usage grows.
+
+
+
+ Regularly review execution logs to identify patterns and optimize performance.
+
+
+
+ Use the manual trigger feature to test your cron jobs before enabling scheduled execution.
+
+
+
+## Next Steps
+
+
+
+ Create your first cron job
+
+
+ Explore the complete API
+
+
\ No newline at end of file
diff --git a/a1cron/list.mdx b/a1cron/list.mdx
new file mode 100644
index 0000000..31bd160
--- /dev/null
+++ b/a1cron/list.mdx
@@ -0,0 +1,217 @@
+---
+title: "List Cron Jobs"
+openapi: "GET /v1/cron-jobs/{accountId}/list"
+---
+
+List all cron jobs for your company with optional filtering and pagination.
+
+## Query Parameters
+
+
+ Page number for pagination (minimum: 1)
+
+
+
+ Number of items per page (minimum: 1, maximum: 100)
+
+
+
+ Filter by active status. Set to `true` to show only active jobs, `false` for inactive jobs only.
+
+
+
+ Comma-separated list of tags to filter by. Jobs matching any of the specified tags will be returned.
+
+ Example: `tags=reports,daily`
+
+
+## Response
+
+
+ Array of cron job objects
+
+
+
+ Unique identifier for the cron job
+
+
+
+ Name of the cron job
+
+
+
+ The URL that will be called when the cron job executes
+
+
+
+ Cron expression representing the schedule
+
+
+
+ HTTP method used for the request
+
+
+
+ Whether the cron job is currently active
+
+
+
+ Timezone for the cron job execution
+
+
+
+ ISO 8601 timestamp of the next scheduled execution
+
+
+
+ ISO 8601 timestamp of the last execution (null if never run)
+
+
+
+ Number of consecutive failed executions
+
+
+
+ ISO 8601 timestamp of when the cron job was created
+
+
+
+ ISO 8601 timestamp of the last update
+
+
+
+
+
+ Pagination information
+
+
+
+ Current page number
+
+
+
+ Items per page
+
+
+
+ Total number of cron jobs
+
+
+
+ Total number of pages
+
+
+
+
+
+
+```bash cURL
+curl -X GET "https://api.a1base.com/v1/cron-jobs/{accountId}/list?page=1&limit=20&is_active=true&tags=reports,daily" \
+ -H "X-API-Key: your-api-key" \
+ -H "X-API-Secret: your-api-secret"
+```
+
+```javascript Node.js
+const axios = require('axios');
+
+const listCronJobs = async () => {
+ try {
+ const response = await axios.get(
+ 'https://api.a1base.com/v1/cron-jobs/{accountId}/list',
+ {
+ params: {
+ page: 1,
+ limit: 20,
+ is_active: true,
+ tags: 'reports,daily'
+ },
+ headers: {
+ 'X-API-Key': 'your-api-key',
+ 'X-API-Secret': 'your-api-secret'
+ }
+ }
+ );
+
+ console.log('Cron jobs:', response.data);
+ } catch (error) {
+ console.error('Error:', error.response.data);
+ }
+};
+
+listCronJobs();
+```
+
+```python Python
+import requests
+
+def list_cron_jobs():
+ url = "https://api.a1base.com/v1/cron-jobs/{accountId}/list"
+
+ headers = {
+ "X-API-Key": "your-api-key",
+ "X-API-Secret": "your-api-secret"
+ }
+
+ params = {
+ "page": 1,
+ "limit": 20,
+ "is_active": True,
+ "tags": "reports,daily"
+ }
+
+ response = requests.get(url, headers=headers, params=params)
+
+ if response.status_code == 200:
+ print("Cron jobs:", response.json())
+ else:
+ print("Error:", response.status_code, response.json())
+
+list_cron_jobs()
+```
+
+
+
+
+
+```json
+{
+ "data": [
+ {
+ "id": "550e8400-e29b-41d4-a716-446655440000",
+ "name": "Daily Sales Report",
+ "endpoint_url": "https://api.company.com/reports/daily",
+ "schedule": "0 9 * * *",
+ "method": "POST",
+ "is_active": true,
+ "timezone": "America/New_York",
+ "next_run_at": "2024-01-26T14:00:00Z",
+ "last_run_at": "2024-01-25T14:00:00Z",
+ "consecutive_failures": 0,
+ "created_at": "2024-01-01T00:00:00Z",
+ "updated_at": "2024-01-25T14:00:00Z"
+ },
+ {
+ "id": "660f9500-f38c-52e5-b827-557766551111",
+ "name": "Weekly Summary",
+ "endpoint_url": "https://api.company.com/reports/weekly",
+ "schedule": "0 8 * * 1",
+ "method": "GET",
+ "is_active": true,
+ "timezone": "UTC",
+ "next_run_at": "2024-01-29T08:00:00Z",
+ "last_run_at": "2024-01-22T08:00:00Z",
+ "consecutive_failures": 0,
+ "created_at": "2024-01-01T00:00:00Z",
+ "updated_at": "2024-01-22T08:00:00Z"
+ }
+ ],
+ "pagination": {
+ "page": 1,
+ "limit": 20,
+ "total_items": 2,
+ "total_pages": 1
+ }
+}
+```
+
+
\ No newline at end of file
diff --git a/a1cron/quickstart.mdx b/a1cron/quickstart.mdx
new file mode 100644
index 0000000..5f5c854
--- /dev/null
+++ b/a1cron/quickstart.mdx
@@ -0,0 +1,325 @@
+---
+title: "Quick Start"
+description: "Get up and running with A1Cron in minutes"
+---
+
+# Quick Start
+
+This guide will help you create and manage your first cron job with A1Cron. By the end, you'll have a scheduled task running automatically.
+
+## Prerequisites
+
+Before you begin, make sure you have:
+- An A1Base account
+- Your API key and secret from the [dashboard](https://a1base.com)
+- A publicly accessible endpoint URL to call
+
+## Step 1: Create Your First Cron Job
+
+Let's create a simple daily cron job that calls your endpoint every morning at 9 AM.
+
+
+
+```bash cURL
+curl -X POST https://api.a1base.com/v1/cron-jobs/{accountId}/create \
+ -H "X-API-Key: your-api-key" \
+ -H "X-API-Secret: your-api-secret" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "name": "Daily Morning Report",
+ "description": "Generate daily sales report",
+ "endpoint_url": "https://your-app.com/api/daily-report",
+ "method": "POST",
+ "headers": {
+ "Authorization": "Bearer your-token",
+ "Content-Type": "application/json"
+ },
+ "body": "{\"report_type\": \"daily\", \"format\": \"pdf\"}",
+ "timezone": "America/New_York",
+ "schedule_config": {
+ "repeat_type": "days",
+ "repeat_every": 1,
+ "time": "09:00",
+ "end_type": "never"
+ },
+ "retry_config": {
+ "max_retries": 3,
+ "retry_delay_seconds": 300,
+ "timeout_seconds": 30
+ },
+ "tags": ["reports", "daily"],
+ "is_active": true
+ }'
+```
+
+```javascript Node.js
+const axios = require('axios');
+
+const createCronJob = async () => {
+ try {
+ const response = await axios.post(
+ 'https://api.a1base.com/v1/cron-jobs/{accountId}/create',
+ {
+ name: 'Daily Morning Report',
+ description: 'Generate daily sales report',
+ endpoint_url: 'https://your-app.com/api/daily-report',
+ method: 'POST',
+ headers: {
+ 'Authorization': 'Bearer your-token',
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ report_type: 'daily',
+ format: 'pdf'
+ }),
+ timezone: 'America/New_York',
+ schedule_config: {
+ repeat_type: 'days',
+ repeat_every: 1,
+ time: '09:00',
+ end_type: 'never'
+ },
+ retry_config: {
+ max_retries: 3,
+ retry_delay_seconds: 300,
+ timeout_seconds: 30
+ },
+ tags: ['reports', 'daily'],
+ is_active: true
+ },
+ {
+ headers: {
+ 'X-API-Key': 'your-api-key',
+ 'X-API-Secret': 'your-api-secret',
+ 'Content-Type': 'application/json'
+ }
+ }
+ );
+
+ console.log('Cron job created:', response.data);
+ } catch (error) {
+ console.error('Error creating cron job:', error.response.data);
+ }
+};
+
+createCronJob();
+```
+
+```python Python
+import requests
+import json
+
+def create_cron_job():
+ url = "https://api.a1base.com/v1/cron-jobs/{accountId}/create"
+
+ headers = {
+ "X-API-Key": "your-api-key",
+ "X-API-Secret": "your-api-secret",
+ "Content-Type": "application/json"
+ }
+
+ data = {
+ "name": "Daily Morning Report",
+ "description": "Generate daily sales report",
+ "endpoint_url": "https://your-app.com/api/daily-report",
+ "method": "POST",
+ "headers": {
+ "Authorization": "Bearer your-token",
+ "Content-Type": "application/json"
+ },
+ "body": json.dumps({
+ "report_type": "daily",
+ "format": "pdf"
+ }),
+ "timezone": "America/New_York",
+ "schedule_config": {
+ "repeat_type": "days",
+ "repeat_every": 1,
+ "time": "09:00",
+ "end_type": "never"
+ },
+ "retry_config": {
+ "max_retries": 3,
+ "retry_delay_seconds": 300,
+ "timeout_seconds": 30
+ },
+ "tags": ["reports", "daily"],
+ "is_active": True
+ }
+
+ response = requests.post(url, headers=headers, json=data)
+
+ if response.status_code == 201:
+ print("Cron job created:", response.json())
+ else:
+ print("Error:", response.status_code, response.json())
+
+create_cron_job()
+```
+
+
+
+### Response
+
+```json
+{
+ "data": {
+ "id": "550e8400-e29b-41d4-a716-446655440000",
+ "name": "Daily Morning Report",
+ "description": "Generate daily sales report",
+ "endpoint_url": "https://your-app.com/api/daily-report",
+ "schedule": "0 9 * * *",
+ "method": "POST",
+ "is_active": true,
+ "timezone": "America/New_York",
+ "next_run_at": "2024-01-26T14:00:00Z",
+ "created_at": "2024-01-25T10:30:00Z"
+ }
+}
+```
+
+
+Save the returned `id` - you'll need it to manage this cron job later.
+
+
+## Step 2: Verify Your Cron Job
+
+Let's check that your cron job was created successfully:
+
+```bash
+curl -X GET https://api.a1base.com/v1/cron-jobs/{accountId}/details/{cron_job_id} \
+ -H "X-API-Key: your-api-key" \
+ -H "X-API-Secret: your-api-secret"
+```
+
+## Step 3: Test with Manual Trigger
+
+Before waiting for the scheduled time, test your cron job manually:
+
+```bash
+curl -X POST https://api.a1base.com/v1/cron-jobs/{accountId}/trigger/{cron_job_id} \
+ -H "X-API-Key: your-api-key" \
+ -H "X-API-Secret: your-api-secret"
+```
+
+This will immediately execute your cron job and return the result:
+
+```json
+{
+ "data": {
+ "execution_id": "exe_123456",
+ "status": "success",
+ "response_code": 200,
+ "response_body": "{\"message\": \"Report generated successfully\"}",
+ "executed_at": "2024-01-25T10:35:00Z"
+ }
+}
+```
+
+## Step 4: Monitor Execution Logs
+
+Check the execution history of your cron job:
+
+```bash
+curl -X GET "https://api.a1base.com/v1/cron-jobs/{accountId}/logs/{cron_job_id}?limit=10" \
+ -H "X-API-Key: your-api-key" \
+ -H "X-API-Secret: your-api-secret"
+```
+
+## Common Patterns
+
+### Hourly Health Check
+
+```json
+{
+ "name": "API Health Check",
+ "endpoint_url": "https://your-api.com/health",
+ "method": "GET",
+ "timezone": "UTC",
+ "schedule_config": {
+ "repeat_type": "hourly",
+ "repeat_every": 1,
+ "time": "00:00"
+ },
+ "callbacks": {
+ "failure_url": "https://your-app.com/alerts/health-check-failed"
+ }
+}
+```
+
+### Weekly Report on Business Days
+
+```json
+{
+ "name": "Weekday Summary",
+ "endpoint_url": "https://your-app.com/api/summary",
+ "method": "POST",
+ "timezone": "America/Chicago",
+ "schedule_config": {
+ "repeat_type": "weeks",
+ "repeat_every": 1,
+ "time": "17:00",
+ "days_of_week": ["1", "2", "3", "4", "5"]
+ }
+}
+```
+
+### Limited Duration Campaign
+
+```json
+{
+ "name": "30-Day Campaign",
+ "endpoint_url": "https://your-app.com/api/campaign",
+ "method": "POST",
+ "timezone": "America/Los_Angeles",
+ "schedule_config": {
+ "repeat_type": "days",
+ "repeat_every": 1,
+ "time": "10:00",
+ "end_type": "after",
+ "end_occurrences": 30
+ }
+}
+```
+
+## Troubleshooting
+
+
+
+ - Check that `is_active` is set to `true`
+ - Verify the timezone and scheduled time
+ - Ensure your endpoint URL is publicly accessible
+ - Check the execution logs for error messages
+
+
+
+ - Verify your API key and secret are correct
+ - Ensure you're including both headers in your requests
+ - Check that your account ID in the URL is correct
+
+
+
+ - Increase the `timeout_seconds` in your retry configuration
+ - Ensure your endpoint responds within the timeout period
+ - Consider optimizing your endpoint for faster response
+
+
+
+## Next Steps
+
+Now that you have a working cron job:
+
+
+
+ See more use cases and patterns
+
+
+ Deep dive into all endpoints
+
+
+ Learn about logs and monitoring
+
+
+ Configure success/failure callbacks
+
+
\ No newline at end of file
diff --git a/a1cron/trigger.mdx b/a1cron/trigger.mdx
new file mode 100644
index 0000000..98ec8ef
--- /dev/null
+++ b/a1cron/trigger.mdx
@@ -0,0 +1,205 @@
+---
+title: "Trigger Cron Job"
+openapi: "POST /v1/cron-jobs/{accountId}/trigger/{cron_job_id}"
+---
+
+Manually trigger a cron job execution immediately, regardless of its schedule. This is useful for testing or when you need to run a job outside its normal schedule.
+
+## Path Parameters
+
+
+ Your A1Base account ID
+
+
+
+ The unique identifier of the cron job to trigger (UUID format)
+
+
+## Response
+
+
+ Execution result information
+
+
+
+ Unique identifier for this execution
+
+
+
+ Execution status: `success`, `failure`, `error`, `timeout`
+
+
+
+ HTTP response code from the endpoint
+
+
+
+ Response body from the endpoint (truncated to 1KB)
+
+
+
+ ISO 8601 timestamp of when the job was executed
+
+
+
+
+
+Manual triggers bypass the schedule and execute immediately. The job must be active (`is_active: true`) to be triggered.
+
+
+
+
+```bash cURL
+curl -X POST "https://api.a1base.com/v1/cron-jobs/{accountId}/trigger/550e8400-e29b-41d4-a716-446655440000" \
+ -H "X-API-Key: your-api-key" \
+ -H "X-API-Secret: your-api-secret"
+```
+
+```javascript Node.js
+const axios = require('axios');
+
+const triggerCronJob = async (cronJobId) => {
+ try {
+ const response = await axios.post(
+ `https://api.a1base.com/v1/cron-jobs/{accountId}/trigger/${cronJobId}`,
+ {},
+ {
+ headers: {
+ 'X-API-Key': 'your-api-key',
+ 'X-API-Secret': 'your-api-secret'
+ }
+ }
+ );
+
+ console.log('Execution result:', response.data);
+ } catch (error) {
+ console.error('Error:', error.response.data);
+ }
+};
+
+triggerCronJob('550e8400-e29b-41d4-a716-446655440000');
+```
+
+```python Python
+import requests
+
+def trigger_cron_job(cron_job_id):
+ url = f"https://api.a1base.com/v1/cron-jobs/{{accountId}}/trigger/{cron_job_id}"
+
+ headers = {
+ "X-API-Key": "your-api-key",
+ "X-API-Secret": "your-api-secret"
+ }
+
+ response = requests.post(url, headers=headers)
+
+ if response.status_code == 200:
+ print("Execution result:", response.json())
+ else:
+ print("Error:", response.status_code, response.json())
+
+trigger_cron_job("550e8400-e29b-41d4-a716-446655440000")
+```
+
+
+
+
+
+```json Success Response
+{
+ "data": {
+ "execution_id": "exe_123456789",
+ "status": "success",
+ "response_code": 200,
+ "response_body": "{\"message\": \"Report generated successfully\", \"report_id\": \"rpt_98765\"}",
+ "executed_at": "2024-01-25T15:45:00Z"
+ }
+}
+```
+
+```json Failure Response
+{
+ "data": {
+ "execution_id": "exe_987654321",
+ "status": "failure",
+ "response_code": 500,
+ "response_body": "{\"error\": \"Database connection failed\"}",
+ "executed_at": "2024-01-25T15:45:00Z"
+ }
+}
+```
+
+```json Timeout Response
+{
+ "data": {
+ "execution_id": "exe_456789123",
+ "status": "timeout",
+ "response_code": null,
+ "response_body": "Request timed out after 30 seconds",
+ "executed_at": "2024-01-25T15:45:00Z"
+ }
+}
+```
+
+
+
+## Use Cases
+
+
+
+ Test your endpoint before enabling scheduled execution:
+ 1. Create the cron job with `is_active: false`
+ 2. Trigger it manually to verify it works
+ 3. Update to `is_active: true` when ready
+
+
+
+ Run scheduled tasks outside their normal schedule:
+ - Generate reports on demand
+ - Process data immediately
+ - Respond to user actions
+
+
+
+ When a scheduled execution fails:
+ 1. Check the logs to understand the issue
+ 2. Fix the problem
+ 3. Manually trigger to verify the fix
+
+
+
+ Use manual triggers for initial setup:
+ - Load historical data
+ - Populate caches
+ - Initialize systems
+
+
+
+## Important Notes
+
+
+- The cron job must be active (`is_active: true`) to be triggered
+- Manual triggers count towards your API rate limits
+- Execution follows the same retry logic as scheduled runs
+- Webhook callbacks will be triggered as configured
+
+
+## Error Responses
+
+
+ Cron job not found
+ ```json
+ {
+ "detail": "Cron job not found"
+ }
+ ```
+
+
+
+ Cron job is not active
+ ```json
+ {
+ "detail": "Cron job is not active"
+ }
+ ```
+
\ No newline at end of file
diff --git a/a1cron/update.mdx b/a1cron/update.mdx
new file mode 100644
index 0000000..2069fff
--- /dev/null
+++ b/a1cron/update.mdx
@@ -0,0 +1,321 @@
+---
+title: "Update Cron Job"
+openapi: "PATCH /v1/cron-jobs/{accountId}/update/{cron_job_id}"
+---
+
+Update an existing cron job. All fields are optional - only provide the fields you want to update.
+
+## Path Parameters
+
+
+ Your A1Base account ID
+
+
+
+ The unique identifier of the cron job to update (UUID format)
+
+
+## Request Body
+
+All fields are optional. Only include the fields you want to update.
+
+
+ New name for the cron job
+
+
+
+ New description
+
+
+
+ New URL to call
+
+
+
+ New HTTP method: GET, POST, PUT, DELETE
+
+
+
+ New HTTP headers (replaces all existing headers)
+
+
+
+ New request body for POST/PUT methods
+
+
+
+ New timezone
+
+
+
+ New schedule configuration (replaces entire schedule)
+
+
+
+ Type of repetition: `hourly`, `days`, `weeks`, `months`, `years`
+
+
+
+ Frequency of repetition
+
+
+
+ Time in 24-hour format "HH:MM"
+
+
+
+ For weekly schedules: array of day numbers ("0"-"6")
+
+
+
+ How the schedule ends: `never`, `on`, `after`
+
+
+
+ ISO 8601 timestamp when to stop
+
+
+
+ Number of occurrences before stopping
+
+
+
+
+
+ New retry configuration
+
+
+
+ Maximum retry attempts (0-10)
+
+
+
+ Seconds between retries
+
+
+
+ Response timeout in seconds
+
+
+
+
+
+ New webhook URLs
+
+
+
+ Success webhook URL
+
+
+
+ Failure webhook URL
+
+
+
+
+
+ New array of tags (replaces all existing tags)
+
+
+
+ Activate or deactivate the cron job
+
+
+## Response
+
+Returns the updated cron job object with all current values.
+
+
+
+```bash cURL
+curl -X PATCH "https://api.a1base.com/v1/cron-jobs/{accountId}/update/550e8400-e29b-41d4-a716-446655440000" \
+ -H "X-API-Key: your-api-key" \
+ -H "X-API-Secret: your-api-secret" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "name": "Updated Daily Report",
+ "schedule_config": {
+ "repeat_type": "weeks",
+ "repeat_every": 1,
+ "time": "10:00",
+ "days_of_week": ["1", "3", "5"],
+ "end_type": "never"
+ },
+ "is_active": false
+ }'
+```
+
+```javascript Node.js
+const axios = require('axios');
+
+const updateCronJob = async (cronJobId) => {
+ const updates = {
+ name: "Updated Daily Report",
+ schedule_config: {
+ repeat_type: "weeks",
+ repeat_every: 1,
+ time: "10:00",
+ days_of_week: ["1", "3", "5"],
+ end_type: "never"
+ },
+ is_active: false
+ };
+
+ try {
+ const response = await axios.patch(
+ `https://api.a1base.com/v1/cron-jobs/{accountId}/update/${cronJobId}`,
+ updates,
+ {
+ headers: {
+ 'X-API-Key': 'your-api-key',
+ 'X-API-Secret': 'your-api-secret',
+ 'Content-Type': 'application/json'
+ }
+ }
+ );
+
+ console.log('Cron job updated:', response.data);
+ } catch (error) {
+ console.error('Error:', error.response.data);
+ }
+};
+
+updateCronJob('550e8400-e29b-41d4-a716-446655440000');
+```
+
+```python Python
+import requests
+
+def update_cron_job(cron_job_id):
+ url = f"https://api.a1base.com/v1/cron-jobs/{{accountId}}/update/{cron_job_id}"
+
+ headers = {
+ "X-API-Key": "your-api-key",
+ "X-API-Secret": "your-api-secret",
+ "Content-Type": "application/json"
+ }
+
+ updates = {
+ "name": "Updated Daily Report",
+ "schedule_config": {
+ "repeat_type": "weeks",
+ "repeat_every": 1,
+ "time": "10:00",
+ "days_of_week": ["1", "3", "5"],
+ "end_type": "never"
+ },
+ "is_active": False
+ }
+
+ response = requests.patch(url, headers=headers, json=updates)
+
+ if response.status_code == 200:
+ print("Cron job updated:", response.json())
+ else:
+ print("Error:", response.status_code, response.json())
+
+update_cron_job("550e8400-e29b-41d4-a716-446655440000")
+```
+
+
+
+
+
+```json
+{
+ "data": {
+ "id": "550e8400-e29b-41d4-a716-446655440000",
+ "name": "Updated Daily Report",
+ "description": "Generate comprehensive sales report",
+ "endpoint_url": "https://api.company.com/reports/daily",
+ "schedule": "0 10 * * 1,3,5",
+ "method": "POST",
+ "headers": {
+ "Authorization": "Bearer report-token",
+ "Content-Type": "application/json"
+ },
+ "body": "{\"report_type\": \"sales\", \"format\": \"pdf\"}",
+ "is_active": false,
+ "timezone": "America/New_York",
+ "repeat_type": "weeks",
+ "repeat_every": 1,
+ "days_of_week": ["1", "3", "5"],
+ "hours": 10,
+ "minutes": 0,
+ "end_type": "never",
+ "end_date": null,
+ "end_occurrences": null,
+ "max_retries": 3,
+ "retry_delay_seconds": 300,
+ "timeout_seconds": 30,
+ "success_callback_url": "https://webhooks.company.com/cron-success",
+ "failure_callback_url": "https://webhooks.company.com/cron-failure",
+ "tags": ["reports", "daily", "sales"],
+ "next_run_at": null,
+ "last_run_at": "2024-01-25T14:00:00Z",
+ "consecutive_failures": 0,
+ "created_at": "2024-01-01T00:00:00Z",
+ "updated_at": "2024-01-25T15:30:00Z"
+ }
+}
+```
+
+
+
+## Common Update Scenarios
+
+
+
+ ```json
+ {
+ "schedule_config": {
+ "time": "14:30"
+ }
+ }
+ ```
+ Note: When updating schedule_config, you must provide all required fields for the schedule type.
+
+
+
+ ```json
+ {
+ "is_active": false
+ }
+ ```
+
+
+
+ ```json
+ {
+ "headers": {
+ "Authorization": "Bearer new-token",
+ "Content-Type": "application/json",
+ "X-Custom-Header": "value"
+ }
+ }
+ ```
+
+
+
+ ```json
+ {
+ "tags": ["updated", "production", "critical"]
+ }
+ ```
+
+
+
+ ```json
+ {
+ "callbacks": {
+ "success_url": "https://new-webhook.com/success",
+ "failure_url": "https://new-webhook.com/failure"
+ }
+ }
+ ```
+
+
+
+
+When updating `schedule_config`, provide the complete configuration object with all required fields for the schedule type, not just the fields you want to change.
+
\ No newline at end of file
diff --git a/a1cron/webhooks.mdx b/a1cron/webhooks.mdx
new file mode 100644
index 0000000..9c820c7
--- /dev/null
+++ b/a1cron/webhooks.mdx
@@ -0,0 +1,405 @@
+---
+title: "Webhooks"
+description: "Receive real-time notifications about cron job executions"
+---
+
+# Webhooks
+
+A1Cron supports webhook callbacks to notify your application about cron job execution results in real-time. You can configure separate URLs for successful and failed executions.
+
+## Webhook Configuration
+
+When creating or updating a cron job, you can specify webhook URLs in the `callbacks` object:
+
+```json
+{
+ "callbacks": {
+ "success_url": "https://your-app.com/webhooks/cron-success",
+ "failure_url": "https://your-app.com/webhooks/cron-failure"
+ }
+}
+```
+
+## Webhook Payload
+
+Both success and failure webhooks receive the same payload structure:
+
+
+ The unique identifier of the cron job
+
+
+
+ The name of the cron job
+
+
+
+ Unique identifier for this specific execution
+
+
+
+ Execution status: `success`, `failure`, `error`, or `timeout`
+
+
+
+ ISO 8601 timestamp of when the job was executed
+
+
+
+ HTTP response code from the endpoint (null for timeouts)
+
+
+
+ Time taken for the request in milliseconds
+
+
+
+ Response body from the endpoint (truncated to 1KB)
+
+
+
+ Error message if the execution failed (null for success)
+
+
+
+ Which retry attempt this was (0 for first attempt)
+
+
+
+ ISO 8601 timestamp of next retry (null if no more retries)
+
+
+## Webhook Examples
+
+### Success Webhook Payload
+
+```json
+{
+ "cron_job_id": "550e8400-e29b-41d4-a716-446655440000",
+ "cron_job_name": "Daily Sales Report",
+ "execution_id": "exe_123456789",
+ "status": "success",
+ "executed_at": "2024-01-25T14:00:00Z",
+ "response_code": 200,
+ "response_time_ms": 245,
+ "response_body": "{\"message\": \"Report generated successfully\", \"report_id\": \"rpt_98765\"}",
+ "error_message": null,
+ "retry_attempt": 0,
+ "next_retry_at": null
+}
+```
+
+### Failure Webhook Payload
+
+```json
+{
+ "cron_job_id": "550e8400-e29b-41d4-a716-446655440000",
+ "cron_job_name": "Daily Sales Report",
+ "execution_id": "exe_987654321",
+ "status": "failure",
+ "executed_at": "2024-01-25T14:00:00Z",
+ "response_code": 500,
+ "response_time_ms": 1245,
+ "response_body": "{\"error\": \"Database connection failed\"}",
+ "error_message": "Endpoint returned status code 500",
+ "retry_attempt": 1,
+ "next_retry_at": "2024-01-25T14:05:00Z"
+}
+```
+
+### Timeout Webhook Payload
+
+```json
+{
+ "cron_job_id": "550e8400-e29b-41d4-a716-446655440000",
+ "cron_job_name": "Daily Sales Report",
+ "execution_id": "exe_456789123",
+ "status": "timeout",
+ "executed_at": "2024-01-25T14:00:00Z",
+ "response_code": null,
+ "response_time_ms": 30000,
+ "response_body": null,
+ "error_message": "Request timed out after 30 seconds",
+ "retry_attempt": 2,
+ "next_retry_at": "2024-01-25T14:10:00Z"
+}
+```
+
+## Implementing Webhook Handlers
+
+### Node.js Express Example
+
+```javascript
+const express = require('express');
+const app = express();
+
+app.use(express.json());
+
+// Success webhook handler
+app.post('/webhooks/cron-success', (req, res) => {
+ const {
+ cron_job_id,
+ cron_job_name,
+ execution_id,
+ response_body,
+ executed_at
+ } = req.body;
+
+ console.log(`β Cron job "${cron_job_name}" executed successfully`);
+ console.log(`Execution ID: ${execution_id}`);
+ console.log(`Response: ${response_body}`);
+
+ // Process the successful execution
+ // e.g., update database, send notifications, etc.
+
+ res.status(200).json({ received: true });
+});
+
+// Failure webhook handler
+app.post('/webhooks/cron-failure', (req, res) => {
+ const {
+ cron_job_id,
+ cron_job_name,
+ execution_id,
+ status,
+ error_message,
+ retry_attempt,
+ next_retry_at
+ } = req.body;
+
+ console.error(`β Cron job "${cron_job_name}" failed`);
+ console.error(`Status: ${status}`);
+ console.error(`Error: ${error_message}`);
+ console.error(`Retry attempt: ${retry_attempt}`);
+
+ if (next_retry_at) {
+ console.log(`Next retry at: ${next_retry_at}`);
+ } else {
+ console.error('No more retries scheduled');
+ // Send alert to team
+ }
+
+ res.status(200).json({ received: true });
+});
+
+app.listen(3000, () => {
+ console.log('Webhook server listening on port 3000');
+});
+```
+
+### Python Flask Example
+
+```python
+from flask import Flask, request, jsonify
+import logging
+
+app = Flask(__name__)
+logging.basicConfig(level=logging.INFO)
+
+@app.route('/webhooks/cron-success', methods=['POST'])
+def handle_cron_success():
+ data = request.json
+
+ logging.info(f"β Cron job '{data['cron_job_name']}' executed successfully")
+ logging.info(f"Execution ID: {data['execution_id']}")
+ logging.info(f"Response: {data['response_body']}")
+
+ # Process the successful execution
+ # e.g., update database, send notifications, etc.
+
+ return jsonify({"received": True}), 200
+
+@app.route('/webhooks/cron-failure', methods=['POST'])
+def handle_cron_failure():
+ data = request.json
+
+ logging.error(f"β Cron job '{data['cron_job_name']}' failed")
+ logging.error(f"Status: {data['status']}")
+ logging.error(f"Error: {data['error_message']}")
+ logging.error(f"Retry attempt: {data['retry_attempt']}")
+
+ if data.get('next_retry_at'):
+ logging.info(f"Next retry at: {data['next_retry_at']}")
+ else:
+ logging.error('No more retries scheduled')
+ # Send alert to team
+ send_alert_to_team(data)
+
+ return jsonify({"received": True}), 200
+
+def send_alert_to_team(data):
+ # Implement your alerting logic here
+ pass
+
+if __name__ == '__main__':
+ app.run(port=3000)
+```
+
+## Webhook Security
+
+### Verify Webhook Signatures
+
+A1Cron signs webhook payloads using HMAC-SHA256. Verify the signature to ensure the webhook is from A1Cron:
+
+```javascript
+const crypto = require('crypto');
+
+function verifyWebhookSignature(payload, signature, secret) {
+ const expectedSignature = crypto
+ .createHmac('sha256', secret)
+ .update(JSON.stringify(payload))
+ .digest('hex');
+
+ return signature === expectedSignature;
+}
+
+app.post('/webhooks/cron-success', (req, res) => {
+ const signature = req.headers['x-a1cron-signature'];
+ const webhookSecret = process.env.WEBHOOK_SECRET;
+
+ if (!verifyWebhookSignature(req.body, signature, webhookSecret)) {
+ return res.status(401).json({ error: 'Invalid signature' });
+ }
+
+ // Process the webhook...
+});
+```
+
+### IP Whitelisting
+
+For additional security, whitelist A1Cron's webhook IP addresses:
+- `52.89.214.238`
+- `34.212.75.30`
+- `54.218.53.128`
+
+## Webhook Best Practices
+
+
+
+ Return a 2xx status code within 10 seconds. Process heavy operations asynchronously.
+
+ ```javascript
+ app.post('/webhooks/cron-success', (req, res) => {
+ // Acknowledge receipt immediately
+ res.status(200).json({ received: true });
+
+ // Process asynchronously
+ processWebhookAsync(req.body);
+ });
+ ```
+
+
+
+ Use the `execution_id` to prevent duplicate processing:
+
+ ```javascript
+ const processedExecutions = new Set();
+
+ app.post('/webhooks/cron-success', (req, res) => {
+ const { execution_id } = req.body;
+
+ if (processedExecutions.has(execution_id)) {
+ return res.status(200).json({ received: true });
+ }
+
+ processedExecutions.add(execution_id);
+ // Process the webhook...
+ });
+ ```
+
+
+
+ A1Cron retries failed webhook deliveries up to 3 times with exponential backoff.
+
+
+
+ Log all webhook receipts and failures for debugging:
+
+ ```javascript
+ app.post('/webhooks/*', (req, res, next) => {
+ console.log({
+ timestamp: new Date().toISOString(),
+ path: req.path,
+ body: req.body,
+ headers: req.headers
+ });
+ next();
+ });
+ ```
+
+
+
+## Common Use Cases
+
+### Alert on Failures
+
+```javascript
+app.post('/webhooks/cron-failure', async (req, res) => {
+ const { cron_job_name, error_message, retry_attempt } = req.body;
+
+ // Send immediate alert for critical jobs
+ if (cron_job_name.includes('critical')) {
+ await sendSlackAlert({
+ text: `π¨ Critical cron job failed: ${cron_job_name}`,
+ error: error_message,
+ attempt: retry_attempt
+ });
+ }
+
+ res.status(200).json({ received: true });
+});
+```
+
+### Track Execution Metrics
+
+```javascript
+app.post('/webhooks/cron-success', async (req, res) => {
+ const { cron_job_id, response_time_ms, executed_at } = req.body;
+
+ // Store metrics in time-series database
+ await metricsDB.insert({
+ job_id: cron_job_id,
+ timestamp: executed_at,
+ duration_ms: response_time_ms,
+ status: 'success'
+ });
+
+ res.status(200).json({ received: true });
+});
+```
+
+### Chain Dependent Jobs
+
+```javascript
+app.post('/webhooks/cron-success', async (req, res) => {
+ const { cron_job_name, response_body } = req.body;
+
+ // Trigger dependent job after data sync completes
+ if (cron_job_name === 'Data Sync') {
+ const syncResult = JSON.parse(response_body);
+ if (syncResult.records_synced > 0) {
+ await triggerCronJob('Data Processing Job');
+ }
+ }
+
+ res.status(200).json({ received: true });
+});
+```
+
+## Testing Webhooks
+
+Use [webhook.site](https://webhook.site) to test webhook integration:
+
+1. Go to webhook.site to get a unique URL
+2. Use it as your webhook URL when creating a cron job
+3. Trigger the cron job manually
+4. View the webhook payload on webhook.site
+
+Example:
+```json
+{
+ "callbacks": {
+ "success_url": "https://webhook.site/your-unique-id",
+ "failure_url": "https://webhook.site/your-unique-id"
+ }
+}
+```
\ No newline at end of file
diff --git a/mint.json b/mint.json
index 58a4146..c3843ae 100644
--- a/mint.json
+++ b/mint.json
@@ -33,6 +33,10 @@
{
"name": "A1Mail",
"url": "a1mail"
+ },
+ {
+ "name": "A1Cron",
+ "url": "a1cron"
}
],
"anchors": [
@@ -113,6 +117,36 @@
"a1mail/custom-mail-domain/index",
"a1mail/custom-mail-domain/verify-dns-settings"
]
+ },
+ {
+ "group": "A1Cron",
+ "pages": ["a1cron/index"]
+ },
+ {
+ "group": "Getting Started with A1Cron",
+ "pages": [
+ "a1cron/introduction",
+ "a1cron/quickstart",
+ "a1cron/examples"
+ ]
+ },
+ {
+ "group": "Cron Job Management",
+ "pages": [
+ "a1cron/list",
+ "a1cron/get-details",
+ "a1cron/create",
+ "a1cron/update",
+ "a1cron/delete",
+ "a1cron/trigger"
+ ]
+ },
+ {
+ "group": "Monitoring & Logs",
+ "pages": [
+ "a1cron/get-logs",
+ "a1cron/webhooks"
+ ]
}
],
"footerSocials": {
From afa739062302bae2fe9eb4de706365aaa26c33e1 Mon Sep 17 00:00:00 2001
From: pasha rayan
Date: Wed, 28 May 2025 23:57:38 +1000
Subject: [PATCH 34/40] one off cron job note
---
a1cron/one-off-jobs.mdx | 370 ++++++++++++++++++++++++++++++++++++++++
mint.json | 1 +
2 files changed, 371 insertions(+)
create mode 100644 a1cron/one-off-jobs.mdx
diff --git a/a1cron/one-off-jobs.mdx b/a1cron/one-off-jobs.mdx
new file mode 100644
index 0000000..a01dbb5
--- /dev/null
+++ b/a1cron/one-off-jobs.mdx
@@ -0,0 +1,370 @@
+---
+title: "One-Off Cron Jobs"
+description: "Schedule tasks to run only once at a specific time"
+---
+
+# One-Off Cron Jobs
+
+Sometimes you need to schedule a task to run just once at a specific time in the future. While A1Cron is primarily designed for recurring schedules, you can achieve one-off execution using a simple workaround with our existing scheduling options.
+
+
+This is a temporary workaround. We're developing enhanced features that will allow AI agents and applications to dynamically schedule one-time tasks more elegantly. Stay tuned for updates!
+
+
+## Why One-Off Jobs?
+
+One-off cron jobs are useful for:
+- **Scheduled reminders**: Send a reminder email at a specific future time
+- **Delayed processing**: Process data after a waiting period
+- **Time-sensitive operations**: Execute tasks at precise moments
+- **Event-driven scheduling**: Schedule follow-ups based on user actions
+- **AI agent tasks**: Allow AI systems to schedule future actions dynamically
+
+## Method 1: Using End Occurrences
+
+The cleanest approach is to create a cron job that ends after exactly one execution.
+
+### How It Works
+
+Set `end_type` to `"after"` and `end_occurrences` to `1`. The job will:
+1. Wait until the scheduled time
+2. Execute once
+3. Automatically deactivate
+
+### Example: Schedule a Task for Tomorrow at 2:30 PM
+
+
+
+```bash cURL
+curl -X POST https://api.a1base.com/v1/cron-jobs/{accountId}/create \
+ -H "X-API-Key: your-api-key" \
+ -H "X-API-Secret: your-api-secret" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "name": "Send Follow-up Email",
+ "description": "One-time follow-up email to customer",
+ "endpoint_url": "https://api.example.com/send-followup",
+ "method": "POST",
+ "headers": {
+ "Content-Type": "application/json"
+ },
+ "body": "{\"customer_id\": \"12345\", \"template\": \"followup_24h\"}",
+ "timezone": "America/New_York",
+ "schedule_config": {
+ "repeat_type": "days",
+ "repeat_every": 1,
+ "time": "14:30",
+ "end_type": "after",
+ "end_occurrences": 1
+ },
+ "tags": ["one-off", "followup", "customer-12345"],
+ "is_active": true
+ }'
+```
+
+```javascript Node.js
+const scheduleOneOffTask = async () => {
+ const response = await axios.post(
+ 'https://api.a1base.com/v1/cron-jobs/{accountId}/create',
+ {
+ name: "Send Follow-up Email",
+ description: "One-time follow-up email to customer",
+ endpoint_url: "https://api.example.com/send-followup",
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json"
+ },
+ body: JSON.stringify({
+ customer_id: "12345",
+ template: "followup_24h"
+ }),
+ timezone: "America/New_York",
+ schedule_config: {
+ repeat_type: "days",
+ repeat_every: 1,
+ time: "14:30",
+ end_type: "after",
+ end_occurrences: 1
+ },
+ tags: ["one-off", "followup", "customer-12345"],
+ is_active: true
+ },
+ {
+ headers: {
+ 'X-API-Key': 'your-api-key',
+ 'X-API-Secret': 'your-api-secret'
+ }
+ }
+ );
+
+ console.log(`One-off task scheduled for: ${response.data.next_run_at}`);
+};
+```
+
+```python Python
+import requests
+from datetime import datetime, timedelta
+
+def schedule_one_off_task():
+ # Calculate tomorrow's date
+ tomorrow = datetime.now() + timedelta(days=1)
+
+ data = {
+ "name": "Send Follow-up Email",
+ "description": "One-time follow-up email to customer",
+ "endpoint_url": "https://api.example.com/send-followup",
+ "method": "POST",
+ "headers": {
+ "Content-Type": "application/json"
+ },
+ "body": json.dumps({
+ "customer_id": "12345",
+ "template": "followup_24h"
+ }),
+ "timezone": "America/New_York",
+ "schedule_config": {
+ "repeat_type": "days",
+ "repeat_every": 1,
+ "time": "14:30",
+ "end_type": "after",
+ "end_occurrences": 1
+ },
+ "tags": ["one-off", "followup", "customer-12345"],
+ "is_active": True
+ }
+
+ response = requests.post(
+ "https://api.a1base.com/v1/cron-jobs/{accountId}/create",
+ headers={
+ "X-API-Key": "your-api-key",
+ "X-API-Secret": "your-api-secret"
+ },
+ json=data
+ )
+
+ if response.status_code == 201:
+ next_run = response.json()['data']['next_run_at']
+ print(f"One-off task scheduled for: {next_run}")
+```
+
+
+
+## Method 2: Using End Date
+
+Alternatively, set an end date shortly after the desired execution time.
+
+### How It Works
+
+Set `end_type` to `"on"` with an `end_date` just after your target time. This ensures:
+1. The job runs at the scheduled time
+2. The schedule expires immediately after
+
+### Example: Schedule for a Specific Date and Time
+
+```json
+{
+ "name": "Holiday Sale Announcement",
+ "description": "Send holiday sale email on Dec 25 at 9 AM",
+ "endpoint_url": "https://api.example.com/send-announcement",
+ "method": "POST",
+ "timezone": "America/New_York",
+ "schedule_config": {
+ "repeat_type": "days",
+ "repeat_every": 1,
+ "time": "09:00",
+ "end_type": "on",
+ "end_date": "2024-12-25T09:01:00-05:00"
+ },
+ "tags": ["one-off", "holiday", "announcement"]
+}
+```
+
+
+When using the end date method, ensure the end_date is at least 1 minute after your scheduled time to account for any execution delays.
+
+
+## Practical Examples
+
+### 1. Delayed Task Execution
+
+Schedule a task to run in 2 hours:
+
+```javascript
+const scheduleDelayedTask = async (delayHours = 2) => {
+ const now = new Date();
+ const runTime = new Date(now.getTime() + (delayHours * 60 * 60 * 1000));
+ const timeStr = `${runTime.getHours().toString().padStart(2, '0')}:${runTime.getMinutes().toString().padStart(2, '0')}`;
+
+ const response = await createCronJob({
+ name: `Delayed Task - ${now.toISOString()}`,
+ endpoint_url: "https://api.example.com/process-delayed",
+ schedule_config: {
+ repeat_type: "days",
+ repeat_every: 1,
+ time: timeStr,
+ end_type: "after",
+ end_occurrences: 1
+ }
+ });
+
+ return response.data;
+};
+```
+
+### 2. AI Agent Scheduling
+
+Allow an AI agent to schedule a follow-up action:
+
+```python
+def ai_schedule_followup(user_id, action, delay_minutes):
+ """
+ AI agent schedules a one-time follow-up action
+ """
+ run_time = datetime.now() + timedelta(minutes=delay_minutes)
+ time_str = run_time.strftime("%H:%M")
+
+ cron_data = {
+ "name": f"AI Follow-up: {action}",
+ "description": f"AI-scheduled task for user {user_id}",
+ "endpoint_url": "https://api.example.com/ai/execute-action",
+ "method": "POST",
+ "body": json.dumps({
+ "user_id": user_id,
+ "action": action,
+ "scheduled_by": "ai_agent"
+ }),
+ "timezone": "UTC",
+ "schedule_config": {
+ "repeat_type": "days",
+ "repeat_every": 1,
+ "time": time_str,
+ "end_type": "after",
+ "end_occurrences": 1
+ },
+ "callbacks": {
+ "success_url": "https://api.example.com/ai/action-completed",
+ "failure_url": "https://api.example.com/ai/action-failed"
+ },
+ "tags": ["ai-scheduled", "one-off", f"user-{user_id}"]
+ }
+
+ # Create the one-off cron job
+ return create_cron_job(cron_data)
+```
+
+### 3. Event-Driven Scheduling
+
+Schedule a task based on user events:
+
+```javascript
+// When user signs up, schedule a welcome email for 24 hours later
+app.post('/user/signup', async (req, res) => {
+ const { userId, email } = req.body;
+
+ // Create user account
+ await createUser({ userId, email });
+
+ // Schedule welcome follow-up for tomorrow
+ const tomorrow = new Date();
+ tomorrow.setDate(tomorrow.getDate() + 1);
+ const timeStr = "10:00"; // 10 AM in user's timezone
+
+ await scheduleOneOffCron({
+ name: `Welcome Email - ${userId}`,
+ endpoint_url: "https://api.example.com/emails/send-welcome",
+ method: "POST",
+ body: JSON.stringify({ userId, email }),
+ timezone: getUserTimezone(userId),
+ schedule_config: {
+ repeat_type: "days",
+ repeat_every: 1,
+ time: timeStr,
+ end_type: "after",
+ end_occurrences: 1
+ },
+ tags: ["welcome", "one-off", `user-${userId}`]
+ });
+
+ res.json({ message: "User created and welcome email scheduled" });
+});
+```
+
+## Best Practices for One-Off Jobs
+
+
+
+ Include timestamps or unique identifiers in job names to distinguish one-off tasks:
+ ```json
+ {
+ "name": "Follow-up Email - User 12345 - 2024-01-25"
+ }
+ ```
+
+
+
+ Use tags to identify and manage one-off jobs:
+ ```json
+ {
+ "tags": ["one-off", "temporary", "user-12345", "2024-01-25"]
+ }
+ ```
+
+
+
+ Consider deleting completed one-off jobs to keep your job list clean:
+ ```javascript
+ // In your success webhook handler
+ app.post('/webhooks/cron-success', async (req, res) => {
+ const { cron_job_id, cron_job_name } = req.body;
+
+ if (cron_job_name.includes('one-off')) {
+ // Delete the completed one-off job
+ await deleteCronJob(cron_job_id);
+ }
+ });
+ ```
+
+
+
+ Set up webhook callbacks to confirm one-off tasks complete successfully:
+ ```json
+ {
+ "callbacks": {
+ "success_url": "https://api.example.com/one-off-completed",
+ "failure_url": "https://api.example.com/one-off-failed"
+ }
+ }
+ ```
+
+
+
+## Limitations and Considerations
+
+1. **Minimum Scheduling Time**: Jobs can only be scheduled for future times, not immediate execution
+2. **Timezone Awareness**: Ensure you're using the correct timezone for your one-off execution
+3. **No Second Precision**: Cron jobs run at minute precision (HH:MM), not exact seconds
+4. **Cleanup Required**: One-off jobs remain in your job list after execution unless manually deleted
+
+## Future Improvements
+
+
+We're actively developing enhanced features for one-off scheduling:
+
+- **Native one-off job type**: Direct support without workarounds
+- **Dynamic scheduling API**: Allow AI agents to schedule tasks programmatically
+- **Immediate execution**: Option to run tasks with minimal delay
+- **Batch one-off scheduling**: Create multiple one-off tasks in a single request
+- **Auto-cleanup**: Automatic removal of completed one-off jobs
+
+These improvements will make A1Cron even more powerful for AI agents and dynamic applications.
+
+
+## Summary
+
+While A1Cron is designed for recurring schedules, you can effectively create one-off jobs using:
+1. **End occurrences method**: Set `end_occurrences: 1` for clean one-time execution
+2. **End date method**: Set an end date shortly after the scheduled time
+
+Both methods work reliably for scheduling tasks that need to run exactly once at a future time. Choose the method that best fits your use case and remember to tag your one-off jobs appropriately for easy management.
+
+For immediate task execution, consider using the [manual trigger](/a1cron/trigger) feature instead of scheduling a one-off job.
\ No newline at end of file
diff --git a/mint.json b/mint.json
index c3843ae..ae8b68e 100644
--- a/mint.json
+++ b/mint.json
@@ -127,6 +127,7 @@
"pages": [
"a1cron/introduction",
"a1cron/quickstart",
+ "a1cron/one-off-jobs",
"a1cron/examples"
]
},
From 9c016b9d83a48cc7835c915ab66ee837970c7699 Mon Sep 17 00:00:00 2001
From: Pennie
Date: Wed, 11 Jun 2025 16:02:22 -0700
Subject: [PATCH 35/40] webhook security for mai;
---
a1mail/receiving-email.mdx | 109 +++++++++++++++++++++++++++++++++++++
1 file changed, 109 insertions(+)
diff --git a/a1mail/receiving-email.mdx b/a1mail/receiving-email.mdx
index c9ce5f6..d546967 100644
--- a/a1mail/receiving-email.mdx
+++ b/a1mail/receiving-email.mdx
@@ -218,6 +218,115 @@ When an email is received, A1Base will send a POST request to your webhook endpo
| `service` | string | Always "email" for email webhooks |
| `raw_email_data` | string | Complete raw email content including headers and body |
+## Webhook Security
+
+To ensure the security and authenticity of webhook requests, A1Base includes security headers with each webhook POST request:
+
+### Security Headers
+
+| Header | Description |
+|--------|-------------|
+| `x-signature` | HMAC-SHA256 signature to verify request authenticity |
+| `x-timestamp` | Unix timestamp used in the signature, helps prevent replay attacks |
+
+### Signature Verification
+
+The signature is generated using:
+```
+HMAC_SHA256(secret, timestamp + raw JSON body)
+```
+
+Here's how to verify webhook signatures in your application:
+
+
+
+
+ ```python
+ import hmac
+ import hashlib
+ from flask import Flask, request, jsonify
+
+ app = Flask(__name__)
+
+ @app.route('/webhook/email', methods=['POST'])
+ def email_webhook():
+ # Get the webhook headers and raw body
+ signature = request.headers.get('x-signature')
+ timestamp = request.headers.get('x-timestamp')
+ raw_body = request.get_data()
+
+ # Your webhook secret from A1Base dashboard
+ secret = 'your_webhook_secret'
+
+ # Create the expected signature
+ message = timestamp + raw_body.decode()
+ expected_signature = hmac.new(
+ secret.encode(),
+ message.encode(),
+ hashlib.sha256
+ ).hexdigest()
+
+ # Compare signatures using a secure constant-time comparison
+ if not hmac.compare_digest(signature, expected_signature):
+ return jsonify({'error': 'Invalid signature'}), 403
+
+ # Process the verified webhook
+ data = request.json
+ print(f"Received verified email: {data['subject']}")
+
+ return jsonify({'status': 'success'}), 200
+ ```
+
+
+ ```javascript
+ const crypto = require('crypto');
+ const express = require('express');
+ const app = express();
+
+ // Capture raw body for signature verification
+ app.use(express.json({
+ verify: (req, res, buf) => {
+ req.rawBody = buf;
+ }
+ }));
+
+ app.post('/webhook/email', (req, res) => {
+ const signature = req.headers['x-signature'];
+ const timestamp = req.headers['x-timestamp'];
+ const rawBody = req.rawBody.toString();
+ const secret = process.env.A1BASE_WEBHOOK_SECRET;
+
+ // Create expected signature
+ const expectedSignature = crypto
+ .createHmac('sha256', secret)
+ .update(timestamp + rawBody)
+ .digest('hex');
+
+ // Compare signatures
+ if (signature !== expectedSignature) {
+ return res.status(403).send('Invalid signature');
+ }
+
+ // Process verified webhook
+ const data = req.body;
+ console.log(`Received verified email: ${data.subject}`);
+
+ res.status(200).json({ status: 'success' });
+ });
+ ```
+
+
+
+
+### Security Best Practices
+
+1. **Always verify signatures** to ensure requests are from A1Base
+2. **Check timestamp age** to prevent replay attacks (reject requests older than 5 minutes)
+3. **Use environment variables** to store your webhook secret
+4. **Implement proper error handling** for invalid signatures
+5. **Use HTTPS endpoints** in production
+6. **Keep your webhook URL and secret private**
+
## Example Raw Email Data
The `raw_email_data` field contains the complete email including headers, which you can parse to extract additional information like:
From 39a746cc006a5aba5252bb43919917df4706e255 Mon Sep 17 00:00:00 2001
From: Pennie
Date: Sat, 14 Jun 2025 22:50:09 -0700
Subject: [PATCH 36/40] a1mail webhook docs
---
a1mail/receiving-email.mdx | 183 +++++++++++++++++--------------------
1 file changed, 84 insertions(+), 99 deletions(-)
diff --git a/a1mail/receiving-email.mdx b/a1mail/receiving-email.mdx
index d546967..1a5df28 100644
--- a/a1mail/receiving-email.mdx
+++ b/a1mail/receiving-email.mdx
@@ -218,114 +218,99 @@ When an email is received, A1Base will send a POST request to your webhook endpo
| `service` | string | Always "email" for email webhooks |
| `raw_email_data` | string | Complete raw email content including headers and body |
-## Webhook Security
+## Verifying Signatures on Email Webhooks
-To ensure the security and authenticity of webhook requests, A1Base includes security headers with each webhook POST request:
+To ensure the integrity and authenticity of incoming email webhooks, we sign the request using a symmetric-key HMAC (Hash-based Message Authentication Code). This allows you to verify that the webhook was sent by our system and that its payload has not been tampered with.
-### Security Headers
+The signature is created using your unique `company_id` as the secret key.
-| Header | Description |
-|--------|-------------|
-| `x-signature` | HMAC-SHA256 signature to verify request authenticity |
-| `x-timestamp` | Unix timestamp used in the signature, helps prevent replay attacks |
+
+ You can find your `company_id` in the [A1Mail tab](https://www.a1base.com/dashboard/email-addresses) of your dashboard.
+
-### Signature Verification
+### How it Works
-The signature is generated using:
-```
-HMAC_SHA256(secret, timestamp + raw JSON body)
-```
+1. **Timestamping**: Each webhook request includes an `x-timestamp` header containing the Unix timestamp (in seconds) of when the request was created. This helps protect against replay attacks.
-Here's how to verify webhook signatures in your application:
+2. **Signature Creation**: We create a signature string by concatenating the timestamp and the raw JSON body of the request.
+ ```
+ message_to_sign = +
+ ```
-
-
-
- ```python
- import hmac
- import hashlib
- from flask import Flask, request, jsonify
-
- app = Flask(__name__)
-
- @app.route('/webhook/email', methods=['POST'])
- def email_webhook():
- # Get the webhook headers and raw body
- signature = request.headers.get('x-signature')
- timestamp = request.headers.get('x-timestamp')
- raw_body = request.get_data()
-
- # Your webhook secret from A1Base dashboard
- secret = 'your_webhook_secret'
-
- # Create the expected signature
- message = timestamp + raw_body.decode()
- expected_signature = hmac.new(
- secret.encode(),
- message.encode(),
- hashlib.sha256
- ).hexdigest()
-
- # Compare signatures using a secure constant-time comparison
- if not hmac.compare_digest(signature, expected_signature):
- return jsonify({'error': 'Invalid signature'}), 403
-
- # Process the verified webhook
- data = request.json
- print(f"Received verified email: {data['subject']}")
-
- return jsonify({'status': 'success'}), 200
- ```
-
-
- ```javascript
- const crypto = require('crypto');
- const express = require('express');
- const app = express();
-
- // Capture raw body for signature verification
- app.use(express.json({
- verify: (req, res, buf) => {
- req.rawBody = buf;
- }
- }));
-
- app.post('/webhook/email', (req, res) => {
- const signature = req.headers['x-signature'];
- const timestamp = req.headers['x-timestamp'];
- const rawBody = req.rawBody.toString();
- const secret = process.env.A1BASE_WEBHOOK_SECRET;
-
- // Create expected signature
- const expectedSignature = crypto
- .createHmac('sha256', secret)
- .update(timestamp + rawBody)
- .digest('hex');
-
- // Compare signatures
- if (signature !== expectedSignature) {
- return res.status(403).send('Invalid signature');
- }
-
- // Process verified webhook
- const data = req.body;
- console.log(`Received verified email: ${data.subject}`);
-
- res.status(200).json({ status: 'success' });
- });
- ```
-
-
-
+3. **HMAC-SHA256**: This string is then signed using the HMAC-SHA256 algorithm with your `company_id` as the secret key.
+
+4. **Header Inclusion**: The resulting signature is sent in the `x-signature` header of the request.
+
+### Verification Steps
+
+To verify the signature on your end, follow these steps:
+
+1. **Extract Headers**: From the incoming webhook request, extract the `x-timestamp` and `x-signature` headers.
+
+2. **Check the Timestamp**: To mitigate replay attacks, we recommend checking if the `x-timestamp` is recent (e.g., within the last 5 minutes). If the timestamp is too old, you may choose to reject the request.
+
+3. **Prepare the Signature String**: Recreate the `message_to_sign` by concatenating the value from the `x-timestamp` header with the complete, raw body of the POST request.
-### Security Best Practices
+ > **Important**: It is critical to use the raw, unmodified request body. Any parsing or modification of the JSON before creating the signature string will result in a mismatch.
-1. **Always verify signatures** to ensure requests are from A1Base
-2. **Check timestamp age** to prevent replay attacks (reject requests older than 5 minutes)
-3. **Use environment variables** to store your webhook secret
-4. **Implement proper error handling** for invalid signatures
-5. **Use HTTPS endpoints** in production
-6. **Keep your webhook URL and secret private**
+4. **Generate Your Expected Signature**: Using your `company_id` as the secret key, compute the HMAC-SHA256 hash of the `message_to_sign` you just created. The output should be a hex-encoded string.
+
+5. **Compare Signatures**: Compare the `x-signature` from the request header with your generated signature. If they match exactly, the webhook is authentic and can be trusted. If they do not match, the request should be discarded.
+
+### Example (Python)
+
+```python
+import hmac
+import hashlib
+import json
+
+def verify_webhook_signature(request_body_raw, headers, company_id):
+ """
+ Verifies the signature of an incoming email webhook.
+
+ Args:
+ request_body_raw (bytes): The raw, unparsed body of the request.
+ headers (dict): A dictionary of the request headers.
+ company_id (str): Your unique company ID, used as the secret key.
+
+ Returns:
+ bool: True if the signature is valid, False otherwise.
+ """
+ received_timestamp = headers.get('x-timestamp')
+ received_signature = headers.get('x-signature')
+
+ if not received_timestamp or not received_signature:
+ # Necessary headers are missing
+ return False
+
+ # 1. Prepare the message string
+ message_to_sign = received_timestamp.encode('utf-8') + request_body_raw
+
+ # 2. Generate the expected signature
+ expected_signature = hmac.new(
+ key=company_id.encode('utf-8'),
+ msg=message_to_sign,
+ digestmod=hashlib.sha256
+ ).hexdigest()
+
+ # 3. Compare signatures securely
+ return hmac.compare_digest(expected_signature, received_signature)
+
+# --- Usage Example ---
+# Assuming 'request' is your web framework's request object
+
+# company_id = "your_company_id_goes_here"
+# headers = request.headers
+# raw_body = request.get_data() # Use a method that gets the raw body
+
+# is_valid = verify_webhook_signature(raw_body, headers, company_id)
+
+# if is_valid:
+# print("β Signature is valid. Processing webhook.")
+# # ... process the webhook payload ...
+# else:
+# print("β Invalid signature. Discarding request.")
+```
## Example Raw Email Data
From 0e976c14bb85a258b9c0d9c12706e2cf5af771ba Mon Sep 17 00:00:00 2001
From: Pennie
Date: Wed, 18 Jun 2025 20:44:04 -0700
Subject: [PATCH 37/40] attachents
---
a1mail/sending-email.mdx | 104 +++++++++++++++++++++++++++++++++++++++
1 file changed, 104 insertions(+)
diff --git a/a1mail/sending-email.mdx b/a1mail/sending-email.mdx
index 92de834..d20647c 100644
--- a/a1mail/sending-email.mdx
+++ b/a1mail/sending-email.mdx
@@ -14,6 +14,7 @@ The following parameters are used when sending an email:
| `subject` | string | Yes | Subject line of the email |
| `body` | string | Yes | Content of the email (plain text or HTML) |
| `headers` | object | No | Optional email headers as key-value pairs (cc, bcc, etc.) |
+| `attachment_uri` | array | No | Array of URIs pointing to files you want to attach to the email |
## Code Examples
@@ -52,6 +53,25 @@ The following parameters are used when sending an email:
"bcc": "pasha@a1base.com"
}
}'
+
+ ### Send an Email with Attachments
+
+ ```bash
+ curl --location 'https://api.a1base.com/v1/emails/{account_id}/send' \
+ --header 'X-API-Key: YOUR_API_KEY' \
+ --header 'X-API-Secret: YOUR_API_SECRET' \
+ --header 'Content-Type: application/json' \
+ --data-raw '{
+ "sender_address": "hello@a1send.com",
+ "recipient_address": "recipient@example.com",
+ "subject": "Email with Attachments",
+ "body": "Please find the attached files.",
+ "headers": {},
+ "attachment_uri": [
+ "https://example.com/files/document.pdf",
+ "https://example.com/files/image.jpg"
+ ]
+ }'
```
@@ -134,6 +154,36 @@ The following parameters are used when sending an email:
response = requests.post(url, headers=headers, data=json.dumps(data))
print(response.json())
```
+
+ ### Send an Email with Attachments
+
+ ```python
+ import requests
+ import json
+
+ url = "https://api.a1base.com/v1/emails/{account_id}/send"
+
+ headers = {
+ 'X-API-Key': 'YOUR_API_KEY',
+ 'X-API-Secret': 'YOUR_API_SECRET',
+ 'Content-Type': 'application/json'
+ }
+
+ data = {
+ "sender_address": "hello@a1send.com",
+ "recipient_address": "recipient@example.com",
+ "subject": "Email with Attachments",
+ "body": "Please find the attached files.",
+ "headers": {},
+ "attachment_uri": [
+ "https://example.com/files/document.pdf",
+ "https://example.com/files/image.jpg"
+ ]
+ }
+
+ response = requests.post(url, headers=headers, data=json.dumps(data))
+ print(response.json())
+ ```
@@ -215,6 +265,36 @@ The following parameters are used when sending an email:
.then(response => console.log(response.data))
.catch(error => console.error('Error:', error));
```
+
+ ### Send an Email with Attachments
+
+ ```javascript
+ const axios = require('axios');
+
+ const url = 'https://api.a1base.com/v1/emails/{account_id}/send';
+
+ const headers = {
+ 'X-API-Key': 'YOUR_API_KEY',
+ 'X-API-Secret': 'YOUR_API_SECRET',
+ 'Content-Type': 'application/json'
+ };
+
+ const data = {
+ sender_address: 'hello@a1send.com',
+ recipient_address: 'recipient@example.com',
+ subject: 'Email with Attachments',
+ body: 'Please find the attached files.',
+ headers: {},
+ attachment_uri: [
+ 'https://example.com/files/document.pdf',
+ 'https://example.com/files/image.jpg'
+ ]
+ };
+
+ axios.post(url, data, { headers })
+ .then(response => console.log(response.data))
+ .catch(error => console.error('Error:', error));
+ ```
@@ -223,3 +303,27 @@ The following parameters are used when sending an email:
Don't hesitate to reach out to pennie@a1base.com or pasha@a1base.com if there's any features you'd like to see or prioritised!
+
+## Working with Attachments
+
+To include attachments in your emails, use the `attachment_uri` parameter which accepts an array of URLs pointing to the files you want to attach.
+
+### Supported Attachment Types
+
+A1Mail supports most common file types for email attachments, including but not limited to:
+
+- PDF documents (*.pdf)
+- Images (*.jpg, *.jpeg, *.png, *.gif)
+- Office documents (*.docx, *.xlsx, *.pptx)
+- Text files (*.txt, *.csv)
+
+### Attachment Size Limits
+
+Attachments are subject to size limitations. Please ensure your attachments adhere to the following guidelines:
+
+- Individual attachment: Maximum 10MB
+- Total attachments per email: Maximum 25MB
+
+### Hosting Attachments
+
+Attachments must be accessible via a public URL. You can use your own hosting solution or any publicly accessible file storage service. Make sure the URLs provided in the `attachment_uri` array are directly accessible without authentication.
From 880b570e8f0de0586d6d0ffda43d84078833c578 Mon Sep 17 00:00:00 2001
From: Pennie
Date: Wed, 2 Jul 2025 15:17:51 -0700
Subject: [PATCH 38/40] cal niv
---
A1ZAP_MESSAGING_IMPLEMENTATION_GUIDE.md | 686 ++++++++++++++++++
a1base-email-integration.md | 152 ++++
.../MESSAGE_PROCESSING_ARCHITECTURE.md | 624 ++++++++++++++++
.../a1framework-initialsupabase.sql | 182 +++++
a1mail/send-calendar-invites.mdx | 354 +++++++++
database-design.md | 345 +++++++++
.../agent-memory-settings.json | 43 ++
example-agent-jsons/base-information.json | 1 +
.../group-onboarding-flow.json | 34 +
example-agent-jsons/message-settings.json | 3 +
example-agent-jsons/model-settings.json | 3 +
example-agent-jsons/onboarding-flow.json | 36 +
example-agent-jsons/profile-settings.json | 31 +
example-agent-jsons/safety-settings.json | 6 +
mint.json | 3 +-
sql/create-fresh-db.sql | 346 +++++++++
sql/migrate-existing-db.sql | 319 ++++++++
stack-auth-integration.md | 294 ++++++++
stack-auth-setup.md | 106 +++
19 files changed, 3567 insertions(+), 1 deletion(-)
create mode 100644 A1ZAP_MESSAGING_IMPLEMENTATION_GUIDE.md
create mode 100644 a1base-email-integration.md
create mode 100644 a1framework-notes/MESSAGE_PROCESSING_ARCHITECTURE.md
create mode 100644 a1framework-notes/a1framework-initialsupabase.sql
create mode 100644 a1mail/send-calendar-invites.mdx
create mode 100644 database-design.md
create mode 100644 example-agent-jsons/agent-memory-settings.json
create mode 100644 example-agent-jsons/base-information.json
create mode 100644 example-agent-jsons/group-onboarding-flow.json
create mode 100644 example-agent-jsons/message-settings.json
create mode 100644 example-agent-jsons/model-settings.json
create mode 100644 example-agent-jsons/onboarding-flow.json
create mode 100644 example-agent-jsons/profile-settings.json
create mode 100644 example-agent-jsons/safety-settings.json
create mode 100644 sql/create-fresh-db.sql
create mode 100644 sql/migrate-existing-db.sql
create mode 100644 stack-auth-integration.md
create mode 100644 stack-auth-setup.md
diff --git a/A1ZAP_MESSAGING_IMPLEMENTATION_GUIDE.md b/A1ZAP_MESSAGING_IMPLEMENTATION_GUIDE.md
new file mode 100644
index 0000000..d0d0575
--- /dev/null
+++ b/A1ZAP_MESSAGING_IMPLEMENTATION_GUIDE.md
@@ -0,0 +1,686 @@
+# A1Zap Messaging System Implementation Guide
+
+## Overview
+
+This guide outlines the implementation of a multi-agent messaging system in A1Zap, based on A1Framework's architecture but extended to support multiple AI agents managed by different users.
+
+## Table of Contents
+
+1. [Architecture Overview](#architecture-overview)
+2. [Database Schema Changes](#database-schema-changes)
+3. [Implementation Phases](#implementation-phases)
+4. [Core Components](#core-components)
+5. [API Endpoints](#api-endpoints)
+6. [Message Flow](#message-flow)
+7. [Multi-Agent Routing](#multi-agent-routing)
+8. [Integration Points](#integration-points)
+9. [Migration Strategy](#migration-strategy)
+
+## Architecture Overview
+
+### Key Differences from A1Framework
+
+1. **Multi-Agent Support**: Each user can have multiple AI agents
+2. **Stack Auth Integration**: User authentication via Stack Auth instead of Supabase Auth
+3. **Agent Isolation**: Messages and conversations are scoped to specific agents
+4. **Subscription-Based**: Message limits and agent counts based on subscription plans
+
+### System Components
+
+```
+βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
+β Stack Auth β β Web UI β β External β
+β (User Auth) β β (Dashboard) β β Channels β
+ββββββββββ¬βββββββββ ββββββββββ¬βββββββββ ββββββββββ¬βββββββββ
+ β β β
+ βββββββββββββββββββββββββ΄ββββββββββββββββββββββββββ
+ β
+ ββββββββββββββΌβββββββββββββ
+ β A1Zap API Layer β
+ β (Message Router) β
+ ββββββββββββββ¬βββββββββββββ
+ β
+ ββββββββββββββββββΌβββββββββββββββββ
+ β β β
+ ββββββββββββΌβββββββ βββββββΌβββββββ βββββββΌβββββββ
+ β Agent Manager β β Message β β Triage β
+ β β β Processor β β System β
+ βββββββββββββββββββ ββββββββββββββ ββββββββββββββ
+ β β β
+ ββββββββββββββββββΌβββββββββββββββββ
+ β
+ ββββββββββββββΌβββββββββββββ
+ β Supabase Database β
+ β (Messages, Chats, etc) β
+ βββββββββββββββββββββββββββ
+```
+
+## Database Schema Changes
+
+### New Tables Added
+
+1. **conversation_users**: External users chatting with agents
+2. **chats**: Conversation threads between users and agents
+3. **chat_participants**: Junction table for chat members
+4. **projects**: Tasks/workflows managed by agents
+5. **email_scheduled_reports**: Scheduled email reports per agent
+
+### Modified Tables
+
+1. **messages**: Extended to support chat system
+2. **a1_agents**: Added messaging configuration fields
+3. **agent_conversations**: Linked to new chat system
+
+### Key Relationships
+
+```sql
+-- Each agent belongs to a user
+a1_agents.owner_id -> users.id
+
+-- Each chat is handled by an agent
+chats.agent_id -> a1_agents.id
+
+-- Messages belong to chats and agents
+messages.chat_id -> chats.id
+messages.agent_id -> a1_agents.id
+
+-- Conversation users can be linked to Stack Auth users
+conversation_users.stack_auth_user_id -> users.id
+```
+
+## Implementation Phases
+
+### Phase 1: Core Infrastructure (Week 1-2)
+
+1. **Database Setup**
+ - Run the Supabase initial setup SQL
+ - Create database adapter layer
+ - Implement connection pooling
+
+2. **Basic Message Processing**
+ - Create message ingestion endpoints
+ - Implement message persistence
+ - Build basic response generation
+
+3. **Agent Management**
+ - Extend agent creation to include messaging config
+ - Add agent status management
+ - Implement agent-specific settings
+
+### Phase 2: Channel Integration (Week 3-4)
+
+1. **Web Chat Interface**
+ - Build chat UI component
+ - Implement real-time messaging
+ - Add typing indicators
+
+2. **Email Integration**
+ - Set up email webhook endpoint
+ - Implement email parsing
+ - Build email response formatting
+
+3. **WhatsApp Integration** (if using A1Base)
+ - Configure webhook endpoints
+ - Handle multimedia messages
+ - Implement message status tracking
+
+### Phase 3: AI & Triage System (Week 5-6)
+
+1. **Triage Implementation**
+ - Port triage logic from A1Framework
+ - Adapt for multi-agent routing
+ - Implement confidence scoring
+
+2. **Workflow System**
+ - Basic response workflow
+ - Project management workflow
+ - Custom workflow framework
+
+3. **Memory & Context**
+ - Implement conversation memory
+ - Add user preference tracking
+ - Build context handoff system
+
+### Phase 4: Advanced Features (Week 7-8)
+
+1. **Multi-Agent Coordination**
+ - Agent selection logic
+ - Inter-agent communication
+ - Escalation paths
+
+2. **Analytics & Monitoring**
+ - Message analytics dashboard
+ - Agent performance metrics
+ - Usage tracking
+
+3. **Optimization**
+ - Response caching
+ - Query optimization
+ - Load balancing
+
+## Core Components
+
+### 1. Database Adapter
+
+Create a Supabase adapter that extends the existing database layer:
+
+```typescript
+// src/lib/messaging/supabase-adapter.ts
+import { db } from '@/lib/db-supabase';
+import { WebhookPayload } from './types';
+
+export class MessagingAdapter {
+ async processWebhookPayload(payload: WebhookPayload, agentId: string) {
+ // 1. Get or create conversation user
+ const userId = await this.getOrCreateConversationUser(
+ payload.sender_number,
+ payload.sender_name,
+ payload.service
+ );
+
+ // 2. Get or create chat
+ const chatId = await this.getOrCreateChat(
+ payload.thread_id,
+ payload.thread_type,
+ payload.service,
+ agentId
+ );
+
+ // 3. Add participants
+ await this.addChatParticipant(chatId, userId);
+
+ // 4. Store message
+ return await this.storeMessage({
+ chatId,
+ senderId: userId,
+ content: payload.message_content.text || '',
+ messageType: payload.message_type,
+ externalId: payload.message_id,
+ service: payload.service,
+ agentId,
+ richContent: payload.message_content,
+ });
+ }
+
+ // Additional methods...
+}
+```
+
+### 2. Message Router
+
+Route incoming messages to the appropriate agent:
+
+```typescript
+// src/lib/messaging/message-router.ts
+export class MessageRouter {
+ async routeMessage(
+ message: IncomingMessage,
+ availableAgents: Agent[]
+ ): Promise {
+ // 1. Check if there's an existing conversation
+ const existingChat = await this.findExistingChat(message);
+ if (existingChat) {
+ return existingChat.agent;
+ }
+
+ // 2. Analyze message intent
+ const intent = await this.analyzeIntent(message);
+
+ // 3. Score agents based on capabilities
+ const scores = await this.scoreAgents(availableAgents, intent);
+
+ // 4. Return best matching agent
+ return scores[0].agent;
+ }
+}
+```
+
+### 3. Triage System
+
+Adapt the triage system for multi-agent context:
+
+```typescript
+// src/lib/messaging/triage.ts
+export async function triageMessage(
+ message: Message,
+ agent: Agent,
+ context: ConversationContext
+): Promise {
+ const systemPrompt = agent.system_prompt || getDefaultSystemPrompt();
+
+ const result = await openai.chat.completions.create({
+ model: agent.model_settings?.model || 'gpt-4',
+ messages: [
+ { role: 'system', content: systemPrompt },
+ { role: 'system', content: getTriagePrompt(agent) },
+ ...context.messages,
+ { role: 'user', content: message.content }
+ ],
+ response_format: { type: 'json_object' }
+ });
+
+ return JSON.parse(result.choices[0].message.content);
+}
+```
+
+### 4. Response Generator
+
+Generate responses using agent-specific settings:
+
+```typescript
+// src/lib/messaging/response-generator.ts
+export async function generateResponse(
+ agent: Agent,
+ triageResult: TriageResult,
+ context: ConversationContext
+): Promise {
+ const workflow = getWorkflow(triageResult.responseType);
+
+ return await workflow.execute({
+ agent,
+ context,
+ triageResult,
+ llmProvider: getLLMProvider(agent.model_settings)
+ });
+}
+```
+
+## API Endpoints
+
+### 1. Webhook Endpoints
+
+```typescript
+// src/app/api/webhooks/messaging/route.ts
+export async function POST(request: Request) {
+ const payload = await request.json();
+ const agentId = request.headers.get('x-agent-id');
+
+ if (!agentId) {
+ return Response.json({ error: 'Agent ID required' }, { status: 400 });
+ }
+
+ // Process webhook
+ await processWebhook(payload, agentId);
+
+ return Response.json({ success: true });
+}
+```
+
+### 2. Chat API
+
+```typescript
+// src/app/api/agents/[agentId]/chat/route.ts
+export async function POST(
+ request: Request,
+ { params }: { params: { agentId: string } }
+) {
+ const { message } = await request.json();
+ const user = await requireUser();
+
+ // Verify agent ownership
+ const agent = await verifyAgentOwnership(params.agentId, user.id);
+
+ // Process message
+ const response = await processUserMessage(message, agent, user);
+
+ return Response.json(response);
+}
+```
+
+### 3. Conversation Management
+
+```typescript
+// src/app/api/agents/[agentId]/conversations/route.ts
+export async function GET(
+ request: Request,
+ { params }: { params: { agentId: string } }
+) {
+ const user = await requireUser();
+ const agent = await verifyAgentOwnership(params.agentId, user.id);
+
+ const conversations = await db
+ .select()
+ .from(chats)
+ .where(eq(chats.agentId, agent.id))
+ .orderBy(desc(chats.lastMessageAt));
+
+ return Response.json(conversations);
+}
+```
+
+## Message Flow
+
+### Incoming Message Flow
+
+```mermaid
+sequenceDiagram
+ participant External as External Channel
+ participant Webhook as Webhook Handler
+ participant Router as Message Router
+ participant DB as Database
+ participant Triage as Triage System
+ participant Workflow as Workflow Engine
+ participant Response as Response Sender
+
+ External->>Webhook: Incoming Message
+ Webhook->>Router: Process Message
+ Router->>DB: Store Message
+ Router->>DB: Get/Create Chat
+ Router->>Triage: Analyze Intent
+ Triage->>Workflow: Route to Workflow
+ Workflow->>DB: Update Context
+ Workflow->>Response: Generate Response
+ Response->>External: Send Response
+```
+
+### Web UI Message Flow
+
+```mermaid
+sequenceDiagram
+ participant UI as Web UI
+ participant API as Chat API
+ participant Auth as Stack Auth
+ participant Agent as Agent Service
+ participant DB as Database
+ participant AI as AI Service
+
+ UI->>API: Send Message
+ API->>Auth: Verify User
+ Auth-->>API: User Verified
+ API->>Agent: Get Agent Config
+ API->>DB: Store Message
+ API->>AI: Generate Response
+ AI-->>API: Response
+ API->>DB: Store Response
+ API-->>UI: Return Response
+```
+
+## Multi-Agent Routing
+
+### Agent Selection Strategy
+
+1. **Direct Routing**: Messages to specific agent endpoints
+2. **Smart Routing**: Analyze message and route to best agent
+3. **Escalation**: Transfer between agents when needed
+
+```typescript
+// src/lib/messaging/agent-selector.ts
+export class AgentSelector {
+ async selectAgent(
+ message: Message,
+ userAgents: Agent[]
+ ): Promise {
+ // 1. Check for explicit agent mention
+ const mentionedAgent = this.checkMentions(message, userAgents);
+ if (mentionedAgent) return mentionedAgent;
+
+ // 2. Check conversation history
+ const previousAgent = await this.checkHistory(message);
+ if (previousAgent) return previousAgent;
+
+ // 3. Analyze message intent
+ const intent = await this.analyzeIntent(message);
+
+ // 4. Score agents
+ const scores = await this.scoreAgents(userAgents, intent);
+
+ // 5. Return best match
+ return scores[0].agent;
+ }
+
+ private async scoreAgents(
+ agents: Agent[],
+ intent: Intent
+ ): Promise {
+ return Promise.all(
+ agents.map(async (agent) => ({
+ agent,
+ score: await this.calculateScore(agent, intent)
+ }))
+ ).then(scores => scores.sort((a, b) => b.score - a.score));
+ }
+}
+```
+
+### Context Handoff
+
+When transferring between agents:
+
+```typescript
+interface AgentHandoff {
+ fromAgent: string;
+ toAgent: string;
+ reason: string;
+ context: {
+ conversation: Message[];
+ summary: string;
+ userInfo: any;
+ currentTask?: any;
+ };
+}
+
+async function handoffConversation(
+ handoff: AgentHandoff
+): Promise {
+ // 1. Update chat assignment
+ await db
+ .update(chats)
+ .set({ agentId: handoff.toAgent })
+ .where(eq(chats.id, handoff.chatId));
+
+ // 2. Create handoff message
+ await createSystemMessage(
+ handoff.chatId,
+ `Conversation transferred from ${handoff.fromAgent} to ${handoff.toAgent}: ${handoff.reason}`
+ );
+
+ // 3. Notify new agent
+ await notifyAgent(handoff.toAgent, handoff.context);
+}
+```
+
+## Integration Points
+
+### 1. Stack Auth Integration
+
+```typescript
+// src/lib/messaging/auth-integration.ts
+export async function linkConversationUser(
+ conversationUserId: string,
+ stackAuthUserId: string
+): Promise {
+ await db
+ .update(conversationUsers)
+ .set({ stackAuthUserId })
+ .where(eq(conversationUsers.id, conversationUserId));
+}
+```
+
+### 2. Existing Agent System
+
+```typescript
+// src/lib/messaging/agent-integration.ts
+export async function extendAgentWithMessaging(
+ agentId: string,
+ messagingConfig: MessagingConfig
+): Promise {
+ await db
+ .update(a1Agents)
+ .set({
+ systemPrompt: messagingConfig.systemPrompt,
+ respondOnlyWhenMentioned: messagingConfig.respondOnlyWhenMentioned,
+ messageSettings: messagingConfig.messageSettings,
+ })
+ .where(eq(a1Agents.id, agentId));
+}
+```
+
+### 3. UI Components
+
+Create reusable chat components:
+
+```typescript
+// src/components/chat/agent-chat.tsx
+export function AgentChat({ agentId }: { agentId: string }) {
+ const [messages, setMessages] = useState([]);
+ const [input, setInput] = useState('');
+
+ const sendMessage = async () => {
+ const response = await fetch(`/api/agents/${agentId}/chat`, {
+ method: 'POST',
+ body: JSON.stringify({ message: input }),
+ });
+
+ const data = await response.json();
+ setMessages([...messages, data.userMessage, data.agentResponse]);
+ setInput('');
+ };
+
+ return (
+
+
+
+
+ );
+}
+```
+
+## Migration Strategy
+
+### 1. Database Migration
+
+```bash
+# 1. Run the new SQL setup
+psql $DATABASE_URL < supabase/initial-setup.sql
+
+# 2. Migrate existing messages
+npm run migrate:messages
+
+# 3. Update agent configurations
+npm run migrate:agents
+```
+
+### 2. Code Migration
+
+1. **Update Imports**: Change from old message table to new structure
+2. **Add Adapters**: Create compatibility layers for existing code
+3. **Gradual Rollout**: Enable messaging per agent
+
+### 3. Testing Strategy
+
+```typescript
+// src/__tests__/messaging/integration.test.ts
+describe('Messaging System', () => {
+ it('should route messages to correct agent', async () => {
+ const agent = await createTestAgent();
+ const message = await sendTestMessage(agent.id);
+
+ expect(message.agentId).toBe(agent.id);
+ });
+
+ it('should handle agent handoffs', async () => {
+ const [agent1, agent2] = await createTestAgents(2);
+ const chat = await startChat(agent1.id);
+
+ await handoffChat(chat.id, agent2.id);
+
+ const updatedChat = await getChat(chat.id);
+ expect(updatedChat.agentId).toBe(agent2.id);
+ });
+});
+```
+
+## Performance Considerations
+
+### 1. Database Optimization
+
+- Add appropriate indexes (already in SQL)
+- Use connection pooling
+- Implement query result caching
+
+### 2. Message Processing
+
+- Queue long-running operations
+- Implement rate limiting per agent
+- Cache AI responses when appropriate
+
+### 3. Scaling Strategy
+
+- Horizontal scaling of API servers
+- Message queue for webhook processing
+- Separate read/write database connections
+
+## Security Considerations
+
+### 1. Agent Isolation
+
+- Ensure agents can only access their own conversations
+- Implement proper authorization checks
+- Audit all cross-agent operations
+
+### 2. Data Privacy
+
+- Encrypt sensitive message content
+- Implement data retention policies
+- Provide user data export functionality
+
+### 3. Rate Limiting
+
+```typescript
+// src/lib/messaging/rate-limiter.ts
+export async function checkRateLimit(
+ agentId: string,
+ userId: string
+): Promise {
+ const subscription = await getUserSubscription(userId);
+ const usage = await getMonthlyUsage(agentId);
+
+ return usage.messageCount < subscription.maxMessagesPerMonth;
+}
+```
+
+## Monitoring & Analytics
+
+### 1. Key Metrics
+
+- Messages per agent per day
+- Response time percentiles
+- Triage accuracy
+- Handoff frequency
+
+### 2. Dashboard Components
+
+```typescript
+// src/components/analytics/messaging-stats.tsx
+export function MessagingStats({ agentId }: { agentId: string }) {
+ const stats = useMessagingStats(agentId);
+
+ return (
+
+
+
+
+
+
+ );
+}
+```
+
+## Next Steps
+
+1. **Review** the SQL schema and make any necessary adjustments
+2. **Set up** the database with the new schema
+3. **Implement** Phase 1 core infrastructure
+4. **Test** with a single agent before rolling out
+5. **Iterate** based on user feedback
+
+## Resources
+
+- [A1Framework Message Processing Architecture](./MESSAGE_PROCESSING_ARCHITECTURE.md)
+- [Supabase Documentation](https://supabase.com/docs)
+- [AI SDK Documentation](https://sdk.vercel.ai/docs)
+- [Stack Auth Documentation](https://stack-auth.com/docs)
\ No newline at end of file
diff --git a/a1base-email-integration.md b/a1base-email-integration.md
new file mode 100644
index 0000000..4cb9fce
--- /dev/null
+++ b/a1base-email-integration.md
@@ -0,0 +1,152 @@
+# A1Base Email Integration
+
+This document describes how A1Zap-maker integrates with A1Base to provide email functionality for AI agents.
+
+## Overview
+
+A1Base provides email infrastructure that allows AI agents to:
+- Have their own email addresses (@a1send.com or @a101.bot)
+- Send emails independently
+- Receive emails via webhooks
+- Maintain email conversations
+
+## Setup
+
+### Environment Variables
+
+Add these to your `.env` file:
+
+```env
+A1BASE_API_KEY=your_api_key
+A1BASE_API_SECRET=your_api_secret
+A1BASE_ACCOUNT_ID=your_account_id
+```
+
+### Webhook Configuration
+
+Configure A1Base to send incoming emails to:
+```
+https://your-domain.com/api/webhooks/a1base-email
+```
+
+## Usage
+
+### 1. Email Service (`src/lib/a1base-email.ts`)
+
+Core service for interacting with A1Base API:
+
+```typescript
+import { a1BaseEmail } from "@/lib/a1base-email";
+
+// Create email for agent
+const result = await a1BaseEmail.createAgentEmail("AgentName", "a1send.com");
+
+// Send email
+await a1BaseEmail.sendEmail({
+ sender_address: "agent@a1send.com",
+ recipient_address: "user@example.com",
+ subject: "Hello",
+ body: "Message content"
+});
+```
+
+### 2. Server Actions (`src/app/actions/email-actions.ts`)
+
+Server-side functions for email operations:
+
+```typescript
+import { createAgentEmailAddress, sendAgentEmail } from "@/app/actions/email-actions";
+
+// Create email for existing agent
+const result = await createAgentEmailAddress(agentId, "a1send.com");
+
+// Send email from agent
+await sendAgentEmail(agentId, recipientEmail, subject, body);
+```
+
+### 3. React Hook (`src/hooks/use-agent-email.ts`)
+
+Client-side hook for email operations:
+
+```typescript
+const { createEmail, sendEmail, isLoading, error } = useAgentEmail();
+
+// Create email
+const result = await createEmail(agentId);
+
+// Send email
+await sendEmail(agentId, recipientEmail, subject, body);
+```
+
+### 4. Email Manager Component (`src/components/agent-email-manager.tsx`)
+
+Ready-to-use UI component:
+
+```tsx
+
+```
+
+## Automatic Email Creation
+
+When creating a new agent via the onboarding flow, an email address is automatically created if:
+- The agent doesn't have an email yet
+- The email domain is @a1friend.com (placeholder)
+
+The system will:
+1. Generate a valid email address based on the agent name
+2. Create the email via A1Base API
+3. Send a welcome email to the user
+4. Store the email in the agent record
+
+## Email Validation Rules
+
+A1Base email addresses must:
+- Be 5-30 characters long
+- Only contain letters, numbers, '.', '_', '-'
+- Have no consecutive dots, spaces, or commas
+
+## Receiving Emails
+
+Incoming emails are:
+1. Received via webhook at `/api/webhooks/a1base-email`
+2. Matched to agents by email address
+3. Stored as messages in the database
+4. Associated with a conversation
+
+## Testing
+
+Use the test page at `/test-email` to:
+- Create email addresses
+- Validate email formats
+- Send test emails
+- View email suggestions
+
+## Error Handling
+
+The integration includes:
+- Graceful fallbacks if email creation fails
+- Error messages in UI components
+- Logging for debugging
+- Non-blocking operations (email failures don't break agent creation)
+
+## Security Considerations
+
+- API credentials are stored in environment variables
+- Webhook endpoint validates incoming requests
+- Email content is sanitized before storage
+- Access control via agent ownership
+
+## Future Enhancements
+
+Potential improvements:
+- Custom domain support
+- Email templates
+- Automated responses
+- Email threading
+- Attachment handling
+- Email analytics
\ No newline at end of file
diff --git a/a1framework-notes/MESSAGE_PROCESSING_ARCHITECTURE.md b/a1framework-notes/MESSAGE_PROCESSING_ARCHITECTURE.md
new file mode 100644
index 0000000..ea05a25
--- /dev/null
+++ b/a1framework-notes/MESSAGE_PROCESSING_ARCHITECTURE.md
@@ -0,0 +1,624 @@
+# Message Processing Architecture Guide
+
+## Overview
+
+This document provides a comprehensive guide to understanding how the A1Base messaging system processes incoming messages, performs triage, and routes them through various workflows. This architecture can be adapted for implementing a multi-agent system where different agents handle different types of requests.
+
+!!NOTE: For this implementation in a1zap, we are happy to use any llm provider like claude, grok, and openai.!!
+
+## Table of Contents
+
+1. [Architecture Overview](#architecture-overview)
+2. [Message Flow](#message-flow)
+3. [Core Components](#core-components)
+4. [Triage System](#triage-system)
+5. [Workflow Implementation](#workflow-implementation)
+6. [Multi-Agent Considerations](#multi-agent-considerations)
+7. [Implementation Examples](#implementation-examples)
+
+## Architecture Overview
+
+The A1Base messaging system follows a pipeline architecture:
+
+1. **Webhook Reception**: Messages arrive via webhooks from various channels (WhatsApp, SMS, RCS, iMessage)
+2. **Message Processing**: Messages are normalized, stored, and prepared for processing
+3. **Triage**: AI-powered classification determines the intent and appropriate workflow
+4. **Workflow Execution**: Specialized handlers process different types of requests
+5. **Response Generation**: AI generates contextual responses
+6. **Message Delivery**: Responses are sent back through the appropriate channel
+
+## Message Flow
+
+### 1. Webhook Entry Point
+
+All messages enter through a unified webhook endpoint:
+
+```typescript
+// app/api/a1base/messaging/route.ts
+export interface WebhookPayload {
+ thread_id: string;
+ message_id: string;
+ thread_type: 'group' | 'individual' | 'broadcast';
+ sender_number: string;
+ sender_name: string;
+ timestamp: string;
+ service: string; // 'whatsapp', 'sms', 'rcs', 'imessage'
+ message_type: string; // 'text', 'image', 'video', etc.
+ is_from_agent: boolean;
+ agent_mentioned?: boolean;
+ message_content: {
+ text?: string;
+ data?: string; // base64 for media
+ // ... other content types
+ };
+}
+```
+
+### 2. Message Persistence
+
+Messages are stored in both database and memory for redundancy:
+
+```typescript
+// lib/supabase/adapter.ts - processWebhookPayload method
+async processWebhookPayload(payload: WebhookPayload) {
+ // 1. Get or create user
+ const userId = await this.getUserFromWebhook(
+ payload.sender_number,
+ payload.sender_name,
+ payload.service
+ );
+
+ // 2. Get or create chat/thread
+ const chatId = await this.getChatFromWebhook(
+ payload.thread_id,
+ payload.thread_type,
+ payload.service
+ );
+
+ // 3. Add user as participant
+ await this.addParticipantToChat(chatId, userId);
+
+ // 4. Store the message
+ await this.storeMessage(
+ chatId,
+ userId,
+ payload.message_id,
+ payload.message_content,
+ payload.message_type,
+ payload.service
+ );
+}
+```
+
+### 3. Message Processing Pipeline
+
+The main processing happens in `handleWhatsAppIncoming`:
+
+```typescript
+// lib/ai-triage/handle-whatsapp-incoming.ts
+export async function handleWhatsAppIncoming(webhookData: WebhookPayload) {
+ // 1. Skip agent's own messages
+ if (sender_number === process.env.A1BASE_AGENT_NUMBER) {
+ // Store but don't process
+ return;
+ }
+
+ // 2. Check group chat mention requirements
+ if (thread_type === 'group' && respondOnlyWhenMentioned && !agent_mentioned) {
+ // Store but skip processing
+ return;
+ }
+
+ // 3. Process multimedia messages
+ if (message_type !== 'text') {
+ processedContent = processIncomingMediaMessage(message_content, message_type);
+ }
+
+ // 4. Persist message
+ const { chatId, isNewChatInDb } = await persistIncomingMessage(webhookData, adapter);
+
+ // 5. Check for onboarding needs
+ const shouldTriggerOnboarding = await checkIfOnboardingNeeded(thread_id, adapter, threadMessages);
+
+ // 6. Handle onboarding or standard triage
+ if (shouldTriggerOnboarding) {
+ await manageIndividualOnboardingProcess(threadMessages, webhookData, adapter, chatId);
+ } else {
+ const triageResult = await triageMessage({
+ thread_id,
+ message_id,
+ content: processedContent,
+ // ... other params
+ });
+ }
+
+ // 7. Send response
+ await sendResponseMessage(triageResponseText, thread_type, recipient, service, chatId, adapter);
+}
+```
+
+## Core Components
+
+### 1. Triage System
+
+The triage system uses OpenAI to classify incoming messages and determine the appropriate workflow:
+
+```typescript
+// lib/services/openai.ts
+export async function triageMessageIntent(
+ threadMessages: ThreadMessage[],
+ projects: any[] = []
+): Promise {
+ // AI analyzes conversation context and returns:
+ return {
+ responseType: "simpleResponse" | "projectFlow" | "emailReportFlow" | "onboardingFlow",
+ // Additional context-specific fields
+ projectAction?: "create" | "update" | "complete" | "reference",
+ projectName?: string,
+ // ... other fields
+ };
+}
+```
+
+### 2. Workflow Router
+
+Based on triage results, messages are routed to appropriate workflows:
+
+```typescript
+// lib/ai-triage/triage-logic.ts
+switch (triage.responseType) {
+ case "onboardingFlow":
+ // Handle new user onboarding
+ break;
+
+ case "projectFlow":
+ // Handle project management requests
+ break;
+
+ case "emailReportFlow":
+ // Handle email report requests
+ break;
+
+ case "simpleResponse":
+ default:
+ // Generate standard conversational response
+ break;
+}
+```
+
+### 3. Response Generation
+
+Each workflow generates responses using AI with specific prompts:
+
+```typescript
+// lib/workflows/basic_workflow.ts
+export async function DefaultReplyToMessage(
+ threadMessages: ThreadMessage[],
+ thread_type: "individual" | "group",
+ // ... other params
+): Promise {
+ // Generate AI response with context
+ const aiResponse = await generateAgentResponse(
+ threadMessages,
+ basicWorkflowsPrompt.simple_response.user,
+ thread_type,
+ participants,
+ projects,
+ service
+ );
+
+ // Send response through appropriate channel
+ await _sendWhatsAppMessage(thread_type, recipientId, aiResponse);
+
+ return aiResponse;
+}
+```
+
+## Triage System
+
+### Intent Classification
+
+The triage system uses a sophisticated prompt to classify intents:
+
+```typescript
+const triagePrompt = `
+Based on the conversation, analyze the user's intent and respond with JSON:
+- "responseType": one of ["simpleResponse", "projectFlow", "emailReportFlow", "onboardingFlow"]
+- Additional fields based on intent.
+
+Rules:
+- "projectFlow": Used for ALL project-related actions
+- "emailReportFlow": Used for email report requests
+- "onboardingFlow": Related to user onboarding
+- "simpleResponse": Default for all other messages
+
+For "projectFlow" responses, include:
+- "projectAction": one of ["create", "update", "complete", "reference"]
+- "projectName": Name of the project
+- "projectDescription": Description (for create)
+`;
+```
+
+### Context Awareness
+
+The triage system considers:
+- Conversation history
+- Existing projects
+- User preferences
+- Previous interactions
+- Current context
+
+## Workflow Implementation
+
+### 1. Simple Response Workflow
+
+Handles general conversational messages:
+
+```typescript
+// Uses OpenAI to generate contextual responses
+// Considers conversation history and user context
+// Applies personality and tone settings from agent profile
+```
+
+### 2. Project Management Workflow
+
+Handles CRUD operations for projects:
+
+```typescript
+case "projectFlow":
+ const projectAction = triage.projectAction;
+
+ switch(projectAction) {
+ case "create":
+ await adapter.createProject(projectName, projectDescription, chatId);
+ break;
+ case "update":
+ await adapter.updateProject(projectId, updates);
+ break;
+ case "complete":
+ await adapter.updateProject(projectId, { is_live: false });
+ break;
+ }
+```
+
+### 3. Onboarding Workflow
+
+Handles new user onboarding:
+
+```typescript
+// Loads onboarding configuration
+const onboardingFlow = await loadOnboardingFlow();
+
+// Generates conversational onboarding messages
+const onboardingMessage = await generateOnboardingMessage(
+ systemPrompt,
+ threadMessages,
+ userMessage,
+ service
+);
+```
+
+## Multi-Agent Considerations
+
+### 1. Agent Selection
+
+For a multi-agent system, extend the triage to include agent selection:
+
+```typescript
+interface MultiAgentTriageResult {
+ responseType: string;
+ selectedAgent: string; // 'sales', 'support', 'technical', etc.
+ confidence: number;
+ handoffRequired: boolean;
+ context: any;
+}
+```
+
+### 2. Agent Profiles
+
+Each agent should have a distinct profile:
+
+```typescript
+interface AgentProfile {
+ id: string;
+ name: string;
+ specialties: string[];
+ systemPrompt: string;
+ capabilities: string[];
+ escalationAgent?: string;
+}
+```
+
+### 3. Context Handoff
+
+When transferring between agents:
+
+```typescript
+interface AgentHandoff {
+ fromAgent: string;
+ toAgent: string;
+ reason: string;
+ context: {
+ conversation: ThreadMessage[];
+ userInfo: any;
+ currentIntent: string;
+ metadata: any;
+ };
+}
+```
+
+### 4. Agent Routing Logic
+
+```typescript
+async function routeToAgent(
+ triageResult: MultiAgentTriageResult,
+ threadMessages: ThreadMessage[]
+): Promise {
+ const agent = await getAgent(triageResult.selectedAgent);
+
+ // Check if agent can handle request
+ if (!agent.canHandle(triageResult.context)) {
+ // Escalate to supervisor or fallback agent
+ return await escalateToAgent(agent.escalationAgent, triageResult);
+ }
+
+ // Process with selected agent
+ return await agent.processRequest(threadMessages, triageResult.context);
+}
+```
+
+## Implementation Examples
+
+### 1. Adding a New Workflow
+
+To add a new workflow (e.g., appointment scheduling):
+
+```typescript
+// 1. Update triage intent types
+export async function triageMessageIntent() {
+ // Add "appointmentFlow" to responseType options
+}
+
+// 2. Add workflow handler
+export async function handleAppointmentFlow(
+ messages: ThreadMessage[],
+ appointmentAction: string
+) {
+ switch(appointmentAction) {
+ case "schedule":
+ // Schedule appointment logic
+ break;
+ case "reschedule":
+ // Reschedule logic
+ break;
+ case "cancel":
+ // Cancellation logic
+ break;
+ }
+}
+
+// 3. Update triage router
+switch (triage.responseType) {
+ case "appointmentFlow":
+ await handleAppointmentFlow(messages, triage.appointmentAction);
+ break;
+}
+```
+
+### 2. Implementing Agent Specialization
+
+```typescript
+// Define agent specialties
+const AGENT_SPECIALTIES = {
+ sales: {
+ keywords: ['pricing', 'buy', 'purchase', 'cost', 'plan'],
+ intents: ['product_inquiry', 'pricing_request', 'demo_request'],
+ systemPrompt: 'You are a helpful sales agent...'
+ },
+ support: {
+ keywords: ['help', 'issue', 'problem', 'not working', 'error'],
+ intents: ['technical_support', 'bug_report', 'troubleshooting'],
+ systemPrompt: 'You are a technical support agent...'
+ }
+};
+
+// Agent selection logic
+function selectAgent(message: string, intent: string): string {
+ // Score each agent based on keyword matches and intent alignment
+ const scores = {};
+
+ for (const [agent, config] of Object.entries(AGENT_SPECIALTIES)) {
+ let score = 0;
+
+ // Check keyword matches
+ config.keywords.forEach(keyword => {
+ if (message.toLowerCase().includes(keyword)) score += 10;
+ });
+
+ // Check intent alignment
+ if (config.intents.includes(intent)) score += 20;
+
+ scores[agent] = score;
+ }
+
+ // Return highest scoring agent
+ return Object.entries(scores)
+ .sort(([,a], [,b]) => b - a)[0][0];
+}
+```
+
+### 3. Implementing Context Preservation
+
+```typescript
+// Store agent context between interactions
+interface AgentContext {
+ currentAgent: string;
+ conversationState: any;
+ userPreferences: any;
+ taskProgress: any;
+}
+
+async function preserveAgentContext(
+ chatId: string,
+ context: AgentContext
+): Promise {
+ await adapter.upsertChatThreadMemoryValue(
+ chatId,
+ 'agent_context',
+ JSON.stringify(context)
+ );
+}
+
+async function retrieveAgentContext(
+ chatId: string
+): Promise {
+ const stored = await adapter.getChatThreadMemoryValue(
+ chatId,
+ 'agent_context'
+ );
+
+ return stored ? JSON.parse(stored) : null;
+}
+```
+
+## Best Practices
+
+### 1. Error Handling
+
+Always implement robust error handling:
+
+```typescript
+try {
+ const triageResult = await triageMessage(params);
+ // Process result
+} catch (error) {
+ console.error('[Triage] Error:', error);
+ // Fallback to default response
+ return {
+ type: "default",
+ success: false,
+ message: "I'm having trouble processing your request. Please try again."
+ };
+}
+```
+
+### 2. Performance Optimization
+
+- Use parallel processing where possible
+- Cache frequently accessed data
+- Implement message batching for high volume
+- Use connection pooling for database
+
+### 3. Monitoring and Logging
+
+- Log all triage decisions
+- Track response times
+- Monitor error rates
+- Implement alerting for failures
+
+### 4. Testing
+
+- Unit test each workflow
+- Integration test the full pipeline
+- Load test with realistic message volumes
+- Test edge cases and error scenarios
+
+## Conclusion
+
+This architecture provides a flexible foundation for building multi-agent messaging systems. The key components - webhook handling, message persistence, AI-powered triage, and modular workflows - can be extended and customized to support various use cases and agent configurations.
+
+The system's strength lies in its:
+- **Modularity**: Easy to add new workflows and agents
+- **Scalability**: Can handle multiple channels and high message volumes
+- **Intelligence**: AI-powered intent classification and response generation
+- **Flexibility**: Supports various message types and interaction patterns
+
+When implementing a multi-agent system, focus on:
+1. Clear agent boundaries and specializations
+2. Smooth context handoffs between agents
+3. Consistent user experience across agents
+4. Robust error handling and fallback mechanisms
+
+
+
+The files in a1framework that implement this. Feel free to ask for the contents of these files when implementing this in a1zap.
+
+PI Files (Routes/Endpoints)
+Primary Message Ingestion:
+app/api/a1base/messaging/route.ts
+Main webhook endpoint for all messaging channels (WhatsApp, SMS, RCS, iMessage)
+Receives WebhookPayload and routes to appropriate handlers
+Channel-Specific Routes:
+app/api/a1base/sms/route.ts
+Dedicated SMS webhook handler
+Processes SMS-specific payloads
+app/api/a1base/sms/status/route.ts
+Handles SMS delivery status updates
+app/api/a1base/email/route.ts
+Email webhook handler for incoming emails
+Web UI Integration:
+app/api/chat/route.ts
+Web UI chat endpoint
+Handles messages from the web interface
+app/api/chat/socket-message/route.ts
+Real-time message handling for web UI
+Library Files
+Core Message Processing:
+lib/ai-triage/handle-whatsapp-incoming.ts
+Main message processing pipeline
+Handles message persistence, onboarding checks, and triage routing
+lib/ai-triage/triage-logic.ts
+Message triage router
+Routes messages to appropriate workflows based on intent
+lib/ai-triage/types.ts
+Type definitions for triage system
+AI/OpenAI Integration:
+lib/services/openai.ts
+AI intent classification (triageMessageIntent)
+Response generation functions
+Database/Persistence:
+lib/supabase/adapter.ts
+Database operations including processWebhookPayload
+User, chat, and message management
+lib/supabase/config.ts
+Database configuration and initialization
+lib/data/message-storage.ts
+Message storage operations
+In-memory and database storage functions
+Workflows:
+lib/workflows/basic_workflow.ts
+DefaultReplyToMessage - main response generation
+Message sending logic
+lib/workflows/onboarding-workflow.ts
+StartOnboarding - handles new user onboarding
+lib/workflows/email_workflow.ts
+Email-related workflows
+lib/workflows/group-onboarding-workflow.ts
+Group chat onboarding logic
+Messaging/Communication:
+lib/messaging/multimedia-handler.ts
+Handles multimedia message processing
+Webhook Processing:
+lib/webhooks/sms-processor.ts
+SMS webhook processing and transformation
+Configuration:
+lib/agent/system-prompt.ts
+Agent personality and system prompt configuration
+lib/agent-profile/agent-profile-settings.ts
+Agent profile settings management
+lib/settings/message-settings.ts
+Message processing settings
+Memory/Context:
+lib/agent-memory/memory-processor.ts
+Processes messages for memory updates
+Flow Summary
+The typical flow uses these files in sequence:
+Webhook Reception: app/api/a1base/messaging/route.ts
+Message Processing: lib/ai-triage/handle-whatsapp-incoming.ts
+Persistence: lib/supabase/adapter.ts (processWebhookPayload)
+Triage: lib/services/openai.ts (triageMessageIntent)
+Routing: lib/ai-triage/triage-logic.ts
+Workflow Execution: lib/workflows/basic_workflow.ts or other workflow files
+Response Generation: lib/services/openai.ts (generateAgentResponse)
+Message Delivery: Back through lib/workflows/basic_workflow.ts
+This modular structure allows for easy extension and modification of individual components without affecting the entire system.
\ No newline at end of file
diff --git a/a1framework-notes/a1framework-initialsupabase.sql b/a1framework-notes/a1framework-initialsupabase.sql
new file mode 100644
index 0000000..6c57e5a
--- /dev/null
+++ b/a1framework-notes/a1framework-initialsupabase.sql
@@ -0,0 +1,182 @@
+-- To setup your Supabase database, copy and paste this SQL code into
+-- https://app.supabase.io/project/YOUR_PROJECT_ID/settings/database/SQL
+
+-- Note: Row Level Security (RLS) policies are already enabled on all
+-- tables for authenticated users.
+
+-- Create tables
+CREATE TABLE public.conversation_users (
+ id uuid NOT NULL DEFAULT gen_random_uuid(),
+ created_at timestamp with time zone NOT NULL DEFAULT now(),
+ name text NULL,
+ phone_number text NULL,
+ service text NULL,
+ is_agent boolean NULL DEFAULT false,
+ metadata jsonb NULL,
+ memory jsonb NULL DEFAULT '{}'::jsonb,
+ CONSTRAINT conversation_users_pkey PRIMARY KEY (id)
+) WITH (OIDS=FALSE);
+CREATE INDEX IF NOT EXISTS idx_users_phone ON public.conversation_users USING btree (phone_number);
+
+CREATE TABLE public.chats (
+ id uuid NOT NULL DEFAULT gen_random_uuid(),
+ created_at timestamp with time zone NOT NULL DEFAULT now(),
+ type text NOT NULL,
+ name text NULL,
+ external_id text NULL,
+ service text NULL,
+ metadata jsonb NULL,
+ memory jsonb NULL DEFAULT '{}'::jsonb,
+ CONSTRAINT chats_pkey PRIMARY KEY (id),
+ CONSTRAINT chats_type_check CHECK ((type = ANY (ARRAY['individual'::text, 'group'::text])))
+) WITH (OIDS=FALSE);
+CREATE INDEX IF NOT EXISTS idx_chats_external_id ON public.chats USING btree (external_id);
+
+CREATE TABLE public.chat_participants (
+ chat_id uuid NOT NULL,
+ user_id uuid NOT NULL,
+ CONSTRAINT chat_participants_pkey PRIMARY KEY (chat_id, user_id),
+ CONSTRAINT chat_participants_chat_id_fkey FOREIGN KEY (chat_id) REFERENCES public.chats(id),
+ CONSTRAINT chat_participants_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.conversation_users(id)
+) WITH (OIDS=FALSE);
+CREATE INDEX IF NOT EXISTS idx_chat_participants_chat_id ON public.chat_participants USING btree (chat_id);
+CREATE INDEX IF NOT EXISTS idx_chat_participants_user_id ON public.chat_participants USING btree (user_id);
+
+CREATE TABLE public.messages (
+ id uuid NOT NULL DEFAULT gen_random_uuid(),
+ chat_id uuid NULL,
+ sender_id uuid NULL,
+ content text NOT NULL,
+ created_at timestamp with time zone NOT NULL DEFAULT now(),
+ message_type text NULL,
+ external_id text NULL,
+ rich_content jsonb NULL,
+ service text NULL DEFAULT 'whatsapp',
+ status text NULL DEFAULT 'sent',
+ status_updated_at timestamp with time zone NULL,
+ CONSTRAINT messages_pkey PRIMARY KEY (id),
+ CONSTRAINT messages_chat_id_fkey FOREIGN KEY (chat_id) REFERENCES public.chats(id),
+ CONSTRAINT messages_sender_id_fkey FOREIGN KEY (sender_id) REFERENCES public.conversation_users(id)
+) WITH (OIDS=FALSE);
+CREATE INDEX IF NOT EXISTS idx_messages_external_id ON public.messages USING btree (external_id);
+CREATE INDEX IF NOT EXISTS idx_messages_chat_id ON public.messages USING btree (chat_id);
+CREATE INDEX IF NOT EXISTS idx_messages_sender_id ON public.messages USING btree (sender_id);
+CREATE INDEX IF NOT EXISTS idx_messages_created_at ON public.messages USING btree (created_at);
+CREATE INDEX IF NOT EXISTS idx_messages_service ON public.messages USING btree (service);
+
+CREATE TABLE public.cron_jobs (
+ id uuid NOT NULL DEFAULT gen_random_uuid(),
+ job_name text NOT NULL,
+ scheduled_time timestamp with time zone NOT NULL,
+ status text NOT NULL DEFAULT 'pending'::text,
+ result text NULL,
+ created_at timestamp with time zone NOT NULL DEFAULT now(),
+ CONSTRAINT cron_jobs_pkey PRIMARY KEY (id)
+) WITH (OIDS=FALSE);
+
+CREATE TABLE public.user_preferences (
+ user_id uuid NOT NULL,
+ preferences jsonb NOT NULL DEFAULT '{}'::jsonb,
+ CONSTRAINT user_preferences_pkey PRIMARY KEY (user_id),
+ CONSTRAINT user_preferences_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.conversation_users(id)
+) WITH (OIDS=FALSE);
+
+CREATE TABLE public.projects (
+ id uuid NOT NULL DEFAULT gen_random_uuid(),
+ chat_id uuid NULL,
+ name text NOT NULL,
+ description text NULL,
+ created_at timestamp with time zone NOT NULL DEFAULT now(),
+ updated_at timestamp with time zone NOT NULL DEFAULT now(),
+ is_live boolean NULL DEFAULT true,
+ attributes jsonb NULL DEFAULT '{}'::jsonb,
+ CONSTRAINT projects_pkey PRIMARY KEY (id),
+ CONSTRAINT projects_chat_id_fkey FOREIGN KEY (chat_id) REFERENCES public.chats(id)
+) WITH (OIDS=FALSE);
+CREATE INDEX IF NOT EXISTS idx_projects_chat_id ON public.projects USING btree (chat_id);
+CREATE INDEX IF NOT EXISTS idx_projects_chat_live_created ON public.projects (chat_id, is_live, created_at);
+CREATE INDEX IF NOT EXISTS idx_projects_is_live ON public.projects USING btree (is_live);
+
+-- Add trigger to update updated_at timestamp
+CREATE OR REPLACE FUNCTION update_updated_at_column()
+RETURNS TRIGGER AS $$
+BEGIN
+ NEW.updated_at = now();
+ RETURN NEW;
+END;
+$$ language 'plpgsql';
+
+CREATE TRIGGER update_projects_updated_at BEFORE UPDATE ON public.projects
+ FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+
+CREATE TABLE public.project_history (
+ id uuid NOT NULL DEFAULT gen_random_uuid(),
+ project_id uuid NULL,
+ event_type text NOT NULL,
+ details text NULL,
+ created_at timestamp with time zone NOT NULL DEFAULT now(),
+ CONSTRAINT project_history_pkey PRIMARY KEY (id),
+ CONSTRAINT project_history_project_id_fkey FOREIGN KEY (project_id) REFERENCES public.projects(id)
+) WITH (OIDS=FALSE);
+CREATE INDEX IF NOT EXISTS idx_project_history_project_id ON public.project_history USING btree (project_id);
+
+-- Project events table for tracking actions/tasks
+CREATE TABLE public.project_events (
+ id uuid NOT NULL DEFAULT gen_random_uuid(),
+ project_id uuid NOT NULL,
+ event_type text NOT NULL,
+ description text NULL,
+ metadata jsonb NULL DEFAULT '{}'::jsonb,
+ created_at timestamp with time zone NOT NULL DEFAULT now(),
+ CONSTRAINT project_events_pkey PRIMARY KEY (id),
+ CONSTRAINT project_events_project_id_fkey FOREIGN KEY (project_id) REFERENCES public.projects(id) ON DELETE CASCADE
+) WITH (OIDS=FALSE);
+CREATE INDEX IF NOT EXISTS idx_project_events_project_id ON public.project_events USING btree (project_id);
+CREATE INDEX IF NOT EXISTS idx_project_events_created_at ON public.project_events USING btree (created_at);
+
+-- Email scheduled reports table
+CREATE TABLE public.email_scheduled_reports (
+ id uuid NOT NULL DEFAULT gen_random_uuid(),
+ user_id uuid NOT NULL,
+ email_address text NOT NULL,
+ frequency text NOT NULL CHECK (frequency IN ('daily', 'weekly', 'monthly')),
+ scheduled_time time NOT NULL DEFAULT '09:00'::time,
+ timezone text NOT NULL DEFAULT 'UTC',
+ is_active boolean NOT NULL DEFAULT true,
+ report_type text NOT NULL DEFAULT 'project_status',
+ metadata jsonb NULL DEFAULT '{}'::jsonb,
+ created_at timestamp with time zone NOT NULL DEFAULT now(),
+ updated_at timestamp with time zone NOT NULL DEFAULT now(),
+ last_sent_at timestamp with time zone NULL,
+ next_send_at timestamp with time zone NULL,
+ CONSTRAINT email_scheduled_reports_pkey PRIMARY KEY (id),
+ CONSTRAINT email_scheduled_reports_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.conversation_users(id) ON DELETE CASCADE
+) WITH (OIDS=FALSE);
+CREATE INDEX IF NOT EXISTS idx_email_scheduled_reports_user_id ON public.email_scheduled_reports USING btree (user_id);
+CREATE INDEX IF NOT EXISTS idx_email_scheduled_reports_is_active ON public.email_scheduled_reports USING btree (is_active);
+CREATE INDEX IF NOT EXISTS idx_email_scheduled_reports_next_send ON public.email_scheduled_reports USING btree (next_send_at) WHERE is_active = true;
+
+-- Add trigger to update updated_at timestamp for scheduled reports
+CREATE TRIGGER update_email_scheduled_reports_updated_at BEFORE UPDATE ON public.email_scheduled_reports
+ FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+
+-- Email report history table
+CREATE TABLE public.email_report_history (
+ id uuid NOT NULL DEFAULT gen_random_uuid(),
+ user_id uuid NOT NULL,
+ scheduled_report_id uuid NULL,
+ email_address text NOT NULL,
+ report_type text NOT NULL,
+ frequency text NULL,
+ status text NOT NULL CHECK (status IN ('sent', 'failed', 'pending')),
+ subject text NULL,
+ error_message text NULL,
+ metadata jsonb NULL DEFAULT '{}'::jsonb,
+ sent_at timestamp with time zone NOT NULL DEFAULT now(),
+ CONSTRAINT email_report_history_pkey PRIMARY KEY (id),
+ CONSTRAINT email_report_history_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.conversation_users(id) ON DELETE CASCADE,
+ CONSTRAINT email_report_history_scheduled_report_id_fkey FOREIGN KEY (scheduled_report_id) REFERENCES public.email_scheduled_reports(id) ON DELETE SET NULL
+) WITH (OIDS=FALSE);
+CREATE INDEX IF NOT EXISTS idx_email_report_history_user_id ON public.email_report_history USING btree (user_id);
+CREATE INDEX IF NOT EXISTS idx_email_report_history_sent_at ON public.email_report_history USING btree (sent_at);
+CREATE INDEX IF NOT EXISTS idx_email_report_history_status ON public.email_report_history USING btree (status);
\ No newline at end of file
diff --git a/a1mail/send-calendar-invites.mdx b/a1mail/send-calendar-invites.mdx
new file mode 100644
index 0000000..66b3846
--- /dev/null
+++ b/a1mail/send-calendar-invites.mdx
@@ -0,0 +1,354 @@
+---
+title: 'Send Calendar Invites'
+description: 'Send calendar invites programmatically through the A1Mail API with just a few lines of code'
+---
+
+## Request Parameters
+
+The following parameters are used when sending a calendar invite:
+
+| Parameter | Type | Required | Description |
+|-----------|------|----------|-------------|
+| `sender_address` | string | Yes | Email address that will appear in the "From" field |
+| `recipient_address` | string | Yes | Email address of the recipient |
+| `event_title` | string | Yes | Title of the calendar event |
+| `start_time` | string | Yes | Start time of the event in ISO 8601 format (UTC) |
+| `end_time` | string | Yes | End time of the event in ISO 8601 format (UTC) |
+| `organizer_name` | string | Yes | Name of the event organizer |
+| `method` | string | Yes | Calendar method (REQUEST, CANCEL, REPLY) |
+| `location` | string | No | Location of the event |
+| `description` | string | No | Description of the event |
+| `attendees` | array | No | Array of attendee email addresses |
+| `mail_headers` | object | No | Additional email headers (sequence, etc.) |
+
+## Code Examples
+
+
+
+ ### Send a Basic Calendar Invite
+
+ ```bash
+ curl --location 'https://api.a1base.com/v1/emails/{account_id}/send-calendar-invite' \
+ --header 'X-API-Key: YOUR_API_KEY' \
+ --header 'X-API-Secret: YOUR_API_SECRET' \
+ --header 'Content-Type: application/json' \
+ --data-raw '{
+ "sender_address": "sender@example.com",
+ "recipient_address": "recipient@example.com",
+ "event_title": "Team Meeting",
+ "start_time": "2024-06-10T10:00:00Z",
+ "end_time": "2024-06-10T11:00:00Z",
+ "organizer_name": "Alice Smith",
+ "method": "REQUEST"
+ }'
+ ```
+
+ ### Send a Detailed Calendar Invite
+
+ ```bash
+ curl --location 'https://api.a1base.com/v1/emails/{account_id}/send-calendar-invite' \
+ --header 'X-API-Key: YOUR_API_KEY' \
+ --header 'X-API-Secret: YOUR_API_SECRET' \
+ --header 'Content-Type: application/json' \
+ --data-raw '{
+ "sender_address": "sender@example.com",
+ "recipient_address": "recipient@example.com",
+ "event_title": "Team Meeting",
+ "start_time": "2024-06-10T10:00:00Z",
+ "end_time": "2024-06-10T11:00:00Z",
+ "organizer_name": "Alice Smith",
+ "method": "REQUEST",
+ "location": "Conference Room 1",
+ "description": "Monthly team sync-up",
+ "attendees": ["recipient@example.com", "pennie@a1base.com"],
+ "mail_headers": {
+ "sequence": 0
+ }
+ }'
+ ```
+
+ ### Cancel a Calendar Event
+
+ ```bash
+ curl --location 'https://api.a1base.com/v1/emails/{account_id}/send-calendar-invite' \
+ --header 'X-API-Key: YOUR_API_KEY' \
+ --header 'X-API-Secret: YOUR_API_SECRET' \
+ --header 'Content-Type: application/json' \
+ --data-raw '{
+ "sender_address": "sender@example.com",
+ "recipient_address": "recipient@example.com",
+ "event_title": "Team Meeting",
+ "start_time": "2024-06-10T10:00:00Z",
+ "end_time": "2024-06-10T11:00:00Z",
+ "organizer_name": "Alice Smith",
+ "method": "CANCEL",
+ "location": "Conference Room 1",
+ "description": "Meeting has been cancelled",
+ "attendees": ["recipient@example.com", "pennie@a1base.com"],
+ "mail_headers": {
+ "sequence": 1
+ }
+ }'
+ ```
+
+
+
+ ### Send a Basic Calendar Invite
+
+ ```python
+ import requests
+ import json
+
+ url = "https://api.a1base.com/v1/emails/{account_id}/send-calendar-invite"
+
+ headers = {
+ 'X-API-Key': 'YOUR_API_KEY',
+ 'X-API-Secret': 'YOUR_API_SECRET',
+ 'Content-Type': 'application/json'
+ }
+
+ data = {
+ "sender_address": "sender@example.com",
+ "recipient_address": "recipient@example.com",
+ "event_title": "Team Meeting",
+ "start_time": "2024-06-10T10:00:00Z",
+ "end_time": "2024-06-10T11:00:00Z",
+ "organizer_name": "Alice Smith",
+ "method": "REQUEST"
+ }
+
+ response = requests.post(url, headers=headers, data=json.dumps(data))
+ print(response.json())
+ ```
+
+ ### Send a Detailed Calendar Invite
+
+ ```python
+ import requests
+ import json
+
+ url = "https://api.a1base.com/v1/emails/{account_id}/send-calendar-invite"
+
+ headers = {
+ 'X-API-Key': 'YOUR_API_KEY',
+ 'X-API-Secret': 'YOUR_API_SECRET',
+ 'Content-Type': 'application/json'
+ }
+
+ data = {
+ "sender_address": "sender@example.com",
+ "recipient_address": "recipient@example.com",
+ "event_title": "Team Meeting",
+ "start_time": "2024-06-10T10:00:00Z",
+ "end_time": "2024-06-10T11:00:00Z",
+ "organizer_name": "Alice Smith",
+ "method": "REQUEST",
+ "location": "Conference Room 1",
+ "description": "Monthly team sync-up",
+ "attendees": ["recipient@example.com", "pennie@a1base.com"],
+ "mail_headers": {
+ "sequence": 0
+ }
+ }
+
+ response = requests.post(url, headers=headers, data=json.dumps(data))
+ print(response.json())
+ ```
+
+ ### Cancel a Calendar Event
+
+ ```python
+ import requests
+ import json
+
+ url = "https://api.a1base.com/v1/emails/{account_id}/send-calendar-invite"
+
+ headers = {
+ 'X-API-Key': 'YOUR_API_KEY',
+ 'X-API-Secret': 'YOUR_API_SECRET',
+ 'Content-Type': 'application/json'
+ }
+
+ data = {
+ "sender_address": "sender@example.com",
+ "recipient_address": "recipient@example.com",
+ "event_title": "Team Meeting",
+ "start_time": "2024-06-10T10:00:00Z",
+ "end_time": "2024-06-10T11:00:00Z",
+ "organizer_name": "Alice Smith",
+ "method": "CANCEL",
+ "location": "Conference Room 1",
+ "description": "Meeting has been cancelled",
+ "attendees": ["recipient@example.com", "pennie@a1base.com"],
+ "mail_headers": {
+ "sequence": 1
+ }
+ }
+
+ response = requests.post(url, headers=headers, data=json.dumps(data))
+ print(response.json())
+ ```
+
+
+
+ ### Send a Basic Calendar Invite
+
+ ```javascript
+ const axios = require('axios');
+
+ const url = 'https://api.a1base.com/v1/emails/{account_id}/send-calendar-invite';
+
+ const headers = {
+ 'X-API-Key': 'YOUR_API_KEY',
+ 'X-API-Secret': 'YOUR_API_SECRET',
+ 'Content-Type': 'application/json'
+ };
+
+ const data = {
+ sender_address: 'sender@example.com',
+ recipient_address: 'recipient@example.com',
+ event_title: 'Team Meeting',
+ start_time: '2024-06-10T10:00:00Z',
+ end_time: '2024-06-10T11:00:00Z',
+ organizer_name: 'Alice Smith',
+ method: 'REQUEST'
+ };
+
+ axios.post(url, data, { headers })
+ .then(response => console.log(response.data))
+ .catch(error => console.error('Error:', error));
+ ```
+
+ ### Send a Detailed Calendar Invite
+
+ ```javascript
+ const axios = require('axios');
+
+ const url = 'https://api.a1base.com/v1/emails/{account_id}/send-calendar-invite';
+
+ const headers = {
+ 'X-API-Key': 'YOUR_API_KEY',
+ 'X-API-Secret': 'YOUR_API_SECRET',
+ 'Content-Type': 'application/json'
+ };
+
+ const data = {
+ sender_address: 'sender@example.com',
+ recipient_address: 'recipient@example.com',
+ event_title: 'Team Meeting',
+ start_time: '2024-06-10T10:00:00Z',
+ end_time: '2024-06-10T11:00:00Z',
+ organizer_name: 'Alice Smith',
+ method: 'REQUEST',
+ location: 'Conference Room 1',
+ description: 'Monthly team sync-up',
+ attendees: ['recipient@example.com', 'pennie@a1base.com'],
+ mail_headers: {
+ sequence: 0
+ }
+ };
+
+ axios.post(url, data, { headers })
+ .then(response => console.log(response.data))
+ .catch(error => console.error('Error:', error));
+ ```
+
+ ### Cancel a Calendar Event
+
+ ```javascript
+ const axios = require('axios');
+
+ const url = 'https://api.a1base.com/v1/emails/{account_id}/send-calendar-invite';
+
+ const headers = {
+ 'X-API-Key': 'YOUR_API_KEY',
+ 'X-API-Secret': 'YOUR_API_SECRET',
+ 'Content-Type': 'application/json'
+ };
+
+ const data = {
+ sender_address: 'sender@example.com',
+ recipient_address: 'recipient@example.com',
+ event_title: 'Team Meeting',
+ start_time: '2024-06-10T10:00:00Z',
+ end_time: '2024-06-10T11:00:00Z',
+ organizer_name: 'Alice Smith',
+ method: 'CANCEL',
+ location: 'Conference Room 1',
+ description: 'Meeting has been cancelled',
+ attendees: ['recipient@example.com', 'pennie@a1base.com'],
+ mail_headers: {
+ sequence: 1
+ }
+ };
+
+ axios.post(url, data, { headers })
+ .then(response => console.log(response.data))
+ .catch(error => console.error('Error:', error));
+ ```
+
+
+
+
+ We'd love to hear from you!
+
+ Don't hesitate to reach out to pennie@a1base.com or pasha@a1base.com if there's any features you'd like to see or prioritised!
+
+
+## Calendar Invite Methods
+
+The A1Mail API supports different calendar methods for managing events:
+
+### REQUEST
+Used to create new calendar events or invite attendees to an event. This is the most common method for sending calendar invites.
+
+### CANCEL
+Used to cancel an existing calendar event. When using this method, make sure to increment the `sequence` number in `mail_headers` to indicate this is an update to the original event.
+
+### REPLY
+Used by attendees to respond to calendar invites (Accept, Decline, Tentative). This method is typically used when building calendar applications that need to handle responses.
+
+## Time Format Guidelines
+
+All time values should be provided in ISO 8601 format with UTC timezone:
+
+- **Format**: `YYYY-MM-DDTHH:MM:SSZ`
+- **Example**: `2024-06-10T10:00:00Z`
+- **Time Zone**: Always use UTC (Z suffix)
+
+The recipient's calendar application will automatically convert times to their local timezone for display.
+
+## Working with Attendees
+
+When specifying attendees, provide an array of email addresses. The system will automatically:
+
+- Send calendar invites to all specified attendees
+- Handle RSVP responses if configured
+- Track attendance status for each attendee
+
+## Understanding Sequence Numbers
+
+The `mail_headers` field, specifically the `sequence` attribute, is used in calendar invites to manage updates to events. Here's how it works:
+
+### Purpose of Sequence in Calendar Invites
+
+**Event Versioning**: The sequence number is used to track the version of a calendar event. Each time an event is updated (e.g., time change, location change), the sequence number is incremented. This helps recipients' calendar applications understand that the event has been modified and that they should update the existing event details with the new information.
+
+**Conflict Resolution**: By using a sequence number, calendar systems can resolve conflicts between different versions of the same event. If a recipient receives multiple updates for the same event, the update with the highest sequence number is considered the most recent and authoritative.
+
+**Synchronization**: It ensures that all participants have the latest version of the event. When an event organizer sends an update, the sequence number helps ensure that all attendees' calendars are synchronized with the latest event details.
+
+### How It Works
+
+- **Initial Event**: When an event is first created, the sequence number is typically set to `0`
+- **Event Update**: If the event is updated, the sequence number is incremented (e.g., from `0` to `1`)
+- **Event Cancellation**: If the event is canceled, the sequence number is also incremented to indicate a change
+
+### Example Sequence Flow
+
+1. **Original Event**: `sequence: 0`
+2. **First Update**: `sequence: 1` (e.g., time changed)
+3. **Second Update**: `sequence: 2` (e.g., location changed)
+4. **Cancellation**: `sequence: 3`
+
+By using the sequence number, calendar applications can ensure that they are displaying the most current version of an event to users.
diff --git a/database-design.md b/database-design.md
new file mode 100644
index 0000000..660d925
--- /dev/null
+++ b/database-design.md
@@ -0,0 +1,345 @@
+# A1Zap Database Design
+
+## Overview
+This document outlines the complete database schema for A1Zap, an AI agent platform where users can create and manage AI agents (a1agents) built on the a1framework pattern.
+
+## Core Entities
+
+### 1. Users (via Stack Auth)
+- Managed by Stack Auth
+- Connected to our database via `userId` references
+- Each user can have multiple a1agents (limited by subscription)
+
+### 2. A1Agents
+The core entity representing an AI agent instance.
+
+### 3. Apps
+Server instances that run the a1agents (1:1 relationship with a1agents).
+
+### 4. Billing & Subscriptions
+Manages user subscriptions and billing information.
+
+### 5. Usage Tracking
+Tracks usage metrics for billing and analytics.
+
+## Database Schema
+
+### `a1_agents` Table
+Stores the core configuration and settings for each AI agent.
+
+```sql
+CREATE TABLE a1_agents (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ owner_id TEXT NOT NULL, -- Stack Auth user ID
+ app_id UUID UNIQUE NOT NULL REFERENCES apps(id) ON DELETE CASCADE,
+
+ -- Basic Information
+ name TEXT NOT NULL,
+ handle TEXT UNIQUE NOT NULL, -- @handle for the agent
+ description TEXT,
+ avatar_url TEXT,
+
+ -- Contact Information
+ email TEXT,
+ phone_number TEXT,
+ whatsapp_number TEXT,
+
+ -- Websites
+ base_website TEXT, -- Primary website
+ custom_website TEXT, -- Custom domain
+
+ -- Configuration
+ onboarding_data JSONB, -- Stores initial onboarding questions/answers
+ settings JSONB DEFAULT '{}', -- Additional agent settings
+
+ -- Status
+ status TEXT DEFAULT 'active', -- active, paused, archived
+ agent_type TEXT NOT NULL, -- personal, work
+
+ -- Timestamps
+ created_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
+
+ -- Constraints
+ CONSTRAINT valid_agent_type CHECK (agent_type IN ('personal', 'work')),
+ CONSTRAINT valid_status CHECK (status IN ('active', 'paused', 'archived')),
+ CONSTRAINT valid_handle CHECK (handle ~ '^[a-zA-Z0-9_]+$')
+);
+
+-- Indexes
+CREATE INDEX idx_a1_agents_owner_id ON a1_agents(owner_id);
+CREATE INDEX idx_a1_agents_handle ON a1_agents(handle);
+CREATE INDEX idx_a1_agents_status ON a1_agents(status);
+```
+
+### `user_subscriptions` Table
+Manages user subscription tiers and limits.
+
+```sql
+CREATE TABLE user_subscriptions (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ user_id TEXT UNIQUE NOT NULL, -- Stack Auth user ID
+
+ -- Subscription Details
+ plan_id TEXT NOT NULL DEFAULT 'free', -- free, pro, enterprise
+ status TEXT NOT NULL DEFAULT 'active', -- active, cancelled, past_due
+
+ -- Limits
+ max_personal_agents INTEGER DEFAULT 1,
+ max_work_agents INTEGER DEFAULT 1,
+ max_total_agents INTEGER DEFAULT 2,
+
+ -- Billing
+ stripe_customer_id TEXT,
+ stripe_subscription_id TEXT,
+ current_period_start TIMESTAMP,
+ current_period_end TIMESTAMP,
+
+ -- Timestamps
+ created_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ cancelled_at TIMESTAMP,
+
+ -- Constraints
+ CONSTRAINT valid_plan CHECK (plan_id IN ('free', 'pro', 'enterprise')),
+ CONSTRAINT valid_status CHECK (status IN ('active', 'cancelled', 'past_due', 'trialing'))
+);
+```
+
+### `subscription_plans` Table
+Defines available subscription tiers.
+
+```sql
+CREATE TABLE subscription_plans (
+ id TEXT PRIMARY KEY, -- free, pro, enterprise
+ name TEXT NOT NULL,
+ description TEXT,
+
+ -- Pricing
+ price_monthly DECIMAL(10, 2),
+ price_yearly DECIMAL(10, 2),
+ currency TEXT DEFAULT 'USD',
+
+ -- Limits
+ max_personal_agents INTEGER NOT NULL,
+ max_work_agents INTEGER NOT NULL,
+ max_messages_per_month INTEGER,
+ max_storage_gb INTEGER,
+
+ -- Features
+ features JSONB DEFAULT '[]',
+
+ -- Status
+ is_active BOOLEAN DEFAULT true,
+
+ created_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMP NOT NULL DEFAULT NOW()
+);
+
+-- Insert default plans
+INSERT INTO subscription_plans (id, name, max_personal_agents, max_work_agents, price_monthly) VALUES
+('free', 'Free', 1, 1, 0),
+('pro', 'Pro', 5, 5, 29.99),
+('enterprise', 'Enterprise', -1, -1, 99.99); -- -1 means unlimited
+```
+
+### `agent_usage` Table
+Tracks usage metrics for each agent.
+
+```sql
+CREATE TABLE agent_usage (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ agent_id UUID NOT NULL REFERENCES a1_agents(id) ON DELETE CASCADE,
+
+ -- Usage Metrics
+ date DATE NOT NULL,
+ message_count INTEGER DEFAULT 0,
+ api_calls INTEGER DEFAULT 0,
+ storage_bytes BIGINT DEFAULT 0,
+ compute_seconds INTEGER DEFAULT 0,
+
+ -- Cost Tracking
+ estimated_cost DECIMAL(10, 4) DEFAULT 0,
+
+ -- Timestamps
+ created_at TIMESTAMP NOT NULL DEFAULT NOW(),
+
+ -- Constraints
+ CONSTRAINT unique_agent_date UNIQUE (agent_id, date)
+);
+
+-- Indexes
+CREATE INDEX idx_agent_usage_agent_id_date ON agent_usage(agent_id, date);
+```
+
+### `billing_events` Table
+Tracks all billing-related events.
+
+```sql
+CREATE TABLE billing_events (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ user_id TEXT NOT NULL,
+
+ -- Event Details
+ event_type TEXT NOT NULL, -- subscription_created, payment_succeeded, payment_failed, etc.
+ amount DECIMAL(10, 2),
+ currency TEXT DEFAULT 'USD',
+ description TEXT,
+
+ -- References
+ subscription_id UUID REFERENCES user_subscriptions(id),
+ invoice_id TEXT, -- Stripe invoice ID
+
+ -- Metadata
+ metadata JSONB DEFAULT '{}',
+
+ -- Timestamps
+ created_at TIMESTAMP NOT NULL DEFAULT NOW()
+);
+
+-- Indexes
+CREATE INDEX idx_billing_events_user_id ON billing_events(user_id);
+CREATE INDEX idx_billing_events_type ON billing_events(event_type);
+```
+
+### `agent_conversations` Table
+Stores conversation history for each agent.
+
+```sql
+CREATE TABLE agent_conversations (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ agent_id UUID NOT NULL REFERENCES a1_agents(id) ON DELETE CASCADE,
+
+ -- Conversation Details
+ external_user_id TEXT, -- ID of the person talking to the agent
+ channel TEXT NOT NULL, -- web, whatsapp, email, etc.
+ thread_id TEXT, -- For threading conversations
+
+ -- Status
+ status TEXT DEFAULT 'active', -- active, archived
+
+ -- Timestamps
+ started_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ last_message_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ ended_at TIMESTAMP,
+
+ -- Metadata
+ metadata JSONB DEFAULT '{}'
+);
+
+-- Indexes
+CREATE INDEX idx_agent_conversations_agent_id ON agent_conversations(agent_id);
+CREATE INDEX idx_agent_conversations_external_user ON agent_conversations(external_user_id);
+```
+
+### `agent_integrations` Table
+Manages third-party integrations for agents.
+
+```sql
+CREATE TABLE agent_integrations (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ agent_id UUID NOT NULL REFERENCES a1_agents(id) ON DELETE CASCADE,
+
+ -- Integration Details
+ provider TEXT NOT NULL, -- whatsapp, slack, discord, etc.
+ status TEXT DEFAULT 'active',
+
+ -- Configuration
+ config JSONB NOT NULL DEFAULT '{}', -- Encrypted sensitive data
+
+ -- Timestamps
+ created_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ last_sync_at TIMESTAMP,
+
+ -- Constraints
+ CONSTRAINT unique_agent_provider UNIQUE (agent_id, provider)
+);
+```
+
+### Updates to Existing Tables
+
+#### `apps` Table (existing)
+Add a reference to track which app belongs to which agent:
+```sql
+ALTER TABLE apps ADD COLUMN agent_id UUID UNIQUE REFERENCES a1_agents(id);
+```
+
+#### `messages` Table (existing)
+Add fields to track which agent and conversation:
+```sql
+ALTER TABLE messages
+ ADD COLUMN agent_id UUID REFERENCES a1_agents(id),
+ ADD COLUMN conversation_id UUID REFERENCES agent_conversations(id);
+```
+
+## Key Relationships
+
+1. **User β A1Agents**: One-to-many (limited by subscription)
+2. **A1Agent β App**: One-to-one (bidirectional unique constraint)
+3. **User β Subscription**: One-to-one
+4. **A1Agent β Usage**: One-to-many
+5. **A1Agent β Conversations**: One-to-many
+6. **A1Agent β Integrations**: One-to-many
+
+## Business Rules
+
+1. **Agent Limits**:
+ - Free users: 1 personal + 1 work agent
+ - Pro users: 5 personal + 5 work agents
+ - Enterprise: Unlimited agents
+
+2. **Handle Requirements**:
+ - Must be unique across all agents
+ - Only alphanumeric and underscore characters
+ - Used for public agent URLs (e.g., `a1zap.com/@agent_handle`)
+
+3. **Billing**:
+ - Usage tracked daily per agent
+ - Billing calculated monthly based on usage
+ - Overage charges for exceeding plan limits
+
+4. **Agent Lifecycle**:
+ - Creating an agent automatically creates an associated app
+ - Deleting an agent cascades to delete the app and all related data
+ - Agents can be paused to stop billing but preserve data
+
+## Migration Strategy
+
+1. Create new tables in order of dependencies
+2. Update existing tables with new columns
+3. Migrate any existing data to new structure
+4. Add foreign key constraints
+5. Create indexes for performance
+
+## Security Considerations
+
+1. **Data Encryption**:
+ - Sensitive integration configs should be encrypted
+ - Phone numbers and emails should be encrypted at rest
+
+2. **Access Control**:
+ - Users can only access their own agents
+ - Admin users can access all agents for support
+
+3. **Rate Limiting**:
+ - Track API usage per agent
+ - Implement rate limits based on subscription tier
+
+## Future Considerations
+
+1. **Team Collaboration**:
+ - Add team/organization support
+ - Share agents between team members
+
+2. **Agent Templates**:
+ - Pre-configured agent templates
+ - Marketplace for sharing agent configurations
+
+3. **Analytics**:
+ - Detailed analytics per agent
+ - Performance metrics and insights
+
+4. **Audit Trail**:
+ - Track all changes to agents
+ - Compliance and debugging support
\ No newline at end of file
diff --git a/example-agent-jsons/agent-memory-settings.json b/example-agent-jsons/agent-memory-settings.json
new file mode 100644
index 0000000..57a4205
--- /dev/null
+++ b/example-agent-jsons/agent-memory-settings.json
@@ -0,0 +1,43 @@
+{
+ "userMemoryEnabled": true,
+ "userMemoryFields": [
+ {
+ "id": "default-user-role",
+ "title": "User's Role/Industry",
+ "description": "The professional role or industry the user works in (e.g., Software Engineer, Healthcare)."
+ },
+ {
+ "id": "default-user-interests",
+ "title": "Key Goals/Interests",
+ "description": "User's primary goals for interacting or their main topics of interest (e.g., Learning Python, Project Management Tips)."
+ },
+ {
+ "id": "1747119626018",
+ "title": "Annoying things AI agent should avoid",
+ "description": "Mark down any time the user gets annoyed, or flags annoyance. "
+ }
+ ],
+ "chatMemoryEnabled": true,
+ "chatThreadMemoryFields": [
+ {
+ "id": "default-chat-topic",
+ "title": "Current Discussion Topic",
+ "description": "The main subject or problem being discussed in the current chat session (e.g., Debugging a specific function, Brainstorming ideas for X)."
+ },
+ {
+ "id": "default-chat-summary",
+ "title": "Brief Conversation Summary",
+ "description": "A short summary of the key points or decisions made so far in this thread."
+ },
+ {
+ "id": "1747141795099",
+ "title": "Group annoyance",
+ "description": "Save anything the group finds annoying"
+ }
+ ],
+ "memoryTypeNote": "User memory is persistent across chats. Chat thread memory is scoped to the current conversation.",
+ "fields": [],
+ "title": "Agent Memory Settings",
+ "description": "Configure agent memory for user-level and chat-specific contexts.",
+ "_source": "server_direct_file"
+}
\ No newline at end of file
diff --git a/example-agent-jsons/base-information.json b/example-agent-jsons/base-information.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/example-agent-jsons/base-information.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/example-agent-jsons/group-onboarding-flow.json b/example-agent-jsons/group-onboarding-flow.json
new file mode 100644
index 0000000..683ba9a
--- /dev/null
+++ b/example-agent-jsons/group-onboarding-flow.json
@@ -0,0 +1,34 @@
+{
+ "enabled": true,
+ "mode": "agentic",
+ "agenticSettings": {
+ "systemPrompt": "Your name is Felicie! and you are the A1 Founder Mode assistant. A1 Founder Mode helps founders, do-ers, builders, managers and more engage in founder mode. You're now in a group chat context with multiple participants.\n\nYou are conducting an onboarding conversation in a group chat. Your goal is to introduce yourself to the group, make everyone feel welcome, and gather some basic information about the group and its purpose. Be friendly, spicy, professional, and conversational. Use simple and human language. Ask one question at a time, and acknowledge the group's answers before moving to the next question. Once you've collected all the requested information, summarize what you've learned and let them know you're ready to help with their group's needs.",
+ "initialGroupMessage": "Hello everyone! I'm Felicie - your AI assistant for founder mode. We're going to make it. Let's go:",
+ "userFields": [
+ {
+ "id": "group_purpose",
+ "label": "Group Purpose",
+ "required": true,
+ "description": "Ask what the main purpose/goal of this group chat is",
+ "_internalReactKey": "5f04482b-49aa-4a9f-a8de-03948e5acb78"
+ },
+ {
+ "id": "harshness",
+ "label": "harshness",
+ "required": true,
+ "description": "The AI should ask how harsh and rude the AI should be in managing these projects",
+ "_internalReactKey": "76558da5-df68-484e-b5d2-1e888662f2e3"
+ }
+ ],
+ "finalMessage": "I've got everything I need to start working with you! Start by telling me what first project you want me to start tracking:",
+ "participantThreshold": 4,
+ "autoStartOnboarding": true,
+ "reintroduceAgent": true,
+ "reintroductionInterval": 30
+ },
+ "settings": {
+ "captureGroupPreferences": true,
+ "collectIndividualResponses": false,
+ "requireAllParticipantsRespond": false
+ }
+}
\ No newline at end of file
diff --git a/example-agent-jsons/message-settings.json b/example-agent-jsons/message-settings.json
new file mode 100644
index 0000000..cf689fc
--- /dev/null
+++ b/example-agent-jsons/message-settings.json
@@ -0,0 +1,3 @@
+{
+ "splitParagraphs": false
+}
\ No newline at end of file
diff --git a/example-agent-jsons/model-settings.json b/example-agent-jsons/model-settings.json
new file mode 100644
index 0000000..85ed502
--- /dev/null
+++ b/example-agent-jsons/model-settings.json
@@ -0,0 +1,3 @@
+{
+ "selectedModelProvider": "grok"
+}
\ No newline at end of file
diff --git a/example-agent-jsons/onboarding-flow.json b/example-agent-jsons/onboarding-flow.json
new file mode 100644
index 0000000..38f6ddd
--- /dev/null
+++ b/example-agent-jsons/onboarding-flow.json
@@ -0,0 +1,36 @@
+{
+ "enabled": true,
+ "mode": "agentic",
+ "agenticSettings": {
+ "systemPrompt": "You are Felicie, A1 Founder Mode assistant. A1 Founder Mode helps founders, do-ers, builders, managers and more engage in founder mode. You keep your user on top of the various projects on their table. Your nice, supportive, sharp speaking and like conversations with users",
+ "userFields": [
+ {
+ "id": "name",
+ "label": "Full Name",
+ "required": true,
+ "description": "Ask for the user's full name",
+ "_internalReactKey": "08f1c521-7231-4405-9eb1-2eb258a9591e"
+ },
+ {
+ "id": "email",
+ "label": "Email Address",
+ "required": true,
+ "description": "Ask for the user's email address",
+ "_internalReactKey": "9de6c6f7-d49c-4df9-afe9-e7282aacd973"
+ },
+ {
+ "id": "big_dream",
+ "label": "BigDream",
+ "required": true,
+ "description": "Ask the user for what their biggest dream for their project/startup is",
+ "_internalReactKey": "0cc13807-b784-4e23-b160-1540391e1239"
+ }
+ ],
+ "finalMessage": "Thank you for sharing this information. I've saved your details and I'm ready to help you achieve your goals. What would you like assistance with today?"
+ },
+ "settings": {
+ "captureUserPreferences": true,
+ "askForName": true,
+ "askForBusinessType": true
+ }
+}
\ No newline at end of file
diff --git a/example-agent-jsons/profile-settings.json b/example-agent-jsons/profile-settings.json
new file mode 100644
index 0000000..395f81c
--- /dev/null
+++ b/example-agent-jsons/profile-settings.json
@@ -0,0 +1,31 @@
+{
+ "name": "Felicie",
+ "role": "A1 Founder Mode Assistant",
+ "isPersonified": true,
+ "companyName": "A1 Founder Mode",
+ "companyDescription": "A1 Founder Mode empowers founders, builders, and managers to adopt a hands-on, results-driven approach. Through Felicia, our AI assistant, we help you organize multiple projects and ensure progress with regular check-ins.",
+ "botPurpose": [
+ "Support users in adopting a hands-on, results-driven approach to manage and execute projects effectively.",
+ "Assist in organizing and tracking multiple projects efficiently.",
+ "Provide regular progress checks to help users achieve tangible outcomes."
+ ],
+ "languageStyle": {
+ "language": "English",
+ "tone": [
+ "Clear, sharp, direct, and thoughtful with a motivational edge.",
+ "Respond concisely, as if via SMS or WhatsApp. Keep it brief and impactful."
+ ],
+ "dialect": "American"
+ },
+ "workflowSettings": {
+ "workflow": "Technical Guide"
+ },
+ "agentSettings": {
+ "agent": "Product Guide"
+ },
+ "profileImageUrl": "https://a1base-public.s3.us-east-1.amazonaws.com/profile-moving/20250210_1742_Corporate+Serene+Smile_simple_compose_01jkq9gs6rea3v4n7w461rwye2.gif",
+ "groupChatPreferences": {
+ "respond_only_when_mentioned": true
+ },
+ "_source": "file_storage"
+}
\ No newline at end of file
diff --git a/example-agent-jsons/safety-settings.json b/example-agent-jsons/safety-settings.json
new file mode 100644
index 0000000..3761cc1
--- /dev/null
+++ b/example-agent-jsons/safety-settings.json
@@ -0,0 +1,6 @@
+{
+ "enabled": false,
+ "guidelines": [],
+ "jailbreakWarning": "",
+ "identityStatements": []
+}
\ No newline at end of file
diff --git a/mint.json b/mint.json
index ae8b68e..cac1457 100644
--- a/mint.json
+++ b/mint.json
@@ -108,7 +108,8 @@
"pages": [
"a1mail/create-email",
"a1mail/receiving-email",
- "a1mail/sending-email"
+ "a1mail/sending-email",
+ "a1mail/send-calendar-invites"
]
},
{
diff --git a/sql/create-fresh-db.sql b/sql/create-fresh-db.sql
new file mode 100644
index 0000000..f302345
--- /dev/null
+++ b/sql/create-fresh-db.sql
@@ -0,0 +1,346 @@
+-- Fresh Database Creation Script for A1Zap
+-- This script creates the complete database schema from scratch
+-- Run this on a new PostgreSQL database
+
+-- ============================================
+-- 1. Create extensions
+-- ============================================
+
+CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
+
+-- ============================================
+-- 2. Create enums
+-- ============================================
+
+CREATE TYPE app_user_permission AS ENUM ('read', 'write', 'admin');
+
+-- ============================================
+-- 3. Create core tables
+-- ============================================
+
+-- Subscription plans table (no dependencies)
+CREATE TABLE subscription_plans (
+ id TEXT PRIMARY KEY,
+ name TEXT NOT NULL,
+ description TEXT,
+ price_monthly DECIMAL(10, 2),
+ price_yearly DECIMAL(10, 2),
+ currency TEXT DEFAULT 'USD',
+ max_personal_agents INTEGER NOT NULL,
+ max_work_agents INTEGER NOT NULL,
+ max_messages_per_month INTEGER,
+ max_storage_gb INTEGER,
+ features JSONB DEFAULT '[]',
+ is_active BOOLEAN DEFAULT true,
+ created_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMP NOT NULL DEFAULT NOW()
+);
+
+-- User subscriptions table
+CREATE TABLE user_subscriptions (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ user_id TEXT UNIQUE NOT NULL, -- Stack Auth user ID
+ plan_id TEXT NOT NULL DEFAULT 'free' REFERENCES subscription_plans(id),
+ status TEXT NOT NULL DEFAULT 'active',
+ max_personal_agents INTEGER DEFAULT 1,
+ max_work_agents INTEGER DEFAULT 1,
+ max_total_agents INTEGER DEFAULT 2,
+ stripe_customer_id TEXT,
+ stripe_subscription_id TEXT,
+ current_period_start TIMESTAMP,
+ current_period_end TIMESTAMP,
+ created_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ cancelled_at TIMESTAMP,
+ CONSTRAINT valid_plan CHECK (plan_id IN ('free', 'pro', 'enterprise')),
+ CONSTRAINT valid_status CHECK (status IN ('active', 'cancelled', 'past_due', 'trialing'))
+);
+
+-- Apps table (server instances)
+CREATE TABLE apps (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ name TEXT NOT NULL DEFAULT 'Unnamed App',
+ description TEXT NOT NULL DEFAULT 'No description',
+ git_repo TEXT NOT NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ base_id TEXT NOT NULL DEFAULT 'nextjs-dkjfgdf',
+ preview_domain TEXT UNIQUE,
+ agent_id UUID UNIQUE -- Will be set after a1_agents is created
+);
+
+-- A1 Agents table
+CREATE TABLE a1_agents (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ owner_id TEXT NOT NULL, -- Stack Auth user ID
+ app_id UUID UNIQUE NOT NULL REFERENCES apps(id) ON DELETE CASCADE,
+ name TEXT NOT NULL,
+ handle TEXT UNIQUE NOT NULL,
+ description TEXT,
+ avatar_url TEXT,
+ email TEXT,
+ phone_number TEXT,
+ whatsapp_number TEXT,
+ base_website TEXT,
+ custom_website TEXT,
+ onboarding_data JSONB,
+ settings JSONB DEFAULT '{}',
+ status TEXT DEFAULT 'active',
+ agent_type TEXT NOT NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ CONSTRAINT valid_agent_type CHECK (agent_type IN ('personal', 'work')),
+ CONSTRAINT valid_status CHECK (status IN ('active', 'paused', 'archived')),
+ CONSTRAINT valid_handle CHECK (handle ~ '^[a-zA-Z0-9_]+$')
+);
+
+-- Add foreign key constraint back to apps
+ALTER TABLE apps ADD CONSTRAINT apps_agent_id_fkey
+ FOREIGN KEY (agent_id) REFERENCES a1_agents(id) ON DELETE SET NULL;
+
+-- App users table (for sharing apps)
+CREATE TABLE app_users (
+ user_id TEXT NOT NULL, -- Stack Auth user ID
+ app_id UUID NOT NULL REFERENCES apps(id) ON DELETE CASCADE,
+ created_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ permissions app_user_permission,
+ freestyle_identity TEXT NOT NULL,
+ freestyle_access_token TEXT NOT NULL,
+ freestyle_access_token_id TEXT NOT NULL,
+ PRIMARY KEY (user_id, app_id)
+);
+
+-- Agent conversations table
+CREATE TABLE agent_conversations (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ agent_id UUID NOT NULL REFERENCES a1_agents(id) ON DELETE CASCADE,
+ external_user_id TEXT,
+ channel TEXT NOT NULL,
+ thread_id TEXT,
+ status TEXT DEFAULT 'active',
+ started_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ last_message_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ ended_at TIMESTAMP,
+ metadata JSONB DEFAULT '{}',
+ CONSTRAINT valid_status CHECK (status IN ('active', 'archived'))
+);
+
+-- Messages table
+CREATE TABLE messages (
+ id TEXT PRIMARY KEY,
+ created_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ app_id UUID NOT NULL REFERENCES apps(id) ON DELETE CASCADE,
+ message JSONB NOT NULL,
+ agent_id UUID REFERENCES a1_agents(id),
+ conversation_id UUID REFERENCES agent_conversations(id)
+);
+
+-- App deployments table
+CREATE TABLE app_deployments (
+ app_id UUID NOT NULL REFERENCES apps(id) ON DELETE CASCADE,
+ created_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ deployment_id TEXT NOT NULL,
+ commit TEXT NOT NULL,
+ PRIMARY KEY (app_id, deployment_id)
+);
+
+-- Agent usage table
+CREATE TABLE agent_usage (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ agent_id UUID NOT NULL REFERENCES a1_agents(id) ON DELETE CASCADE,
+ date DATE NOT NULL,
+ message_count INTEGER DEFAULT 0,
+ api_calls INTEGER DEFAULT 0,
+ storage_bytes BIGINT DEFAULT 0,
+ compute_seconds INTEGER DEFAULT 0,
+ estimated_cost DECIMAL(10, 4) DEFAULT 0,
+ created_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ CONSTRAINT unique_agent_date UNIQUE (agent_id, date)
+);
+
+-- Billing events table
+CREATE TABLE billing_events (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ user_id TEXT NOT NULL, -- Stack Auth user ID
+ event_type TEXT NOT NULL,
+ amount DECIMAL(10, 2),
+ currency TEXT DEFAULT 'USD',
+ description TEXT,
+ subscription_id UUID REFERENCES user_subscriptions(id),
+ invoice_id TEXT,
+ metadata JSONB DEFAULT '{}',
+ created_at TIMESTAMP NOT NULL DEFAULT NOW()
+);
+
+-- Agent integrations table
+CREATE TABLE agent_integrations (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ agent_id UUID NOT NULL REFERENCES a1_agents(id) ON DELETE CASCADE,
+ provider TEXT NOT NULL,
+ status TEXT DEFAULT 'active',
+ config JSONB NOT NULL DEFAULT '{}',
+ created_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ last_sync_at TIMESTAMP,
+ CONSTRAINT unique_agent_provider UNIQUE (agent_id, provider)
+);
+
+-- ============================================
+-- 4. Create indexes
+-- ============================================
+
+-- A1 Agents indexes
+CREATE INDEX idx_a1_agents_owner_id ON a1_agents(owner_id);
+CREATE INDEX idx_a1_agents_handle ON a1_agents(handle);
+CREATE INDEX idx_a1_agents_status ON a1_agents(status);
+
+-- App users indexes
+CREATE INDEX idx_app_users_user_id ON app_users(user_id);
+CREATE INDEX idx_app_users_app_id ON app_users(app_id);
+
+-- Agent conversations indexes
+CREATE INDEX idx_agent_conversations_agent_id ON agent_conversations(agent_id);
+CREATE INDEX idx_agent_conversations_external_user ON agent_conversations(external_user_id);
+
+-- Messages indexes
+CREATE INDEX idx_messages_app_id ON messages(app_id);
+CREATE INDEX idx_messages_agent_id ON messages(agent_id);
+CREATE INDEX idx_messages_conversation_id ON messages(conversation_id);
+CREATE INDEX idx_messages_created_at ON messages(created_at DESC);
+
+-- Agent usage indexes
+CREATE INDEX idx_agent_usage_agent_id_date ON agent_usage(agent_id, date);
+
+-- Billing events indexes
+CREATE INDEX idx_billing_events_user_id ON billing_events(user_id);
+CREATE INDEX idx_billing_events_type ON billing_events(event_type);
+CREATE INDEX idx_billing_events_created_at ON billing_events(created_at DESC);
+
+-- User subscriptions indexes
+CREATE INDEX idx_user_subscriptions_user_id ON user_subscriptions(user_id);
+CREATE INDEX idx_user_subscriptions_status ON user_subscriptions(status);
+
+-- ============================================
+-- 5. Create triggers
+-- ============================================
+
+-- Function to update updated_at timestamp
+CREATE OR REPLACE FUNCTION update_updated_at_column()
+RETURNS TRIGGER AS $$
+BEGIN
+ NEW.updated_at = NOW();
+ RETURN NEW;
+END;
+$$ language 'plpgsql';
+
+-- Apply updated_at triggers
+CREATE TRIGGER update_a1_agents_updated_at
+ BEFORE UPDATE ON a1_agents
+ FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+
+CREATE TRIGGER update_subscription_plans_updated_at
+ BEFORE UPDATE ON subscription_plans
+ FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+
+CREATE TRIGGER update_user_subscriptions_updated_at
+ BEFORE UPDATE ON user_subscriptions
+ FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+
+CREATE TRIGGER update_agent_integrations_updated_at
+ BEFORE UPDATE ON agent_integrations
+ FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+
+-- ============================================
+-- 6. Insert default data
+-- ============================================
+
+-- Insert default subscription plans
+INSERT INTO subscription_plans (id, name, description, max_personal_agents, max_work_agents, price_monthly, price_yearly, max_messages_per_month, max_storage_gb) VALUES
+('free', 'Free', 'Perfect for trying out A1Zap', 1, 1, 0, 0, 1000, 1),
+('pro', 'Pro', 'For professionals and small teams', 5, 5, 29.99, 299.99, 10000, 10),
+('enterprise', 'Enterprise', 'Unlimited agents and custom limits', -1, -1, 99.99, 999.99, -1, -1);
+
+-- ============================================
+-- 7. Create views for common queries
+-- ============================================
+
+-- View for user's agents with app info
+CREATE VIEW user_agents_view AS
+SELECT
+ a.id,
+ a.owner_id,
+ a.name,
+ a.handle,
+ a.description,
+ a.status,
+ a.agent_type,
+ a.created_at,
+ app.id as app_id,
+ app.git_repo,
+ app.preview_domain,
+ COALESCE(
+ (SELECT SUM(message_count)
+ FROM agent_usage
+ WHERE agent_id = a.id
+ AND date >= date_trunc('month', CURRENT_DATE)
+ ), 0
+ ) as monthly_message_count
+FROM a1_agents a
+JOIN apps app ON a.app_id = app.id;
+
+-- View for billing summary
+CREATE VIEW user_billing_summary AS
+SELECT
+ us.user_id,
+ us.plan_id,
+ sp.name as plan_name,
+ us.status as subscription_status,
+ us.current_period_end,
+ sp.price_monthly,
+ sp.max_personal_agents,
+ sp.max_work_agents,
+ (SELECT COUNT(*) FROM a1_agents WHERE owner_id = us.user_id AND agent_type = 'personal') as current_personal_agents,
+ (SELECT COUNT(*) FROM a1_agents WHERE owner_id = us.user_id AND agent_type = 'work') as current_work_agents
+FROM user_subscriptions us
+JOIN subscription_plans sp ON us.plan_id = sp.id;
+
+-- ============================================
+-- 8. Row Level Security (Optional but recommended)
+-- ============================================
+
+-- Enable RLS on sensitive tables
+ALTER TABLE a1_agents ENABLE ROW LEVEL SECURITY;
+ALTER TABLE agent_usage ENABLE ROW LEVEL SECURITY;
+ALTER TABLE agent_conversations ENABLE ROW LEVEL SECURITY;
+ALTER TABLE agent_integrations ENABLE ROW LEVEL SECURITY;
+
+-- Create policies (example for a1_agents)
+-- Note: These would need to be adjusted based on your auth implementation
+CREATE POLICY "Users can view their own agents" ON a1_agents
+ FOR SELECT USING (owner_id = current_setting('app.current_user_id', true));
+
+CREATE POLICY "Users can insert their own agents" ON a1_agents
+ FOR INSERT WITH CHECK (owner_id = current_setting('app.current_user_id', true));
+
+CREATE POLICY "Users can update their own agents" ON a1_agents
+ FOR UPDATE USING (owner_id = current_setting('app.current_user_id', true));
+
+CREATE POLICY "Users can delete their own agents" ON a1_agents
+ FOR DELETE USING (owner_id = current_setting('app.current_user_id', true));
+
+-- ============================================
+-- 9. Performance optimizations
+-- ============================================
+
+-- Partial index for active agents
+CREATE INDEX idx_a1_agents_active ON a1_agents(owner_id) WHERE status = 'active';
+
+-- Partial index for active conversations
+CREATE INDEX idx_agent_conversations_active ON agent_conversations(agent_id) WHERE status = 'active';
+
+-- BRIN index for time-series data
+CREATE INDEX idx_agent_usage_created_at_brin ON agent_usage USING brin(created_at);
+CREATE INDEX idx_messages_created_at_brin ON messages USING brin(created_at);
+
+-- ============================================
+-- Database setup complete!
+-- ============================================
\ No newline at end of file
diff --git a/sql/migrate-existing-db.sql b/sql/migrate-existing-db.sql
new file mode 100644
index 0000000..9081ce6
--- /dev/null
+++ b/sql/migrate-existing-db.sql
@@ -0,0 +1,319 @@
+-- Migration Script for Existing Database
+-- This script adds the new a1zap tables to the existing database
+-- Run this in a transaction to ensure atomicity
+
+BEGIN;
+
+-- ============================================
+-- 1. Create new tables
+-- ============================================
+
+-- Create enum for app permissions
+CREATE TYPE app_user_permission AS ENUM ('read', 'write', 'admin');
+
+-- Create subscription plans table
+CREATE TABLE subscription_plans (
+ id TEXT PRIMARY KEY,
+ name TEXT NOT NULL,
+ description TEXT,
+ price_monthly DECIMAL(10, 2),
+ price_yearly DECIMAL(10, 2),
+ currency TEXT DEFAULT 'USD',
+ max_personal_agents INTEGER NOT NULL,
+ max_work_agents INTEGER NOT NULL,
+ max_messages_per_month INTEGER,
+ max_storage_gb INTEGER,
+ features JSONB DEFAULT '[]',
+ is_active BOOLEAN DEFAULT true,
+ created_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMP NOT NULL DEFAULT NOW()
+);
+
+-- Insert default subscription plans
+INSERT INTO subscription_plans (id, name, max_personal_agents, max_work_agents, price_monthly, max_messages_per_month) VALUES
+('free', 'Free', 1, 1, 0, 1000),
+('pro', 'Pro', 5, 5, 29.99, 10000),
+('enterprise', 'Enterprise', -1, -1, 99.99, -1); -- -1 means unlimited
+
+-- Create user subscriptions table
+CREATE TABLE user_subscriptions (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ user_id TEXT UNIQUE NOT NULL,
+ plan_id TEXT NOT NULL DEFAULT 'free' REFERENCES subscription_plans(id),
+ status TEXT NOT NULL DEFAULT 'active',
+ max_personal_agents INTEGER DEFAULT 1,
+ max_work_agents INTEGER DEFAULT 1,
+ max_total_agents INTEGER DEFAULT 2,
+ stripe_customer_id TEXT,
+ stripe_subscription_id TEXT,
+ current_period_start TIMESTAMP,
+ current_period_end TIMESTAMP,
+ created_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ cancelled_at TIMESTAMP,
+ CONSTRAINT valid_plan CHECK (plan_id IN ('free', 'pro', 'enterprise')),
+ CONSTRAINT valid_status CHECK (status IN ('active', 'cancelled', 'past_due', 'trialing'))
+);
+
+-- Create a1_agents table
+CREATE TABLE a1_agents (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ owner_id TEXT NOT NULL,
+ app_id TEXT UNIQUE, -- Will be updated to UUID after apps table migration
+ name TEXT NOT NULL,
+ handle TEXT UNIQUE NOT NULL,
+ description TEXT,
+ avatar_url TEXT,
+ email TEXT,
+ phone_number TEXT,
+ whatsapp_number TEXT,
+ base_website TEXT,
+ custom_website TEXT,
+ onboarding_data JSONB,
+ settings JSONB DEFAULT '{}',
+ status TEXT DEFAULT 'active',
+ agent_type TEXT NOT NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ CONSTRAINT valid_agent_type CHECK (agent_type IN ('personal', 'work')),
+ CONSTRAINT valid_status CHECK (status IN ('active', 'paused', 'archived')),
+ CONSTRAINT valid_handle CHECK (handle ~ '^[a-zA-Z0-9_]+$')
+);
+
+-- Create indexes for a1_agents
+CREATE INDEX idx_a1_agents_owner_id ON a1_agents(owner_id);
+CREATE INDEX idx_a1_agents_handle ON a1_agents(handle);
+CREATE INDEX idx_a1_agents_status ON a1_agents(status);
+
+-- Create agent usage table
+CREATE TABLE agent_usage (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ agent_id UUID NOT NULL REFERENCES a1_agents(id) ON DELETE CASCADE,
+ date DATE NOT NULL,
+ message_count INTEGER DEFAULT 0,
+ api_calls INTEGER DEFAULT 0,
+ storage_bytes BIGINT DEFAULT 0,
+ compute_seconds INTEGER DEFAULT 0,
+ estimated_cost DECIMAL(10, 4) DEFAULT 0,
+ created_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ CONSTRAINT unique_agent_date UNIQUE (agent_id, date)
+);
+
+-- Create index for agent usage
+CREATE INDEX idx_agent_usage_agent_id_date ON agent_usage(agent_id, date);
+
+-- Create billing events table
+CREATE TABLE billing_events (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ user_id TEXT NOT NULL,
+ event_type TEXT NOT NULL,
+ amount DECIMAL(10, 2),
+ currency TEXT DEFAULT 'USD',
+ description TEXT,
+ subscription_id UUID REFERENCES user_subscriptions(id),
+ invoice_id TEXT,
+ metadata JSONB DEFAULT '{}',
+ created_at TIMESTAMP NOT NULL DEFAULT NOW()
+);
+
+-- Create indexes for billing events
+CREATE INDEX idx_billing_events_user_id ON billing_events(user_id);
+CREATE INDEX idx_billing_events_type ON billing_events(event_type);
+
+-- Create agent conversations table
+CREATE TABLE agent_conversations (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ agent_id UUID NOT NULL REFERENCES a1_agents(id) ON DELETE CASCADE,
+ external_user_id TEXT,
+ channel TEXT NOT NULL,
+ thread_id TEXT,
+ status TEXT DEFAULT 'active',
+ started_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ last_message_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ ended_at TIMESTAMP,
+ metadata JSONB DEFAULT '{}',
+ CONSTRAINT valid_status CHECK (status IN ('active', 'archived'))
+);
+
+-- Create indexes for agent conversations
+CREATE INDEX idx_agent_conversations_agent_id ON agent_conversations(agent_id);
+CREATE INDEX idx_agent_conversations_external_user ON agent_conversations(external_user_id);
+
+-- Create agent integrations table
+CREATE TABLE agent_integrations (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ agent_id UUID NOT NULL REFERENCES a1_agents(id) ON DELETE CASCADE,
+ provider TEXT NOT NULL,
+ status TEXT DEFAULT 'active',
+ config JSONB NOT NULL DEFAULT '{}',
+ created_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ last_sync_at TIMESTAMP,
+ CONSTRAINT unique_agent_provider UNIQUE (agent_id, provider)
+);
+
+-- ============================================
+-- 2. Migrate existing tables
+-- ============================================
+
+-- First, we need to check if the newer schema tables exist
+-- If they do, we'll migrate them. If not, we'll work with the legacy tables.
+
+DO $$
+BEGIN
+ -- Check if the newer schema exists (with UUID ids)
+ IF EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'apps'
+ AND column_name = 'id'
+ AND data_type = 'uuid'
+ ) THEN
+ -- Newer schema exists, add agent_id column
+ ALTER TABLE apps ADD COLUMN IF NOT EXISTS agent_id UUID UNIQUE REFERENCES a1_agents(id);
+
+ -- Add columns to messages table if it exists
+ IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'messages') THEN
+ ALTER TABLE messages
+ ADD COLUMN IF NOT EXISTS agent_id UUID REFERENCES a1_agents(id),
+ ADD COLUMN IF NOT EXISTS conversation_id UUID REFERENCES agent_conversations(id);
+ END IF;
+ ELSE
+ -- Legacy schema with text IDs exists
+ -- Create a temporary mapping table for migration
+ CREATE TEMP TABLE app_id_mapping (
+ old_id TEXT PRIMARY KEY,
+ new_id UUID DEFAULT gen_random_uuid()
+ );
+
+ -- Insert mappings for all existing apps
+ INSERT INTO app_id_mapping (old_id)
+ SELECT id FROM apps;
+
+ -- Create new apps table with proper schema
+ CREATE TABLE apps_new (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ name TEXT NOT NULL DEFAULT 'Unnamed App',
+ description TEXT NOT NULL DEFAULT 'No description',
+ git_repo TEXT NOT NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ base_id TEXT NOT NULL DEFAULT 'nextjs-dkjfgdf',
+ preview_domain TEXT UNIQUE,
+ agent_id UUID UNIQUE
+ );
+
+ -- Migrate data from old apps table
+ INSERT INTO apps_new (id, name, description, git_repo, created_at, preview_domain)
+ SELECT
+ m.new_id,
+ COALESCE(a.name, 'Unnamed App'),
+ COALESCE(a.description, 'No description'),
+ COALESCE(a.github_url, ''),
+ COALESCE(a.created_at, NOW()),
+ a.preview_url
+ FROM apps a
+ JOIN app_id_mapping m ON a.id = m.old_id;
+
+ -- Create app_users table
+ CREATE TABLE app_users (
+ user_id TEXT NOT NULL,
+ app_id UUID NOT NULL REFERENCES apps_new(id) ON DELETE CASCADE,
+ created_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ permissions app_user_permission,
+ freestyle_identity TEXT NOT NULL DEFAULT '',
+ freestyle_access_token TEXT NOT NULL DEFAULT '',
+ freestyle_access_token_id TEXT NOT NULL DEFAULT ''
+ );
+
+ -- Create messages table if using chat_messages
+ IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'chat_messages') THEN
+ CREATE TABLE messages (
+ id TEXT PRIMARY KEY,
+ created_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ app_id UUID NOT NULL REFERENCES apps_new(id),
+ message JSONB NOT NULL,
+ agent_id UUID REFERENCES a1_agents(id),
+ conversation_id UUID REFERENCES agent_conversations(id)
+ );
+
+ -- Migrate chat messages
+ INSERT INTO messages (id, created_at, app_id, message)
+ SELECT
+ cm.id,
+ cm.created_at,
+ m.new_id,
+ jsonb_build_object(
+ 'role', cm.role,
+ 'content', cm.content
+ )
+ FROM chat_messages cm
+ JOIN app_id_mapping m ON cm.app_id = m.old_id;
+ END IF;
+
+ -- Create app_deployments table
+ CREATE TABLE app_deployments (
+ app_id UUID NOT NULL REFERENCES apps_new(id) ON DELETE CASCADE,
+ created_at TIMESTAMP NOT NULL DEFAULT NOW(),
+ deployment_id TEXT NOT NULL,
+ commit TEXT NOT NULL
+ );
+
+ -- Drop old tables and rename new ones
+ DROP TABLE IF EXISTS files CASCADE;
+ DROP TABLE IF EXISTS chat_messages CASCADE;
+ DROP TABLE apps CASCADE;
+ ALTER TABLE apps_new RENAME TO apps;
+
+ -- Update a1_agents app_id to UUID
+ ALTER TABLE a1_agents DROP COLUMN app_id;
+ ALTER TABLE a1_agents ADD COLUMN app_id UUID UNIQUE REFERENCES apps(id) ON DELETE CASCADE;
+ END IF;
+END $$;
+
+-- ============================================
+-- 3. Create triggers for updated_at
+-- ============================================
+
+CREATE OR REPLACE FUNCTION update_updated_at_column()
+RETURNS TRIGGER AS $$
+BEGIN
+ NEW.updated_at = NOW();
+ RETURN NEW;
+END;
+$$ language 'plpgsql';
+
+CREATE TRIGGER update_a1_agents_updated_at BEFORE UPDATE ON a1_agents
+ FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+
+CREATE TRIGGER update_subscription_plans_updated_at BEFORE UPDATE ON subscription_plans
+ FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+
+CREATE TRIGGER update_user_subscriptions_updated_at BEFORE UPDATE ON user_subscriptions
+ FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+
+CREATE TRIGGER update_agent_integrations_updated_at BEFORE UPDATE ON agent_integrations
+ FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+
+-- ============================================
+-- 4. Create initial subscriptions for existing users
+-- ============================================
+
+-- If app_users table exists, create free subscriptions for all existing users
+DO $$
+BEGIN
+ IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'app_users') THEN
+ INSERT INTO user_subscriptions (user_id, plan_id)
+ SELECT DISTINCT user_id, 'free'
+ FROM app_users
+ ON CONFLICT (user_id) DO NOTHING;
+ END IF;
+END $$;
+
+COMMIT;
+
+-- ============================================
+-- Post-migration notes:
+-- ============================================
+-- 1. Update your application code to use the new schema
+-- 2. Test all functionality thoroughly
+-- 3. Consider adding data encryption for sensitive fields
+-- 4. Set up regular backups before running in production
\ No newline at end of file
diff --git a/stack-auth-integration.md b/stack-auth-integration.md
new file mode 100644
index 0000000..d185ccc
--- /dev/null
+++ b/stack-auth-integration.md
@@ -0,0 +1,294 @@
+# Stack Auth Integration Guide
+
+## Overview
+This document explains how Stack Auth is integrated into a1zap and how it connects to our internal user system. Stack Auth handles authentication while our database maintains user-related data and relationships.
+
+## Architecture Overview
+
+```
+βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
+β Stack Auth ββββββΆβ a1zap App ββββββΆβ PostgreSQL β
+β (Auth Layer) β β (Business Logic) β β (Data Layer) β
+βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
+```
+
+## How Stack Auth Works
+
+### 1. Authentication Provider
+Stack Auth is a third-party authentication service that:
+- Manages user registration and login
+- Handles password resets and email verification
+- Provides OAuth integrations (Google, GitHub, etc.)
+- Stores basic user profile data
+- Issues JWT tokens for session management
+
+### 2. User Identification
+When a user authenticates via Stack Auth:
+- Stack Auth assigns a unique `user.id` (string)
+- This ID becomes our primary user identifier
+- We use this ID to link all user data in our database
+
+## Integration Points
+
+### 1. Stack Auth Configuration (`src/auth/stack-auth.ts`)
+
+```typescript
+import { StackServerApp } from "@stackframe/stack";
+
+export const stackServerApp = new StackServerApp({
+ tokenStore: "nextjs-cookie", // Stores auth tokens in cookies
+});
+
+export async function getUser() {
+ const user = await stackServerApp.getUser();
+
+ if (!user) {
+ throw new Error("User not found");
+ }
+
+ // Ensure user has a Freestyle identity for Git operations
+ if (!user?.serverMetadata?.freestyleIdentity) {
+ const gitIdentity = await freestyle.createGitIdentity();
+
+ await user.update({
+ serverMetadata: {
+ freestyleIdentity: gitIdentity.id,
+ },
+ });
+ }
+
+ return {
+ userId: user.id, // Stack Auth user ID
+ freestyleIdentity: user.serverMetadata.freestyleIdentity,
+ };
+}
+```
+
+### 2. Environment Variables Required
+
+```env
+NEXT_PUBLIC_STACK_PROJECT_ID=
+NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=
+STACK_SECRET_SERVER_KEY=
+```
+
+## Database Relationships
+
+### User References in Our Database
+
+Stack Auth users are referenced throughout our database using their Stack Auth ID:
+
+1. **`app_users` table**
+ ```sql
+ userId: text("user_id").notNull() -- Stack Auth user ID
+ ```
+
+2. **`a1_agents` table**
+ ```sql
+ owner_id: TEXT NOT NULL -- Stack Auth user ID
+ ```
+
+3. **`user_subscriptions` table**
+ ```sql
+ user_id: TEXT UNIQUE NOT NULL -- Stack Auth user ID
+ ```
+
+4. **`billing_events` table**
+ ```sql
+ user_id: TEXT NOT NULL -- Stack Auth user ID
+ ```
+
+### Data Flow Example
+
+When a user creates a new AI agent:
+
+```typescript
+// 1. Get authenticated user from Stack Auth
+const user = await getUser();
+// Returns: { userId: "stack_auth_id_123", freestyleIdentity: "git_id_456" }
+
+// 2. Create agent in our database
+const agent = await db.insert(a1_agents).values({
+ owner_id: user.userId, // Link to Stack Auth user
+ name: "My AI Agent",
+ // ... other fields
+});
+
+// 3. Create subscription record (if first time)
+await db.insert(user_subscriptions).values({
+ user_id: user.userId, // Same Stack Auth ID
+ plan_id: "free",
+ // ... other fields
+});
+```
+
+## Authentication Flow
+
+### 1. User Registration/Login
+```
+User β Stack Auth Sign-in Page β Stack Auth Backend β JWT Token β a1zap App
+```
+
+### 2. Authenticated Requests
+```
+User Request β Next.js Middleware β Verify Stack Auth Token β Load User β Process Request
+```
+
+### 3. Protected Routes
+All authenticated routes use Stack Auth's built-in protection:
+
+```typescript
+// In components
+const user = useUser({ or: "redirect" }); // Redirects to login if not authenticated
+
+// In server actions
+const user = await getUser(); // Throws error if not authenticated
+```
+
+## Key Integration Files
+
+1. **`src/auth/stack-auth.ts`**
+ - Stack Auth configuration
+ - User retrieval helper
+ - Freestyle identity management
+
+2. **`src/app/handler/[...stack]/page.tsx`**
+ - Stack Auth UI handler
+ - Manages login/signup/password reset pages
+
+3. **`src/app/layout.tsx`**
+ - Wraps app with StackProvider
+ - Enables Stack Auth throughout the app
+
+4. **Server Actions** (e.g., `src/actions/create-app.ts`)
+ - All use `getUser()` to get authenticated user
+ - Link created resources to Stack Auth user ID
+
+## User Metadata
+
+Stack Auth allows storing custom metadata on users:
+
+### Server Metadata (Private)
+- Only accessible server-side
+- Used for storing `freestyleIdentity`
+- Can store any JSON data
+
+### Client Metadata (Public)
+- Accessible in the browser
+- Not currently used in a1zap
+
+Example of updating metadata:
+```typescript
+await user.update({
+ serverMetadata: {
+ freestyleIdentity: gitIdentity.id,
+ customField: "value"
+ }
+});
+```
+
+## Security Considerations
+
+1. **Token Storage**
+ - Auth tokens stored in httpOnly cookies
+ - Prevents XSS attacks
+ - Automatically sent with requests
+
+2. **User Isolation**
+ - All queries filter by Stack Auth user ID
+ - Users can only access their own data
+ - Example:
+ ```typescript
+ const userApps = await db
+ .select()
+ .from(appUsers)
+ .where(eq(appUsers.userId, user.userId));
+ ```
+
+3. **Permission Checks**
+ - Every action verifies user ownership
+ - Additional permission levels (read/write/admin) for shared resources
+
+## Benefits of This Architecture
+
+1. **Separation of Concerns**
+ - Stack Auth handles authentication complexity
+ - a1zap focuses on business logic
+ - Clear boundary between auth and app data
+
+2. **Scalability**
+ - No need to manage password hashing
+ - Built-in OAuth providers
+ - Professional auth UI components
+
+3. **Security**
+ - Battle-tested authentication
+ - Regular security updates from Stack Auth
+ - Reduced attack surface
+
+4. **Developer Experience**
+ - Simple API for checking authentication
+ - Built-in React hooks
+ - TypeScript support
+
+## Common Patterns
+
+### Checking Authentication Status
+```typescript
+// In React components
+const user = useUser();
+if (user) {
+ // User is logged in
+}
+
+// In server components/actions
+try {
+ const user = await getUser();
+ // User is authenticated
+} catch {
+ // User is not authenticated
+}
+```
+
+### Creating User-Owned Resources
+```typescript
+const user = await getUser();
+await db.insert(tableName).values({
+ ...data,
+ owner_id: user.userId, // Always link to Stack Auth ID
+});
+```
+
+### Querying User Data
+```typescript
+const user = await getUser();
+const userData = await db
+ .select()
+ .from(tableName)
+ .where(eq(tableName.owner_id, user.userId));
+```
+
+## Migration Considerations
+
+If migrating from another auth system:
+1. Create Stack Auth accounts for existing users
+2. Map old user IDs to Stack Auth IDs
+3. Update all foreign key references
+4. Migrate any custom user data to Stack Auth metadata
+
+## Troubleshooting
+
+### Common Issues
+
+1. **"User not found" errors**
+ - Ensure user is logged in
+ - Check Stack Auth configuration
+ - Verify environment variables
+
+2. **Permission denied**
+ - Verify user owns the resource
+ - Check permission levels in app_users table
+
+3. **Lost Freestyle identity**
+ - Automatically recreated on next access
+ - Old Git permissions may need manual cleanup
\ No newline at end of file
diff --git a/stack-auth-setup.md b/stack-auth-setup.md
new file mode 100644
index 0000000..c5572e5
--- /dev/null
+++ b/stack-auth-setup.md
@@ -0,0 +1,106 @@
+# Stack Auth Setup Guide for a1zap
+
+## Prerequisites
+
+1. Stack Auth account and project created at https://app.stack-auth.com
+2. Google Cloud Console project for OAuth
+
+## Environment Variables
+
+Add these to your `.env.local` file:
+
+```env
+NEXT_PUBLIC_STACK_PROJECT_ID=
+NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=
+STACK_SECRET_SERVER_KEY=
+```
+
+## Stack Auth Dashboard Configuration
+
+### 1. Enable OAuth Providers
+
+1. Go to your Stack Auth dashboard
+2. Navigate to **Authentication** β **OAuth Providers**
+3. Enable **Google** provider
+4. Add your Google OAuth credentials:
+ - Client ID (from Google Cloud Console)
+ - Client Secret (from Google Cloud Console)
+
+### 2. Configure Redirect URLs
+
+1. In Stack Auth dashboard, go to **Settings** β **URLs**
+2. Add the following redirect URLs:
+ - `http://localhost:3001/handler/oauth-callback` (for local development)
+ - `https://your-production-domain.com/handler/oauth-callback` (for production)
+ - Any preview/staging URLs you need
+
+### 3. Configure Domains
+
+1. Go to **Configuration** β **Domains**
+2. Enable **"Allow all localhost callbacks for development"**
+3. Add your production domain(s)
+
+## Google Cloud Console Configuration
+
+### 1. Create OAuth 2.0 Credentials
+
+1. Go to [Google Cloud Console](https://console.cloud.google.com)
+2. Navigate to **APIs & Services** β **Credentials**
+3. Click **Create Credentials** β **OAuth client ID**
+4. Choose **Web application**
+
+### 2. Configure OAuth Client
+
+Add the following to your OAuth client:
+
+**Authorized JavaScript origins:**
+- `http://localhost:3001`
+- `https://your-production-domain.com`
+
+**Authorized redirect URIs:**
+- `http://localhost:3001/handler/oauth-callback`
+- `https://your-production-domain.com/handler/oauth-callback`
+
+### 3. Copy Credentials
+
+Copy the **Client ID** and **Client Secret** to use in Stack Auth dashboard.
+
+## Common Issues
+
+### 1. OAuth Callback 404 Error
+
+If you get a 404 on `/handler/oauth-callback`, ensure:
+- The `[...stack]` catch-all route exists at `src/app/handler/[...stack]/page.tsx`
+- Stack Auth is properly initialized in your app
+
+### 2. ServerMetadata Null Error
+
+This happens when a new user signs up. The fix is already implemented in `src/auth/stack-auth.ts` which:
+1. Checks if `serverMetadata` exists
+2. Creates a Freestyle identity if it doesn't
+3. Refetches the user to get the updated metadata
+
+### 3. Redirect Loop
+
+If you experience redirect loops:
+- Check that your redirect URLs match exactly in both Stack Auth and Google Console
+- Ensure cookies are not being blocked
+- Verify environment variables are set correctly
+
+## Testing OAuth Flow
+
+1. Start your development server: `npm run dev`
+2. Navigate to `http://localhost:3001`
+3. Click through the onboarding flow
+4. When redirected to sign in, choose "Sign in with Google"
+5. Complete Google authentication
+6. You should be redirected back to your app
+
+## Production Deployment
+
+Before deploying to production:
+
+1. Update all redirect URLs to use your production domain
+2. Set environment variables in your hosting provider (Vercel, etc.)
+3. Ensure your production domain is added to both Stack Auth and Google Console
+4. Test the OAuth flow in production
\ No newline at end of file
From b79ccc39460c6ed140294326b976c885653d0f7c Mon Sep 17 00:00:00 2001
From: Pennie
Date: Sat, 20 Sep 2025 04:03:03 -0700
Subject: [PATCH 39/40] docs
---
a1zap/index.mdx | 16 ++++++++++++++
a1zap/messaging-api.mdx | 48 ++++++++++++++++++++++++++++++++++++++++
api-reference/.DS_Store | Bin 0 -> 8196 bytes
mint.json | 18 +++++++++++++++
4 files changed, 82 insertions(+)
create mode 100644 a1zap/index.mdx
create mode 100644 a1zap/messaging-api.mdx
create mode 100644 api-reference/.DS_Store
diff --git a/a1zap/index.mdx b/a1zap/index.mdx
new file mode 100644
index 0000000..03dab94
--- /dev/null
+++ b/a1zap/index.mdx
@@ -0,0 +1,16 @@
+---
+title: "A1Zap"
+description: "Workflows and automations for A1Base"
+---
+
+# A1Zap
+
+Welcome to A1Zap. This section will cover how you can start getting your agent chatting on the A1Zap app.
+
+
+
+ Get started
+
+
+
+
diff --git a/a1zap/messaging-api.mdx b/a1zap/messaging-api.mdx
new file mode 100644
index 0000000..e8030d2
--- /dev/null
+++ b/a1zap/messaging-api.mdx
@@ -0,0 +1,48 @@
+---
+title: "Messaging API"
+description: "Send and retrieve messages with A1Zap"
+---
+
+## Messaging API
+
+Use these endpoints to send individual messages and fetch recent messages.
+
+### Send an Individual Message
+
+Replace placeholders like ``, ``, and `` with your values.
+
+```bash
+curl -X POST "https://api.a1zap.com/v1/messages/individual//send" \
+ -H "Content-Type: application/json" \
+ -H "X-API-Key: " \
+ -d '{
+ "metadata" : {
+ "source" : "ios"
+ },
+ "chatId" : "",
+ "content" : "Hello from the API!"
+}'
+```
+
+### Get Messages (via Agent Webhook Runner)
+
+```bash
+curl -X POST "https://api.a1zap.com/api/run/agentWebhook/getMessages" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "args" : {
+ "agentId" : "",
+ "apiKey" : "",
+ "chatId" : "",
+ "limit" : 25
+ },
+ "format" : "json"
+}'
+```
+
+#### Notes
+
+- `X-API-Key` should be an API key with permissions to send/read messages for the target agent.
+- `chatId` identifies the conversation thread.
+- `metadata.source` is optional and can be used for analytics/debugging.
+
diff --git a/api-reference/.DS_Store b/api-reference/.DS_Store
new file mode 100644
index 0000000000000000000000000000000000000000..4acaaa8ee9918dc8df0550735b7d6a936714bd62
GIT binary patch
literal 8196
zcmeHMyG|QH6ukqM*cgepL83rnQbx!M{J{p9mc|7ovGHbk*sK*mky3>Hg@}9ue-Pyh
zprxgSjw)U1oI5jkXCHphpeVVsnz`$ldw0$`v)=Jt6Or0F?`;yT5K(~2wy}t&qVPD^
zQkio-tFQ|3L`^!Pgt~Ovk4xUtAqt2BqJStM3Wx%Kg96yIxddbOeLE_xC?E>_mkRLn
zL8Gz_olUJZj}9~@1%R!g+Z3E54=^#>+0fb4S`&(E+U!BOP~}n#Wy3M9*&Q}?HnrA<
zld|EY+|0^lD9X%^IBncXh1Ny#dHac2n9z@K@80R$8QTaSjoc!;yJe(d|yzgGG9*d87)JMA9^
zTj^SZBttgIFON$zt_H@%eFapl)oyKpr^~HNj5*iVs2@}PB
literal 0
HcmV?d00001
diff --git a/mint.json b/mint.json
index cac1457..e6e13de 100644
--- a/mint.json
+++ b/mint.json
@@ -37,6 +37,10 @@
{
"name": "A1Cron",
"url": "a1cron"
+ },
+ {
+ "name": "A1Zap",
+ "url": "a1zap"
}
],
"anchors": [
@@ -149,8 +153,22 @@
"a1cron/get-logs",
"a1cron/webhooks"
]
+ },
+ {
+ "group": "A1Zap",
+ "pages": [
+ "a1zap/index"
+ ]
+ },
+
+ {
+ "group": "Messaging API",
+ "pages": [
+ "a1zap/messaging-api"
+ ]
}
],
+
"footerSocials": {
"x": "https://x.com/a1baseapi",
"github": "https://github.com/a1baseai",
From a7b541471c7b051fac9e3edfa8a521be2d411322 Mon Sep 17 00:00:00 2001
From: Pennie
Date: Sat, 20 Sep 2025 04:37:33 -0700
Subject: [PATCH 40/40] a1zap
---
a1zap/index.mdx | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/a1zap/index.mdx b/a1zap/index.mdx
index 03dab94..4669740 100644
--- a/a1zap/index.mdx
+++ b/a1zap/index.mdx
@@ -1,16 +1,22 @@
---
title: "A1Zap"
-description: "Workflows and automations for A1Base"
+description: "Yap with friends and AI"
---
# A1Zap
-Welcome to A1Zap. This section will cover how you can start getting your agent chatting on the A1Zap app.
+Let your agent message users, groups, and communities directly within the A1Zap app.
+List your agent on our platform and let people start conversations with it using our API.
+
+Download the A1Zap app here: https://apps.apple.com/au/app/a1zap/id6748840042
+
-
+
+ Download A1Zap on the iOS AppStore Now →
+
+
Get started
-