fix: credit tracking, voice clone TTL, avatar upload ui, asset serving fallback, OAuth encryption, free plan video renders, backlink outreach sprint
This commit is contained in:
181
docs-site/docs/features/backlink-outreach/analytics.md
Normal file
181
docs-site/docs/features/backlink-outreach/analytics.md
Normal file
@@ -0,0 +1,181 @@
|
||||
# Analytics
|
||||
|
||||
Track campaign performance with built-in analytics including send volume trends, conversion funnels, reply classification breakdowns, and CSV exports.
|
||||
|
||||
## Dashboard Overview
|
||||
|
||||
The analytics tab provides a comprehensive view of your outreach performance:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[Campaign Analytics] --> B[Volume Trends]
|
||||
A --> C[Conversion Funnel]
|
||||
A --> D[Reply Classification]
|
||||
A --> E[Response Rate]
|
||||
A --> F[Placement Rate]
|
||||
A --> G[CSV Exports]
|
||||
|
||||
style A fill:#e3f2fd
|
||||
style B fill:#e8f5e8
|
||||
style G fill:#fff3e0
|
||||
```
|
||||
|
||||
## Metrics
|
||||
|
||||
### Send Volume Trends
|
||||
|
||||
A line chart showing daily email send volume over a configurable time window (7, 14, 30, or 90 days).
|
||||
|
||||
- **X-axis**: Date.
|
||||
- **Y-axis**: Number of emails sent.
|
||||
- **Use case**: Spot trends, ensure consistent outreach cadence, stay within daily caps.
|
||||
|
||||
### Conversion Funnel
|
||||
|
||||
A bar chart showing lead counts at each status stage:
|
||||
|
||||
| Stage | Description |
|
||||
|---|---|
|
||||
| Discovered | Total leads found. |
|
||||
| Contacted | Leads that received an outreach email. |
|
||||
| Replied | Leads that responded (interested or neutral). |
|
||||
| Placed | Leads that resulted in a published backlink. |
|
||||
|
||||
- **Use case**: Identify bottlenecks in your outreach pipeline.
|
||||
|
||||
### Reply Classification
|
||||
|
||||
A breakdown of auto-classified replies:
|
||||
|
||||
| Classification | Color | Meaning |
|
||||
|---|---|---|
|
||||
| Interested | Green | Positive response — follow up! |
|
||||
| Not interested | Red | Declined — auto-suppressed. |
|
||||
| Out of office | Yellow | Auto-responder — schedule follow-up. |
|
||||
| Replied | Blue | General response — needs review. |
|
||||
|
||||
### Response Rate
|
||||
|
||||
Percentage of sent emails that received any reply:
|
||||
|
||||
```
|
||||
Response Rate = (Total Replies / Total Sent) × 100
|
||||
```
|
||||
|
||||
### Placement Rate
|
||||
|
||||
Percentage of contacted leads that resulted in a published backlink:
|
||||
|
||||
```
|
||||
Placement Rate = (Placed Leads / Contacted Leads) × 100
|
||||
```
|
||||
|
||||
## Analytics API
|
||||
|
||||
### Campaign Analytics
|
||||
|
||||
**API:** `GET /api/v1/backlink-outreach/campaigns/{campaign_id}/analytics`
|
||||
|
||||
**Query parameters:**
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `days` | int | `30` | Number of days to include in trends. |
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"total_leads": 150,
|
||||
"leads_by_status": {
|
||||
"discovered": 80,
|
||||
"contacted": 45,
|
||||
"replied": 18,
|
||||
"placed": 7,
|
||||
"bounced": 5
|
||||
},
|
||||
"total_attempts": 52,
|
||||
"total_replies": 23,
|
||||
"replies_by_classification": {
|
||||
"interested": 12,
|
||||
"not_interested": 5,
|
||||
"out_of_office": 3,
|
||||
"replied": 3
|
||||
},
|
||||
"response_rate": 0.44,
|
||||
"placement_rate": 0.16,
|
||||
"daily_send_volume": [
|
||||
{"date": "2025-01-15", "count": 8},
|
||||
{"date": "2025-01-16", "count": 12}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Reporting Snapshot
|
||||
|
||||
Cross-campaign analytics across all campaigns for the authenticated user.
|
||||
|
||||
**API:** `GET /api/v1/backlink-outreach/reporting/snapshot`
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"total_campaigns": 5,
|
||||
"total_sends": 342,
|
||||
"total_replies": 87,
|
||||
"total_placements": 14,
|
||||
"overall_response_rate": 0.25,
|
||||
"overall_placement_rate": 0.04
|
||||
}
|
||||
```
|
||||
|
||||
!!! note "Reply counting"
|
||||
The reporting snapshot counts `OutreachReply` records (not `status == "replied"` on attempts). This ensures accuracy — a lead marked "replied" manually without an actual reply record won't inflate the count.
|
||||
|
||||
## CSV Exports
|
||||
|
||||
Export campaign data as CSV files for CRM import, spreadsheet analysis, or client reporting.
|
||||
|
||||
### Export Leads
|
||||
|
||||
**API:** `GET /api/v1/backlink-outreach/campaigns/{campaign_id}/export/leads`
|
||||
|
||||
### Export Attempts
|
||||
|
||||
**API:** `GET /api/v1/backlink-outreach/campaigns/{campaign_id}/export/attempts`
|
||||
|
||||
### Export Replies
|
||||
|
||||
**API:** `GET /api/v1/backlink-outreach/campaigns/{campaign_id}/export/replies`
|
||||
|
||||
### CSV Safety
|
||||
|
||||
All exports include these safety measures:
|
||||
|
||||
| Measure | Purpose |
|
||||
|---|---|
|
||||
| Explicit fieldnames | Only expected columns are included. |
|
||||
| `extrasaction="ignore"` | Unexpected fields are silently dropped. |
|
||||
| Formula injection sanitization | Cells starting with `=`, `+`, `-`, `@` are prefixed with a single quote to prevent formula injection in spreadsheets. |
|
||||
|
||||
!!! warning "Export loading"
|
||||
Exports may take a few seconds for large campaigns. The UI shows an "Exporting..." state with a disabled button while the download is in progress.
|
||||
|
||||
## UI Features
|
||||
|
||||
### Time Window Selector
|
||||
|
||||
Choose from 7, 14, 30, or 90 days for trend charts. The analytics data is re-fetched when the window changes.
|
||||
|
||||
### Separate Loading States
|
||||
|
||||
Each data section (attempts, replies, analytics) has its own loading indicator, so slow analytics queries don't block the entire page.
|
||||
|
||||
### Error Handling
|
||||
|
||||
If analytics or export requests fail, a toast notification shows the error message. On 5xx server errors, the store automatically retries read operations once with exponential backoff.
|
||||
|
||||
---
|
||||
|
||||
*Next: [API Reference](api-reference.md) — full endpoint documentation.*
|
||||
449
docs-site/docs/features/backlink-outreach/api-reference.md
Normal file
449
docs-site/docs/features/backlink-outreach/api-reference.md
Normal file
@@ -0,0 +1,449 @@
|
||||
# API Reference
|
||||
|
||||
Complete reference for all Backlink Outreach API endpoints. All endpoints require Clerk authentication via `Depends(get_current_user)`.
|
||||
|
||||
## Authentication
|
||||
|
||||
All endpoints use Clerk authentication. Include the session token in the `Authorization` header:
|
||||
|
||||
```
|
||||
Authorization: Bearer <clerk_session_token>
|
||||
```
|
||||
|
||||
The `user_id` is derived from the authenticated session — never from the request body.
|
||||
|
||||
## Endpoint Map
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph Campaigns
|
||||
C1[POST /campaigns]
|
||||
C2[GET /campaigns]
|
||||
C3[GET /campaigns/{id}]
|
||||
C4[DELETE /campaigns/{id}]
|
||||
end
|
||||
subgraph Leads
|
||||
L1[POST /campaigns/{id}/leads]
|
||||
L2[POST /campaigns/{id}/leads/bulk]
|
||||
L3[PATCH /campaigns/{id}/leads/{lead_id}/status]
|
||||
L4[PATCH /campaigns/{id}/leads/bulk-status]
|
||||
end
|
||||
subgraph Discovery
|
||||
D1[POST /discover/deep]
|
||||
end
|
||||
subgraph Email
|
||||
E1[POST /emails/generate]
|
||||
E2[POST /emails/personalize]
|
||||
E3[POST /emails/subject-suggestions]
|
||||
E4[POST /emails/follow-up]
|
||||
E5[POST /emails/templates]
|
||||
E6[GET /emails/templates]
|
||||
E7[GET /emails/templates/{id}]
|
||||
E8[DELETE /emails/templates/{id}]
|
||||
end
|
||||
subgraph Outreach
|
||||
O1[POST /outreach/send]
|
||||
O2[POST /policy/validate]
|
||||
O3[GET /campaigns/{id}/attempts]
|
||||
O4[GET /campaigns/{id}/follow-ups]
|
||||
end
|
||||
subgraph Replies
|
||||
R1[POST /replies/poll]
|
||||
R2[GET /campaigns/{id}/replies]
|
||||
end
|
||||
subgraph Suppression
|
||||
S1[POST /suppression]
|
||||
S2[GET /suppression]
|
||||
end
|
||||
subgraph Analytics
|
||||
A1[GET /campaigns/{id}/analytics]
|
||||
A2[GET /reporting/snapshot]
|
||||
A3[GET /campaigns/{id}/export/leads]
|
||||
A4[GET /campaigns/{id}/export/attempts]
|
||||
A5[GET /campaigns/{id}/export/replies]
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Campaigns
|
||||
|
||||
### Create Campaign
|
||||
|
||||
`POST /api/v1/backlink-outreach/campaigns`
|
||||
|
||||
**Request Body:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `name` | string | Yes | Campaign name. |
|
||||
| `description` | string | No | Campaign description. |
|
||||
| `keywords` | string[] | No | Target keywords for discovery. |
|
||||
|
||||
**Response:** `201 Created` — Campaign object.
|
||||
|
||||
### List Campaigns
|
||||
|
||||
`GET /api/v1/backlink-outreach/campaigns`
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `workspace_id` | string | user_id | Workspace to filter by. Defaults to authenticated user. |
|
||||
|
||||
**Response:** `200 OK` — Array of campaign objects.
|
||||
|
||||
### Get Campaign
|
||||
|
||||
`GET /api/v1/backlink-outreach/campaigns/{campaign_id}`
|
||||
|
||||
**Response:** `200 OK` — Campaign object with included leads.
|
||||
|
||||
### Delete Campaign
|
||||
|
||||
`DELETE /api/v1/backlink-outreach/campaigns/{campaign_id}`
|
||||
|
||||
**Response:** `204 No Content`
|
||||
|
||||
---
|
||||
|
||||
## Leads
|
||||
|
||||
### Add Lead
|
||||
|
||||
`POST /api/v1/backlink-outreach/campaigns/{campaign_id}/leads`
|
||||
|
||||
**Request Body:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `website_url` | string | Yes | Target website URL. |
|
||||
| `website_title` | string | No | Website title. |
|
||||
| `contact_email` | string | No | Contact email address. |
|
||||
| `quality_score` | float | No | Quality score (0-1). |
|
||||
| `relevance_score` | float | No | Relevance score (0-1). |
|
||||
| `guest_post_likelihood` | float | No | Guest post likelihood (0-1). |
|
||||
| `source` | string | No | Source of the lead. |
|
||||
|
||||
**Response:** `201 Created` — Lead object.
|
||||
|
||||
### Bulk Add Leads
|
||||
|
||||
`POST /api/v1/backlink-outreach/campaigns/{campaign_id}/leads/bulk`
|
||||
|
||||
**Request Body:** Array of lead objects.
|
||||
|
||||
**Response:** `200 OK`
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `added` | int | Number of leads successfully added. |
|
||||
| `skipped` | int | Number of duplicates skipped. |
|
||||
| `failed` | string[] | List of failed entries with reasons. |
|
||||
|
||||
### Update Lead Status
|
||||
|
||||
`PATCH /api/v1/backlink-outreach/campaigns/{campaign_id}/leads/{lead_id}/status`
|
||||
|
||||
**Request Body:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `status` | string | Yes | New status: discovered, contacted, replied, placed, bounced, lost. |
|
||||
|
||||
**Response:** `200 OK` — Updated lead object.
|
||||
|
||||
### Bulk Update Status
|
||||
|
||||
`PATCH /api/v1/backlink-outreach/campaigns/{campaign_id}/leads/bulk-status`
|
||||
|
||||
**Request Body:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `lead_ids` | string[] | Yes | Lead IDs to update. |
|
||||
| `status` | string | Yes | New status for all leads. |
|
||||
|
||||
**Response:** `200 OK`
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `updated` | int | Number of leads successfully updated. |
|
||||
| `failed` | string[] | List of lead IDs that failed to update. |
|
||||
|
||||
!!! warning "Partial failures"
|
||||
Bulk operations may partially succeed. Always check the `failed` field and show appropriate warnings to users.
|
||||
|
||||
---
|
||||
|
||||
## Discovery
|
||||
|
||||
### Deep Discovery
|
||||
|
||||
`POST /api/v1/backlink-outreach/discover/deep`
|
||||
|
||||
**Request Body:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `keyword` | string | Yes | Search keyword or phrase. |
|
||||
| `campaign_id` | string | No | Campaign to save results to. |
|
||||
| `max_results` | int | No | Maximum results to return (default 20). |
|
||||
| `save_to_campaign` | bool | No | Auto-save results to campaign. |
|
||||
|
||||
**Response:** `200 OK`
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `results` | array | Discovered opportunities with scores. |
|
||||
| `saved_to_campaign` | int | Number of leads saved to campaign. |
|
||||
| `save_failed` | int | Number of leads that failed to save. |
|
||||
|
||||
---
|
||||
|
||||
## Email
|
||||
|
||||
### Generate Email
|
||||
|
||||
`POST /api/v1/backlink-outreach/emails/generate`
|
||||
|
||||
**Request Body:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `topic` | string | Yes | Email topic. |
|
||||
| `tone` | string | No | professional, friendly, casual, formal. |
|
||||
| `template_id` | string | No | Template to base generation on. |
|
||||
|
||||
**Response:** `200 OK` — `{ subject, body }`
|
||||
|
||||
### Personalize Email
|
||||
|
||||
`POST /api/v1/backlink-outreach/emails/personalize`
|
||||
|
||||
**Request Body:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `base_email` | string | Yes | Email content to personalize. |
|
||||
| `lead_name` | string | No | Lead's name. |
|
||||
| `lead_website` | string | No | Lead's website. |
|
||||
| `content_topic` | string | No | Topic to reference. |
|
||||
|
||||
**Response:** `200 OK` — `{ subject, body }`
|
||||
|
||||
### Subject Suggestions
|
||||
|
||||
`POST /api/v1/backlink-outreach/emails/subject-suggestions`
|
||||
|
||||
**Request Body:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `topic` | string | Yes | Email topic. |
|
||||
| `tone` | string | No | Tone for suggestions. |
|
||||
|
||||
**Response:** `200 OK` — `{ suggestions: string[] }`
|
||||
|
||||
### Generate Follow-up
|
||||
|
||||
`POST /api/v1/backlink-outreach/emails/follow-up`
|
||||
|
||||
**Request Body:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `original_subject` | string | Yes | Subject of original email. |
|
||||
| `original_body` | string | Yes | Body of original email. |
|
||||
| `tone` | string | No | Tone for follow-up. |
|
||||
|
||||
**Response:** `200 OK` — `{ subject, body }`
|
||||
|
||||
### Create Template
|
||||
|
||||
`POST /api/v1/backlink-outreach/emails/templates`
|
||||
|
||||
**Request Body:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `name` | string | Yes | Template name. |
|
||||
| `subject` | string | Yes | Subject line with `{placeholders}`. |
|
||||
| `body` | string | Yes | Email body with `{placeholders}`. |
|
||||
| `category` | string | No | Template category. |
|
||||
|
||||
**Response:** `201 Created` — Template object.
|
||||
|
||||
### List Templates
|
||||
|
||||
`GET /api/v1/backlink-outreach/emails/templates`
|
||||
|
||||
**Response:** `200 OK` — Array of template objects.
|
||||
|
||||
### Get Template
|
||||
|
||||
`GET /api/v1/backlink-outreach/emails/templates/{template_id}`
|
||||
|
||||
**Response:** `200 OK` — Template object.
|
||||
|
||||
### Delete Template
|
||||
|
||||
`DELETE /api/v1/backlink-outreach/emails/templates/{template_id}`
|
||||
|
||||
**Response:** `204 No Content`
|
||||
|
||||
---
|
||||
|
||||
## Outreach
|
||||
|
||||
### Send Outreach
|
||||
|
||||
`POST /api/v1/backlink-outreach/outreach/send`
|
||||
|
||||
**Request Body:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `campaign_id` | string | Yes | Campaign for the outreach. |
|
||||
| `lead_id` | string | Yes | Lead to send to. |
|
||||
| `subject` | string | Yes | Email subject. |
|
||||
| `body` | string | Yes | Email body. |
|
||||
| `workspace_id` | string | No | Workspace ID (default "default"). |
|
||||
|
||||
**Response:** `200 OK` — Outreach attempt object.
|
||||
|
||||
**Error responses:**
|
||||
|
||||
| Code | Meaning |
|
||||
|---|---|
|
||||
| `403` | Policy validation failed (caps, suppression, idempotency). |
|
||||
| `500` | SMTP delivery failed (generic error, no stack trace). |
|
||||
|
||||
### Validate Policy
|
||||
|
||||
`POST /api/v1/backlink-outreach/policy/validate`
|
||||
|
||||
**Request Body:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `recipient_email` | string | Yes | Recipient email address. |
|
||||
| `sender_email` | string | Yes | Sender email address. |
|
||||
| `subject` | string | No | Email subject for idempotency check. |
|
||||
|
||||
**Response:** `200 OK` — Policy validation result with `allowed`, `reason`, `legal_basis`, counts, and limits.
|
||||
|
||||
### List Attempts
|
||||
|
||||
`GET /api/v1/backlink-outreach/campaigns/{campaign_id}/attempts`
|
||||
|
||||
**Response:** `200 OK` — Array of outreach attempt objects.
|
||||
|
||||
### List Follow-ups
|
||||
|
||||
`GET /api/v1/backlink-outreach/campaigns/{campaign_id}/follow-ups`
|
||||
|
||||
**Response:** `200 OK` — Array of follow-up objects.
|
||||
|
||||
---
|
||||
|
||||
## Replies
|
||||
|
||||
### Poll Replies
|
||||
|
||||
`POST /api/v1/backlink-outreach/replies/poll`
|
||||
|
||||
**Request Body:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `campaign_id` | string | No | Campaign to filter by. |
|
||||
|
||||
**Response:** `200 OK`
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `replies_found` | int | Number of new replies processed. |
|
||||
| `failed` | int | Number of replies that failed to process. |
|
||||
|
||||
### List Replies
|
||||
|
||||
`GET /api/v1/backlink-outreach/campaigns/{campaign_id}/replies`
|
||||
|
||||
**Response:** `200 OK` — Array of reply objects with classification.
|
||||
|
||||
---
|
||||
|
||||
## Suppression
|
||||
|
||||
### Add to Suppression
|
||||
|
||||
`POST /api/v1/backlink-outreach/suppression`
|
||||
|
||||
**Request Body:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `email` | string | Yes | Email to suppress. |
|
||||
| `reason` | string | No | Reason for suppression. |
|
||||
|
||||
**Response:** `201 Created` — Suppression record.
|
||||
|
||||
### List Suppressed
|
||||
|
||||
`GET /api/v1/backlink-outreach/suppression`
|
||||
|
||||
**Response:** `200 OK` — Array of suppression records.
|
||||
|
||||
---
|
||||
|
||||
## Analytics
|
||||
|
||||
### Campaign Analytics
|
||||
|
||||
`GET /api/v1/backlink-outreach/campaigns/{campaign_id}/analytics`
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `days` | int | 30 | Days to include in trends. |
|
||||
|
||||
**Response:** `200 OK` — Analytics object with leads_by_status, replies_by_classification, rates, and daily_send_volume.
|
||||
|
||||
### Reporting Snapshot
|
||||
|
||||
`GET /api/v1/backlink-outreach/reporting/snapshot`
|
||||
|
||||
**Response:** `200 OK` — Cross-campaign summary with total counts and rates.
|
||||
|
||||
### Export Leads
|
||||
|
||||
`GET /api/v1/backlink-outreach/campaigns/{campaign_id}/export/leads`
|
||||
|
||||
**Response:** `200 OK` — CSV file download.
|
||||
|
||||
### Export Attempts
|
||||
|
||||
`GET /api/v1/backlink-outreach/campaigns/{campaign_id}/export/attempts`
|
||||
|
||||
**Response:** `200 OK` — CSV file download.
|
||||
|
||||
### Export Replies
|
||||
|
||||
`GET /api/v1/backlink-outreach/campaigns/{campaign_id}/export/replies`
|
||||
|
||||
**Response:** `200 OK` — CSV file download.
|
||||
|
||||
---
|
||||
|
||||
## Common Error Responses
|
||||
|
||||
| Status | Meaning | Body |
|
||||
|---|---|---|
|
||||
| `401` | Not authenticated | `{"detail": "Not authenticated"}` |
|
||||
| `403` | Policy blocked | `{"detail": "Policy validation failed", "reason": "..."}` |
|
||||
| `404` | Not found | `{"detail": "Resource not found"}` |
|
||||
| `422` | Validation error | `{"detail": [...validation errors]}` |
|
||||
| `500` | Server error | `{"detail": "An internal error occurred"}` (generic, no stack trace) |
|
||||
108
docs-site/docs/features/backlink-outreach/campaign-management.md
Normal file
108
docs-site/docs/features/backlink-outreach/campaign-management.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# Campaign Management
|
||||
|
||||
Campaigns are the top-level organizational unit for backlink outreach. Every lead, email, attempt, reply, and analytics data point belongs to a campaign.
|
||||
|
||||
## Creating a Campaign
|
||||
|
||||
A campaign requires only a name. Add a description and keywords to make discovery and reporting easier.
|
||||
|
||||
**API:** `POST /api/v1/backlink-outreach/campaigns`
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "SaaS Growth Blogs Q3",
|
||||
"description": "Outreach to SaaS marketing blogs for guest post placements",
|
||||
"keywords": ["SaaS", "growth marketing", "B2B"]
|
||||
}
|
||||
```
|
||||
|
||||
**UI:** Navigate to **Backlink Outreach → Campaigns → + New Campaign**.
|
||||
|
||||
!!! tip "Naming conventions"
|
||||
Use a consistent naming scheme like `[Vertical] [Content Type] [Period]` — e.g., "Fitness Guest Posts June" or "AI Startups Roundup Q3".
|
||||
|
||||
## Campaign List View
|
||||
|
||||
The campaign list shows:
|
||||
- **Name** and description
|
||||
- **Lead count** broken down by status
|
||||
- **Creation date**
|
||||
- **Quick actions**: Add leads, view analytics, manage templates
|
||||
|
||||
## Campaign Detail View
|
||||
|
||||
Click a campaign to see its full detail:
|
||||
- **Leads tab**: All leads with status, quality score, and actions.
|
||||
- **Email tab**: Compose and preview outreach emails.
|
||||
- **Outreach tab**: Send emails, view attempts, manage follow-ups.
|
||||
- **Inbox tab**: Replies with auto-classification tags.
|
||||
- **Analytics tab**: Campaign-specific charts and metrics.
|
||||
|
||||
## Managing Leads
|
||||
|
||||
### Adding Leads
|
||||
|
||||
**Single lead:**
|
||||
`POST /api/v1/backlink-outreach/campaigns/{campaign_id}/leads`
|
||||
|
||||
```json
|
||||
{
|
||||
"website_url": "https://example.com",
|
||||
"website_title": "Example Marketing Blog",
|
||||
"contact_email": "editor@example.com",
|
||||
"quality_score": 0.85,
|
||||
"relevance_score": 0.72,
|
||||
"guest_post_likelihood": 0.65,
|
||||
"source": "manual"
|
||||
}
|
||||
```
|
||||
|
||||
**Bulk add:**
|
||||
`POST /api/v1/backlink-outreach/campaigns/{campaign_id}/leads/bulk`
|
||||
|
||||
Send an array of lead objects to add multiple leads at once.
|
||||
|
||||
### Updating Lead Status
|
||||
|
||||
Lead status lifecycle:
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> discovered
|
||||
discovered --> contacted: Send outreach email
|
||||
contacted --> replied: Lead replies (interested)
|
||||
contacted --> bounced: Email bounced / not interested
|
||||
replied --> placed: Backlink published
|
||||
replied --> lost: Lead declined after reply
|
||||
placed --> [*]
|
||||
lost --> [*]
|
||||
bounced --> [*]
|
||||
```
|
||||
|
||||
**Single update:** Click the status button on a lead card.
|
||||
|
||||
**Bulk update:** Select multiple leads → choose new status → confirm.
|
||||
|
||||
!!! warning "Bulk status updates"
|
||||
Bulk updates may partially fail. If some leads can't be updated, the response includes a `failed` list and the UI shows a warning toast with the count of failures.
|
||||
|
||||
## Deleting a Campaign
|
||||
|
||||
`DELETE /api/v1/backlink-outreach/campaigns/{campaign_id}`
|
||||
|
||||
!!! warning "Irreversible"
|
||||
Deleting a campaign removes all associated leads, attempts, replies, and analytics data. This action cannot be undone.
|
||||
|
||||
## Campaign Organization Best Practices
|
||||
|
||||
| Practice | Why |
|
||||
|---|---|
|
||||
| One campaign per vertical | Keeps leads relevant and analytics clean. |
|
||||
| Add keywords at creation | Powers better discovery queries later. |
|
||||
| Review leads before sending | Avoid wasting daily caps on low-quality leads. |
|
||||
| Archive completed campaigns | Keeps the campaign list manageable. |
|
||||
| Use consistent naming | Easier to find and compare campaigns later. |
|
||||
|
||||
---
|
||||
|
||||
*Next: [Discovery](discovery.md) — finding opportunities with AI-powered search.*
|
||||
122
docs-site/docs/features/backlink-outreach/configuration.md
Normal file
122
docs-site/docs/features/backlink-outreach/configuration.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# Configuration
|
||||
|
||||
Environment variables and deployment configuration for the Backlink Outreach feature.
|
||||
|
||||
## SMTP Configuration
|
||||
|
||||
Required for sending outreach emails.
|
||||
|
||||
| Variable | Required | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `SMTP_HOST` | Yes | — | SMTP server hostname. |
|
||||
| `SMTP_PORT` | No | `587` | SMTP server port. Use 587 for STARTTLS, 465 for implicit TLS. |
|
||||
| `SMTP_USER` | Yes | — | SMTP authentication username. |
|
||||
| `SMTP_PASS` | Yes | — | SMTP authentication password. |
|
||||
| `SMTP_FROM_EMAIL` | Yes | — | Default "From" email address for outreach. |
|
||||
| `SMTP_FROM_NAME` | No | — | Display name for the From address. |
|
||||
| `SMTP_VERIFY_TLS` | No | `true` | Verify TLS certificate on SMTP connection. Set to `false` only for local dev. |
|
||||
| `SMTP_SEND_TIMEOUT` | No | `30` | Timeout in seconds for each SMTP send operation. |
|
||||
|
||||
!!! warning "SMTP_VERIFY_TLS"
|
||||
Never set `SMTP_VERIFY_TLS=false` in production. Disabling TLS verification exposes you to man-in-the-middle attacks. Only use `false` for local development with self-signed certificates.
|
||||
|
||||
## IMAP Configuration
|
||||
|
||||
Required for reply monitoring.
|
||||
|
||||
| Variable | Required | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `IMAP_HOST` | Yes | — | IMAP server hostname. |
|
||||
| `IMAP_PORT` | No | `993` | IMAP server port. 993 for SSL, 143 for STARTTLS. |
|
||||
| `IMAP_USER` | Yes | — | IMAP authentication username. |
|
||||
| `IMAP_PASS` | Yes | — | IMAP authentication password. |
|
||||
| `IMAP_FETCH_LIMIT` | No | `50` | Maximum messages to process per poll cycle. |
|
||||
|
||||
## Search API Configuration
|
||||
|
||||
Required for AI-powered opportunity discovery.
|
||||
|
||||
| Variable | Required | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `EXA_API_KEY` | No | — | Exa neural search API key. Discovery falls back to DuckDuckGo if not set. |
|
||||
|
||||
## AI Configuration
|
||||
|
||||
Required for email generation and personalization.
|
||||
|
||||
| Variable | Required | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `OPENAI_API_KEY` | Yes | — | OpenAI API key for email generation, personalization, and subject suggestions. |
|
||||
|
||||
## Policy Configuration
|
||||
|
||||
These are currently hardcoded but can be made configurable:
|
||||
|
||||
| Setting | Current Value | Description |
|
||||
|---|---|---|
|
||||
| Daily user cap | 100 | Max emails per user per day. |
|
||||
| Daily domain cap | 20 | Max emails per target domain per day. |
|
||||
| Idempotency window | 24 hours | Duplicate send prevention window. |
|
||||
|
||||
## Database Configuration
|
||||
|
||||
The Backlink Outreach feature uses SQLite with automatic table creation:
|
||||
|
||||
| Variable | Required | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `DATABASE_URL` | No | `sqlite+aiosqlite:///./backlink_outreach.db` | Database connection string. |
|
||||
|
||||
Tables are created automatically on first use via `_ensure_tables()`. No manual migration is required.
|
||||
|
||||
## Deployment Checklist
|
||||
|
||||
### Minimal Setup
|
||||
|
||||
1. Set all **SMTP** environment variables.
|
||||
2. Set all **IMAP** environment variables.
|
||||
3. Set `OPENAI_API_KEY`.
|
||||
4. Optionally set `EXA_API_KEY` for Exa-powered discovery.
|
||||
5. Start the backend server.
|
||||
6. Verify health: `GET /api/v1/backlink-outreach/campaigns` (returns empty list if auth works).
|
||||
|
||||
### Production Setup
|
||||
|
||||
1. All minimal setup steps.
|
||||
2. Ensure `SMTP_VERIFY_TLS=true` (default).
|
||||
3. Set `SMTP_SEND_TIMEOUT` to 30+ seconds for reliable delivery.
|
||||
4. Set `IMAP_FETCH_LIMIT` based on mailbox volume (50-200).
|
||||
5. Set up a scheduled job to poll replies every 5-15 minutes.
|
||||
6. Configure monitoring for SMTP/IMAP connection failures.
|
||||
7. Review the suppression list periodically.
|
||||
|
||||
### Email Provider Setup
|
||||
|
||||
The system works with any SMTP/IMAP provider:
|
||||
|
||||
| Provider | SMTP Host | SMTP Port | IMAP Host | IMAP Port |
|
||||
|---|---|---|---|---|
|
||||
| Gmail | smtp.gmail.com | 587 | imap.gmail.com | 993 |
|
||||
| Outlook | smtp.office365.com | 587 | outlook.office365.com | 993 |
|
||||
| SendGrid | smtp.sendgrid.net | 587 | — (use webhooks) | — |
|
||||
| Mailgun | smtp.mailgun.org | 587 | — (use webhooks) | — |
|
||||
| Amazon SES | email-smtp.*.amazonaws.com | 587 | — (use SNS) | — |
|
||||
|
||||
!!! note "Transaction email providers"
|
||||
SendGrid, Mailgun, and Amazon SES don't support IMAP. For reply monitoring with these providers, you'll need to set up inbound webhooks or use a separate IMAP-capable mailbox.
|
||||
|
||||
## Security Considerations
|
||||
|
||||
| Area | Recommendation |
|
||||
|---|---|
|
||||
| **SMTP credentials** | Store in environment variables, never in code or config files. |
|
||||
| **IMAP credentials** | Use app-specific passwords (Gmail) or dedicated mailbox accounts. |
|
||||
| **TLS verification** | Always enabled in production (`SMTP_VERIFY_TLS=true`). |
|
||||
| **Error responses** | 500 errors return generic messages — no stack traces leaked. |
|
||||
| **Auth** | All endpoints require Clerk authentication. User identity derived from session, not request body. |
|
||||
| **SQL injection** | Column names are whitelisted and quoted in dynamic SQL. |
|
||||
| **IMAP injection** | Search terms are sanitized before IMAP SEARCH commands. |
|
||||
| **CSV injection** | All CSV exports sanitize formula injection characters. |
|
||||
|
||||
---
|
||||
|
||||
*Next: [Implementation Overview](implementation-overview.md) — architecture and internals.*
|
||||
132
docs-site/docs/features/backlink-outreach/discovery.md
Normal file
132
docs-site/docs/features/backlink-outreach/discovery.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# Discovery
|
||||
|
||||
The discovery system finds websites that accept guest posts in your niche using AI-powered search across multiple engines.
|
||||
|
||||
## How It Works
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Enter Keyword] --> B[Generate Query Patterns]
|
||||
B --> C1[Exa Neural Search]
|
||||
B --> C2[DuckDuckGo Search]
|
||||
C1 --> D[Merge & Deduplicate Results]
|
||||
C2 --> D
|
||||
D --> E[Scrape Full Pages]
|
||||
E --> F[Extract Contact Emails]
|
||||
F --> G[Score Quality & Relevance]
|
||||
G --> H[Return Ranked Results]
|
||||
H --> I[Save to Campaign]
|
||||
|
||||
style A fill:#e3f2fd
|
||||
style G fill:#e8f5e8
|
||||
style I fill:#fff3e0
|
||||
```
|
||||
|
||||
## Search Engines
|
||||
|
||||
### Exa Neural Search
|
||||
|
||||
Exa uses semantic understanding to find pages that *mean* what you're looking for, not just pages that contain the keywords.
|
||||
|
||||
- **Strength**: High-relevance results, understands context.
|
||||
- **Limitation**: Requires `EXA_API_KEY` environment variable.
|
||||
- **Best for**: Niche-specific discovery, finding high-quality sites.
|
||||
|
||||
### DuckDuckGo Search
|
||||
|
||||
DuckDuckGo provides broad coverage with traditional keyword matching.
|
||||
|
||||
- **Strength**: No API key required, broad coverage.
|
||||
- **Limitation**: Less semantic understanding.
|
||||
- **Best for**: Broad discovery, supplementing Exa results.
|
||||
|
||||
## Query Patterns
|
||||
|
||||
The system automatically generates multiple search queries from your keyword:
|
||||
|
||||
| Pattern | Example (keyword: "AI marketing") |
|
||||
|---|---|
|
||||
| `{keyword} write for us` | "AI marketing write for us" |
|
||||
| `{keyword} guest post` | "AI marketing guest post" |
|
||||
| `{keyword} contribute` | "AI marketing contribute" |
|
||||
| `{keyword} submit article` | "AI marketing submit article" |
|
||||
| `{keyword} become a contributor` | "AI marketing become a contributor" |
|
||||
| `{keyword} guest contributor guidelines` | "AI marketing guest contributor guidelines" |
|
||||
|
||||
## Deep Discovery
|
||||
|
||||
Deep discovery goes beyond search results by:
|
||||
|
||||
1. **Scraping full pages** — not just snippets, but the complete HTML.
|
||||
2. **Extracting contact emails** — parses `mailto:` links, contact pages, and author bios.
|
||||
3. **Detecting guest post guidelines** — identifies pages with "write for us" or submission instructions.
|
||||
4. **Scoring quality** — assigns a 0-1 quality score based on relevance, authority signals, and content quality.
|
||||
5. **Scoring confidence** — assigns a 0-1 confidence score for guest-post likelihood.
|
||||
|
||||
**API:** `POST /api/v1/backlink-outreach/discover/deep`
|
||||
|
||||
```json
|
||||
{
|
||||
"keyword": "AI marketing",
|
||||
"campaign_id": "uuid-of-campaign",
|
||||
"max_results": 20,
|
||||
"save_to_campaign": true
|
||||
}
|
||||
```
|
||||
|
||||
!!! note "Automatic saving"
|
||||
When `save_to_campaign` is `true`, discovered leads are automatically saved to the specified campaign. The response includes `saved_to_campaign` and `save_failed` counts.
|
||||
|
||||
## Result Scoring
|
||||
|
||||
Each result is scored on two dimensions:
|
||||
|
||||
### Quality Score (0-1)
|
||||
|
||||
How relevant and authoritative is the site for your keyword?
|
||||
|
||||
| Factor | Weight |
|
||||
|---|---|
|
||||
| Keyword relevance in title/URL | High |
|
||||
| Domain authority signals | Medium |
|
||||
| Content freshness | Low |
|
||||
| Site structure (blog section) | Medium |
|
||||
|
||||
### Confidence Score (0-1)
|
||||
|
||||
How likely is the site to accept guest posts?
|
||||
|
||||
| Factor | Weight |
|
||||
|---|---|
|
||||
| "Write for us" page found | Very High |
|
||||
| Guest post guidelines detected | High |
|
||||
| Contact email found | High |
|
||||
| Previous guest posts on site | Medium |
|
||||
| Blog section exists | Low |
|
||||
|
||||
## Reviewing Results
|
||||
|
||||
After discovery, review each result:
|
||||
|
||||
| Badge | Meaning |
|
||||
|---|---|
|
||||
| **Email found** | A contact email was extracted from the page. |
|
||||
| **Has guidelines** | A guest post guidelines page was detected. |
|
||||
| **High quality** | Quality score > 0.7. |
|
||||
| **High confidence** | Confidence score > 0.7. |
|
||||
|
||||
!!! tip "Prioritize leads"
|
||||
Focus on leads with both "Email found" and "Has guidelines" badges — these have the highest conversion potential.
|
||||
|
||||
## Saving to Campaign
|
||||
|
||||
Results can be saved to a campaign in two ways:
|
||||
|
||||
1. **Automatic**: Set `save_to_campaign: true` in the deep discovery request.
|
||||
2. **Manual**: Select results in the UI and click **Save to Campaign**.
|
||||
|
||||
Duplicate leads (same `website_url` in the same campaign) are automatically skipped.
|
||||
|
||||
---
|
||||
|
||||
*Next: [Email Composer](email-composer.md) — AI-powered email generation and personalization.*
|
||||
167
docs-site/docs/features/backlink-outreach/email-composer.md
Normal file
167
docs-site/docs/features/backlink-outreach/email-composer.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# Email Composer
|
||||
|
||||
The AI email composer generates personalized outreach emails, subject lines, and follow-ups using large language models.
|
||||
|
||||
## AI Generation Modes
|
||||
|
||||
### Generate
|
||||
|
||||
Create a complete email (subject + body) from a topic and tone.
|
||||
|
||||
**API:** `POST /api/v1/backlink-outreach/emails/generate`
|
||||
|
||||
```json
|
||||
{
|
||||
"topic": "Guest post about AI marketing trends",
|
||||
"tone": "professional",
|
||||
"template_id": "optional-template-uuid"
|
||||
}
|
||||
```
|
||||
|
||||
**Available tones:**
|
||||
|
||||
| Tone | Style |
|
||||
|---|---|
|
||||
| `professional` | Formal, business-appropriate language. |
|
||||
| `friendly` | Warm, approachable, conversational. |
|
||||
| `casual` | Relaxed, informal, peer-to-peer. |
|
||||
| `formal` | Highly structured, traditional business correspondence. |
|
||||
|
||||
### Personalize
|
||||
|
||||
Tailor an email to a specific lead using their name, website, and content.
|
||||
|
||||
**API:** `POST /api/v1/backlink-outreach/emails/personalize`
|
||||
|
||||
```json
|
||||
{
|
||||
"base_email": "I'd love to contribute a guest post...",
|
||||
"lead_name": "Jane",
|
||||
"lead_website": "techblog.example.com",
|
||||
"content_topic": "AI Marketing Trends 2025"
|
||||
}
|
||||
```
|
||||
|
||||
### Subject Line Suggestions
|
||||
|
||||
Get 5-10 AI-generated subject line variants for A/B testing.
|
||||
|
||||
**API:** `POST /api/v1/backlink-outreach/emails/subject-suggestions`
|
||||
|
||||
```json
|
||||
{
|
||||
"topic": "Guest post about AI marketing trends",
|
||||
"tone": "professional"
|
||||
}
|
||||
```
|
||||
|
||||
### Follow-up Draft
|
||||
|
||||
Generate a polite follow-up email referencing the original outreach.
|
||||
|
||||
**API:** `POST /api/v1/backlink-outreach/emails/follow-up`
|
||||
|
||||
```json
|
||||
{
|
||||
"original_subject": "Guest Post: AI Marketing Trends",
|
||||
"original_body": "I'd love to contribute...",
|
||||
"tone": "friendly"
|
||||
}
|
||||
```
|
||||
|
||||
## Template System
|
||||
|
||||
Templates let you save and reuse winning email structures with variable placeholders.
|
||||
|
||||
### Creating a Template
|
||||
|
||||
**API:** `POST /api/v1/backlink-outreach/emails/templates`
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Standard Guest Post Pitch",
|
||||
"subject": "Guest Post: {topic}",
|
||||
"body": "Hi {name},\n\nI've been following {website} and really enjoyed your recent posts...",
|
||||
"category": "guest-post"
|
||||
}
|
||||
```
|
||||
|
||||
### Supported Placeholders
|
||||
|
||||
| Placeholder | Replaced With |
|
||||
|---|---|
|
||||
| `{name}` | Lead's contact name. |
|
||||
| `{website}` | Lead's website URL. |
|
||||
| `{topic}` | Your content topic. |
|
||||
| `{your_name}` | Your name (from sender config). |
|
||||
| `{your_site}` | Your website URL (from sender config). |
|
||||
|
||||
!!! tip "Template best practices"
|
||||
- Use `{name}` for personalization — emails with names get 26% higher open rates.
|
||||
- Keep subject lines under 50 characters.
|
||||
- Include a clear call-to-action in every template.
|
||||
- Test multiple templates and track which gets the best response rate.
|
||||
|
||||
### Managing Templates
|
||||
|
||||
| Action | Endpoint |
|
||||
|---|---|
|
||||
| List templates | `GET /api/v1/backlink-outreach/emails/templates` |
|
||||
| Get template | `GET /api/v1/backlink-outreach/emails/templates/{template_id}` |
|
||||
| Delete template | `DELETE /api/v1/backlink-outreach/emails/templates/{template_id}` |
|
||||
|
||||
## Email Composer UI
|
||||
|
||||
The composer provides:
|
||||
|
||||
- **Topic input**: Describe what you want to write about.
|
||||
- **Tone selector**: Choose the writing style.
|
||||
- **Template picker**: Start from a saved template.
|
||||
- **Generate button**: Create AI email from inputs.
|
||||
- **Personalize button**: Tailor the current email to a specific lead.
|
||||
- **Subject Suggest button**: Get subject line variants.
|
||||
- **Live preview**: See the rendered email as you edit.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[Choose Template] --> B[Enter Topic + Tone]
|
||||
B --> C[Generate with AI]
|
||||
C --> D{Satisfied?}
|
||||
D -->|Yes| E[Send Outreach]
|
||||
D -->|No| F[Personalize / Edit]
|
||||
F --> D
|
||||
C --> G[Suggest Subjects]
|
||||
G --> H[Pick Best Subject]
|
||||
H --> E
|
||||
|
||||
style C fill:#e8f5e8
|
||||
style E fill:#fff3e0
|
||||
```
|
||||
|
||||
## Writing Effective Outreach Emails
|
||||
|
||||
### Subject Lines
|
||||
|
||||
- Be specific: "Guest Post: 5 AI Marketing Trends for 2025" > "Collaboration?"
|
||||
- Keep it short: Under 50 characters for best open rates.
|
||||
- Avoid spam triggers: ALL CAPS, excessive punctuation, "free", "guaranteed".
|
||||
|
||||
### Email Body
|
||||
|
||||
- **First line**: Reference their content specifically (proves you read their site).
|
||||
- **Value proposition**: What's in it for them (free quality content, fresh perspective).
|
||||
- **Credentials**: Brief mention of your expertise or published work.
|
||||
- **Call-to-action**: One clear next step (reply with interest, check your draft).
|
||||
- **Signature**: Professional sign-off with links to your published work.
|
||||
|
||||
### Follow-ups
|
||||
|
||||
- Wait 3-5 business days before following up.
|
||||
- Reference the original email date and subject.
|
||||
- Add new value (a specific article idea, a data point).
|
||||
- Keep it shorter than the original.
|
||||
- Maximum 2 follow-ups per lead.
|
||||
|
||||
---
|
||||
|
||||
*Next: [Outreach Operations](outreach-operations.md) — sending, policy validation, and suppression.*
|
||||
@@ -0,0 +1,317 @@
|
||||
# Implementation Overview
|
||||
|
||||
Architecture, database schema, service layer, and authentication flow for the Backlink Outreach feature.
|
||||
|
||||
## Architecture
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph Frontend
|
||||
UI[Dashboard Component]
|
||||
Store[Zustand Store]
|
||||
API[API Client]
|
||||
end
|
||||
subgraph Backend
|
||||
Router[FastAPI Router]
|
||||
Service[Outreach Service]
|
||||
Storage[Storage Layer]
|
||||
Sender[SMTP Sender]
|
||||
Monitor[IMAP Monitor]
|
||||
end
|
||||
subgraph External
|
||||
SMTP[SMTP Server]
|
||||
IMAP[IMAP Server]
|
||||
EXA[Exa API]
|
||||
DDG[DuckDuckGo]
|
||||
LLM[OpenAI API]
|
||||
Clerk[Clerk Auth]
|
||||
end
|
||||
|
||||
UI --> Store
|
||||
Store --> API
|
||||
API --> Router
|
||||
Router --> Service
|
||||
Router --> Storage
|
||||
Service --> Storage
|
||||
Service --> Sender
|
||||
Service --> Monitor
|
||||
Sender --> SMTP
|
||||
Monitor --> IMAP
|
||||
Service --> EXA
|
||||
Service --> DDG
|
||||
Service --> LLM
|
||||
Router --> Clerk
|
||||
|
||||
style Frontend fill:#e3f2fd
|
||||
style Backend fill:#e8f5e8
|
||||
style External fill:#fff3e0
|
||||
```
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
backend/
|
||||
├── routers/
|
||||
│ └── backlink_outreach.py # 18+ API endpoints
|
||||
├── services/
|
||||
│ ├── backlink_outreach_service.py # Business logic, policy, analytics
|
||||
│ ├── backlink_outreach_storage.py # SQLite CRUD operations
|
||||
│ ├── backlink_outreach_sender.py # SMTP email delivery
|
||||
│ ├── backlink_outreach_reply_monitor.py # IMAP reply polling
|
||||
│ └── backlink_outreach_models.py # Pydantic request/response models
|
||||
├── models/
|
||||
│ └── backlink_outreach_models.py # SQLAlchemy models + indexes
|
||||
|
||||
frontend/src/
|
||||
├── components/
|
||||
│ └── BacklinkOutreach/
|
||||
│ └── BacklinkOutreachDashboard.tsx # Main UI component
|
||||
├── stores/
|
||||
│ └── backlinkOutreachStore.ts # Zustand state management
|
||||
└── api/
|
||||
└── backlinkOutreachApi.ts # API client functions
|
||||
```
|
||||
|
||||
## Database Schema
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
BacklinkCampaign {
|
||||
string id PK
|
||||
string user_id
|
||||
string name
|
||||
string description
|
||||
string keywords
|
||||
datetime created_at
|
||||
datetime updated_at
|
||||
}
|
||||
BacklinkLead {
|
||||
string id PK
|
||||
string campaign_id FK
|
||||
string website_url
|
||||
string website_title
|
||||
string contact_email
|
||||
float quality_score
|
||||
float relevance_score
|
||||
float guest_post_likelihood
|
||||
string status
|
||||
string source
|
||||
datetime created_at
|
||||
}
|
||||
OutreachAttempt {
|
||||
string id PK
|
||||
string campaign_id FK
|
||||
string lead_id FK
|
||||
string user_id
|
||||
string sender_email
|
||||
string recipient_email
|
||||
string subject
|
||||
string body
|
||||
string status
|
||||
string legal_basis
|
||||
datetime sent_at
|
||||
}
|
||||
OutreachReply {
|
||||
string id PK
|
||||
string campaign_id FK
|
||||
string attempt_id FK
|
||||
string from_email
|
||||
string subject
|
||||
string body
|
||||
string classification
|
||||
datetime received_at
|
||||
}
|
||||
SuppressionEntry {
|
||||
string id PK
|
||||
string user_id
|
||||
string email
|
||||
string reason
|
||||
datetime created_at
|
||||
}
|
||||
AuditLog {
|
||||
string id PK
|
||||
string user_id
|
||||
string lead_email
|
||||
string sender_email
|
||||
string subject
|
||||
string policy_result
|
||||
string reason
|
||||
string legal_basis
|
||||
datetime timestamp
|
||||
}
|
||||
SendCounterUser {
|
||||
string id PK
|
||||
string user_id
|
||||
date date
|
||||
int count
|
||||
}
|
||||
SendCounterDomain {
|
||||
string id PK
|
||||
string domain
|
||||
date date
|
||||
int count
|
||||
}
|
||||
IdempotencyKey {
|
||||
string id PK
|
||||
string key
|
||||
datetime created_at
|
||||
}
|
||||
EmailTemplate {
|
||||
string id PK
|
||||
string user_id
|
||||
string name
|
||||
string subject
|
||||
string body
|
||||
string category
|
||||
datetime created_at
|
||||
}
|
||||
FollowUp {
|
||||
string id PK
|
||||
string attempt_id FK
|
||||
string campaign_id FK
|
||||
string subject
|
||||
string body
|
||||
string status
|
||||
datetime scheduled_at
|
||||
datetime sent_at
|
||||
}
|
||||
|
||||
BacklinkCampaign ||--o{ BacklinkLead : contains
|
||||
BacklinkCampaign ||--o{ OutreachAttempt : tracks
|
||||
BacklinkCampaign ||--o{ OutreachReply : receives
|
||||
BacklinkCampaign ||--o{ EmailTemplate : owns
|
||||
OutreachAttempt ||--o{ OutreachReply : generates
|
||||
OutreachAttempt ||--o{ FollowUp : schedules
|
||||
```
|
||||
|
||||
### Unique Indexes
|
||||
|
||||
| Table | Unique Constraint | Purpose |
|
||||
|---|---|---|
|
||||
| `SendCounterUser` | `(user_id, date)` | Atomic daily cap per user. |
|
||||
| `SendCounterDomain` | `(domain, date)` | Atomic daily cap per domain. |
|
||||
|
||||
These enable `INSERT ... ON CONFLICT DO UPDATE` for atomic counter increments.
|
||||
|
||||
## Service Layer
|
||||
|
||||
### Outreach Service (`backlink_outreach_service.py`)
|
||||
|
||||
Core business logic:
|
||||
|
||||
- `_infer_region(domain)` — Maps 25+ EU TLDs + UK/CA/AU to region codes.
|
||||
- `_determine_legal_basis(recipient_email)` — EU/UK/CA/AU → `consent`, others → `legitimate_interest`.
|
||||
- `validate_policy(...)` — Runs all policy checks, returns approval/block with reasons.
|
||||
- `send_outreach_email(...)` — Orchestrates policy → attempt → SMTP → counters → idempotency.
|
||||
- `deep_discover(...)` — Exa + DuckDuckGo search, page scraping, email extraction, scoring.
|
||||
- `generate_email(...)` — LLM-based email generation with topic + tone.
|
||||
- `personalize_email(...)` — LLM-based personalization for a specific lead.
|
||||
- `get_campaign_analytics(...)` — Aggregates campaign metrics.
|
||||
- `get_reporting_snapshot(...)` — Cross-campaign summary.
|
||||
- `export_leads_csv(...)` / `export_attempts_csv(...)` / `export_replies_csv(...)` — CSV generation with formula injection sanitization.
|
||||
|
||||
### Storage Layer (`backlink_outreach_storage.py`)
|
||||
|
||||
SQLite CRUD operations with 20+ methods:
|
||||
|
||||
- Campaign CRUD: `create_campaign`, `list_backlink_campaigns`, `get_campaign`, `delete_campaign`.
|
||||
- Lead management: `add_campaign_lead`, `add_campaign_leads_bulk`, `update_lead_status`, `bulk_update_lead_status`.
|
||||
- Outreach: `create_outreach_attempt`, `list_outreach_attempts`, `get_lead_attempts`.
|
||||
- Replies: `store_reply`, `find_attempt_by_from_email`, `reply_exists`, `list_replies`, `count_replies`.
|
||||
- Follow-ups: `create_follow_up`, `list_follow_ups`.
|
||||
- Suppression: `add_suppression`, `list_suppression`, `is_suppressed`.
|
||||
- Counters: `increment_user_counter`, `increment_domain_counter` (atomic ON CONFLICT).
|
||||
- Idempotency: `check_idempotency`, `mark_idempotency`.
|
||||
- Audit: `log_audit_entry`.
|
||||
- Templates: `create_email_template`, `list_email_templates`, `get_email_template`, `delete_email_template`.
|
||||
|
||||
All methods call `_ensure_tables()` on first use to auto-create the SQLite schema.
|
||||
|
||||
### SMTP Sender (`backlink_outreach_sender.py`)
|
||||
|
||||
Handles email delivery:
|
||||
|
||||
1. Creates SSL context with `ssl.create_default_context()`.
|
||||
2. Connects to SMTP host.
|
||||
3. Sends `EHLO` greeting.
|
||||
4. Upgrades with `STARTTLS`.
|
||||
5. Sends `EHLO` again (RFC 3207 requirement).
|
||||
6. Authenticates with credentials.
|
||||
7. Sends email with configurable timeout (`SMTP_SEND_TIMEOUT`).
|
||||
8. Cleanly closes the connection.
|
||||
|
||||
### Reply Monitor (`backlink_outreach_reply_monitor.py`)
|
||||
|
||||
Handles IMAP reply processing:
|
||||
|
||||
1. Connects to IMAP over SSL.
|
||||
2. Sanitizes search terms (prevents IMAP injection).
|
||||
3. Searches for messages matching the outreach sender.
|
||||
4. Fetches up to `IMAP_FETCH_LIMIT` messages.
|
||||
5. Checks for duplicates via `reply_exists()`.
|
||||
6. Matches replies to attempts via `find_attempt_by_from_email()`.
|
||||
7. Classifies replies based on content analysis.
|
||||
8. Stores reply records.
|
||||
|
||||
## Authentication Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client as Frontend
|
||||
participant Router as API Router
|
||||
participant Clerk as Clerk Auth
|
||||
participant Service as Service Layer
|
||||
|
||||
Client->>Router: Request with Bearer token
|
||||
Router->>Clerk: Verify session token
|
||||
Clerk-->>Router: user_id
|
||||
Router->>Service: Execute with user_id
|
||||
Service-->>Router: Result (scoped to user_id)
|
||||
Router-->>Client: Response
|
||||
```
|
||||
|
||||
Key principles:
|
||||
|
||||
- **All 18+ endpoints** require `Depends(get_current_user)`.
|
||||
- **User identity** is derived from the Clerk session, never from the request body.
|
||||
- **Workspace isolation**: Data is scoped by `user_id` (from Clerk) or `workspace_id` (from request, defaults to `user_id`).
|
||||
- **No client-controlled user_id**: The `GenerateEmailRequest` and `EmailTemplateRequest` models do not include a `user_id` field — it's always derived from auth.
|
||||
|
||||
## Frontend Architecture
|
||||
|
||||
### State Management (Zustand)
|
||||
|
||||
The `backlinkOutreachStore` manages all client state:
|
||||
|
||||
- **Campaign data**: List, selected campaign, leads.
|
||||
- **UI state**: Active tab, loading flags (`isAttemptsLoading`, `isRepliesLoading`, `isAnalyticsLoading`, `isStatusUpdating`, `isExporting`).
|
||||
- **Async operations**: All store actions with proper error handling and state clearing.
|
||||
- **Retry logic**: `withRetry` helper auto-retries read operations once on 5xx with exponential backoff.
|
||||
|
||||
### User Feedback
|
||||
|
||||
All user-facing feedback uses `showToastNotification` from `utils/toastNotifications.ts`:
|
||||
|
||||
- Success toasts on completed actions.
|
||||
- Error toasts on failed API calls (with error message extraction).
|
||||
- Warning toasts on partial failures (bulk operations).
|
||||
- Loading states on buttons (`isStatusUpdating`, `isExporting`).
|
||||
|
||||
### Analytics Loading
|
||||
|
||||
Analytics data loading uses an inline `useEffect` with a cancel flag to prevent stale closure issues:
|
||||
|
||||
```typescript
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
const loadAnalytics = async () => {
|
||||
if (!cancelled) { /* set state */ }
|
||||
};
|
||||
loadAnalytics();
|
||||
return () => { cancelled = true; };
|
||||
}, [analyticsDays]);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*This concludes the Backlink Outreach documentation. Start with the [Overview](overview.md) or [Workflow Guide](workflow-guide.md).*
|
||||
163
docs-site/docs/features/backlink-outreach/outreach-operations.md
Normal file
163
docs-site/docs/features/backlink-outreach/outreach-operations.md
Normal file
@@ -0,0 +1,163 @@
|
||||
# Outreach Operations
|
||||
|
||||
Outreach operations handle the sending pipeline: policy validation, SMTP delivery, idempotency, suppression, and audit logging.
|
||||
|
||||
## Send Pipeline
|
||||
|
||||
Every outbound email goes through this pipeline:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Send Request] --> B[Authenticate User]
|
||||
B --> C[Resolve Lead Email from DB]
|
||||
C --> D[Policy Validation]
|
||||
D -->|Approved| E[Create Outreach Attempt Record]
|
||||
D -->|Blocked| F[Record Audit Log + Return 403]
|
||||
E --> G[Send via SMTP with TLS]
|
||||
G -->|Success| H[Increment Counters]
|
||||
G -->|Success| I[Mark Idempotency Key]
|
||||
G -->|Success| J[Update Lead Status to Contacted]
|
||||
G -->|Failure| K[Return 500 with Generic Error]
|
||||
H --> L[Return 200 with Attempt Details]
|
||||
I --> L
|
||||
J --> L
|
||||
|
||||
style D fill:#fff3e0
|
||||
style G fill:#e3f2fd
|
||||
style F fill:#ffebee
|
||||
```
|
||||
|
||||
!!! warning "Counter timing"
|
||||
Counters and idempotency keys are marked **only after successful SMTP delivery**, never before. This prevents false cap consumption on failed sends.
|
||||
|
||||
## Policy Validation
|
||||
|
||||
Before every send, the system validates:
|
||||
|
||||
| Check | Rule | On Failure |
|
||||
|---|---|---|
|
||||
| **Daily user cap** | Max 100 emails/user/day | Block + audit |
|
||||
| **Daily domain cap** | Max 20 emails/domain/day | Block + audit |
|
||||
| **Suppression list** | Recipient not suppressed | Block + audit |
|
||||
| **Idempotency** | No duplicate `(sender, recipient, subject)` in 24h | Block + audit |
|
||||
| **Legal basis** | EU domains → "consent", others → "legitimate_interest" | Auto-assign |
|
||||
|
||||
**API:** `POST /api/v1/backlink-outreach/policy/validate`
|
||||
|
||||
```json
|
||||
{
|
||||
"recipient_email": "editor@example.com",
|
||||
"sender_email": "outreach@yourdomain.com",
|
||||
"subject": "Guest Post: AI Marketing Trends"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"allowed": true,
|
||||
"reason": "All checks passed",
|
||||
"legal_basis": "legitimate_interest",
|
||||
"daily_user_count": 23,
|
||||
"daily_user_limit": 100,
|
||||
"daily_domain_count": 5,
|
||||
"daily_domain_limit": 20,
|
||||
"region": "US"
|
||||
}
|
||||
```
|
||||
|
||||
### Region-Aware Legal Basis
|
||||
|
||||
The system infers the recipient's region from their email domain's TLD:
|
||||
|
||||
| TLDs | Region | Legal Basis |
|
||||
|---|---|---|
|
||||
| `.de`, `.fr`, `.it`, `.es`, `.nl`, `.pl`, `.se`, `.at`, `.be`, `.ch`, `.pt`, `.ie`, `.dk`, `.fi`, `.no`, `.cz`, `.gr`, `.hu`, `.ro`, `.bg`, `.hr`, `.sk`, `.si`, `.lt`, `.lv`, `.ee` | EU | `consent` |
|
||||
| `.co.uk`, `.uk` | UK | `consent` |
|
||||
| `.ca` | CA | `consent` |
|
||||
| `.com.au`, `.co.nz` | AU/NZ | `consent` |
|
||||
| All others | — | `legitimate_interest` |
|
||||
|
||||
!!! note "GDPR compliance"
|
||||
EU, UK, CA, and AU domain leads always use `consent` as the legal basis. This means you should have obtained some form of consent before reaching out. For other regions, `legitimate_interest` is applied automatically.
|
||||
|
||||
## Suppression List
|
||||
|
||||
Recipients on the suppression list are blocked from receiving emails.
|
||||
|
||||
### Adding to Suppression
|
||||
|
||||
**API:** `POST /api/v1/backlink-outreach/suppression`
|
||||
|
||||
```json
|
||||
{
|
||||
"email": "unsubscribed@example.com",
|
||||
"reason": "User requested unsubscribe"
|
||||
}
|
||||
```
|
||||
|
||||
### Listing Suppressed Recipients
|
||||
|
||||
**API:** `GET /api/v1/backlink-outreach/suppression`
|
||||
|
||||
### Auto-Suppression
|
||||
|
||||
Recipients are automatically added to the suppression list when:
|
||||
- They reply with "not interested" language.
|
||||
- They explicitly request to be removed.
|
||||
- An email to their address hard-bounces.
|
||||
|
||||
## Idempotency
|
||||
|
||||
The system prevents duplicate sends using idempotency keys derived from `(sender_email, recipient_email, subject)`.
|
||||
|
||||
- Keys are valid for 24 hours.
|
||||
- After successful SMTP delivery, the key is marked as used.
|
||||
- Attempting to send the same `(sender, recipient, subject)` within 24h returns a policy block.
|
||||
|
||||
## SMTP Configuration
|
||||
|
||||
Emails are sent via SMTP with mandatory TLS:
|
||||
|
||||
| Setting | Env Var | Default |
|
||||
|---|---|---|
|
||||
| SMTP host | `SMTP_HOST` | — (required) |
|
||||
| SMTP port | `SMTP_PORT` | `587` |
|
||||
| SMTP username | `SMTP_USER` | — (required) |
|
||||
| SMTP password | `SMTP_PASS` | — (required) |
|
||||
| TLS verification | `SMTP_VERIFY_TLS` | `true` |
|
||||
| Send timeout | `SMTP_SEND_TIMEOUT` | `30` seconds |
|
||||
| From email | `SMTP_FROM_EMAIL` | — (required) |
|
||||
|
||||
!!! warning "TLS certificate verification"
|
||||
By default, `SMTP_VERIFY_TLS=true` validates the SMTP server's TLS certificate. Set to `false` only for local development with self-signed certs. **Never disable in production.**
|
||||
|
||||
### SMTP Connection Flow
|
||||
|
||||
1. Connect to SMTP host on configured port.
|
||||
2. Send `EHLO` greeting.
|
||||
3. Upgrade to TLS with `STARTTLS`.
|
||||
4. Send `EHLO` again (required by RFC 3207 after STARTTLS).
|
||||
5. Authenticate with username/password.
|
||||
6. Send the email with a configurable timeout.
|
||||
7. Quit the connection cleanly.
|
||||
|
||||
## Audit Logging
|
||||
|
||||
Every policy check is recorded in the audit log:
|
||||
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| `user_id` | Authenticated user who initiated the send. |
|
||||
| `lead_email` | Intended recipient. |
|
||||
| `sender_email` | Sending address. |
|
||||
| `subject` | Email subject line. |
|
||||
| `policy_result` | `approved` or `blocked`. |
|
||||
| `reason` | Human-readable explanation. |
|
||||
| `legal_basis` | `consent` or `legitimate_interest`. |
|
||||
| `timestamp` | When the check occurred. |
|
||||
|
||||
---
|
||||
|
||||
*Next: [Reply Inbox](reply-inbox.md) — IMAP monitoring and auto-classification.*
|
||||
104
docs-site/docs/features/backlink-outreach/overview.md
Normal file
104
docs-site/docs/features/backlink-outreach/overview.md
Normal file
@@ -0,0 +1,104 @@
|
||||
# Backlink Outreach Overview
|
||||
|
||||
Backlink Outreach is an AI-powered guest post outreach platform that takes you from opportunity discovery to published backlink — with smart email composition, policy-safe sending, IMAP reply monitoring, and full campaign analytics.
|
||||
|
||||
## What you do in the product
|
||||
|
||||
1. **Create a campaign** to group leads, emails, and analytics together.
|
||||
2. **Discover opportunities** using AI-powered search across Exa neural search and DuckDuckGo.
|
||||
3. **Compose outreach emails** with AI generation, personalization, and subject-line suggestions.
|
||||
4. **Send outreach** through SMTP with built-in policy validation, suppression checks, and idempotency.
|
||||
5. **Monitor replies** via IMAP with auto-classification (interested, not interested, out of office).
|
||||
6. **Track analytics** — send volume trends, conversion funnels, reply classification breakdown, and CSV exports.
|
||||
|
||||
## What you see in the UI
|
||||
|
||||
- Campaign list with status and lead counts.
|
||||
- Discovery results with quality/confidence scores and email detection badges.
|
||||
- AI email composer with tone selector, template library, and live preview.
|
||||
- Lead cards with status lifecycle buttons (discovered → contacted → replied → placed).
|
||||
- Reply inbox with auto-classification tags.
|
||||
- Analytics tab with line charts, bar charts, and export controls.
|
||||
- Toast notifications for every action outcome (success or failure).
|
||||
|
||||
## Feature status matrix
|
||||
|
||||
| Capability | Status | Notes |
|
||||
|---|---|---|
|
||||
| Campaign CRUD | **Implemented** | Create, list, get detail with leads. |
|
||||
| AI-powered deep discovery | **Implemented** | Exa neural search + DuckDuckGo with full-page scraping and email extraction. |
|
||||
| Lead management | **Implemented** | Add, bulk-add, update status, bulk status update. |
|
||||
| AI email generation | **Implemented** | Topic-based generation, personalization, subject-line suggestions, follow-up drafts. |
|
||||
| Template CRUD | **Implemented** | Create, list, get, delete email templates with `{placeholder}` variable substitution. |
|
||||
| SMTP email sending | **Implemented** | TLS with certificate verification, EHLO, configurable timeout. |
|
||||
| Policy validation | **Implemented** | Daily caps, domain caps, suppression list, idempotency, region-aware legal basis (EU → consent). |
|
||||
| IMAP reply monitoring | **Implemented** | Configurable fetch limit, auto-classification, deduplication. |
|
||||
| Follow-up scheduling | **Implemented** | Schedule and track follow-up emails. |
|
||||
| Campaign analytics | **Implemented** | Volume trends, conversion funnel, reply classification, response/placement rates. |
|
||||
| CSV export | **Implemented** | Leads, attempts, replies — with formula injection sanitization. |
|
||||
| Audit logging | **Implemented** | Every policy check is recorded with reasons and outcome. |
|
||||
| Suppression management | **Implemented** | Add and list suppressed recipients. |
|
||||
| Clerk auth on all endpoints | **Implemented** | 18 protected endpoints + user-scoped data isolation. |
|
||||
| Reporting snapshot | **Implemented** | Cross-campaign send volume, reply count, placement conversion. |
|
||||
|
||||
## How It Works
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[Create Campaign] --> B[Discover Opportunities]
|
||||
B --> C[Save Leads]
|
||||
C --> D[Compose Email]
|
||||
D --> E[Policy Validate]
|
||||
E -->|Approved| F[Send via SMTP]
|
||||
E -->|Blocked| G[Audit Log]
|
||||
F --> H[Monitor Replies]
|
||||
H --> I[Auto-Classify]
|
||||
I --> J[Track Analytics]
|
||||
|
||||
style A fill:#e3f2fd
|
||||
style B fill:#e8f5e8
|
||||
style F fill:#fff3e0
|
||||
style I fill:#fce4ec
|
||||
style J fill:#f3e5f5
|
||||
```
|
||||
|
||||
## Who Benefits Most
|
||||
|
||||
### For SEO Professionals
|
||||
- **Scalable outreach**: Send up to 100 emails/day per user with domain-level caps.
|
||||
- **Policy compliance**: Built-in GDPR-aware legal basis, suppression, and audit trail.
|
||||
- **Performance tracking**: Real-time analytics with conversion funnel and reply breakdown.
|
||||
|
||||
### For Content Marketers
|
||||
- **AI email composer**: Generate personalized outreach emails in seconds, not hours.
|
||||
- **Template library**: Save and reuse winning email templates across campaigns.
|
||||
- **Reply triage**: Auto-classified replies let you focus on interested leads first.
|
||||
|
||||
### For Agencies
|
||||
- **Multi-campaign management**: Organize outreach by client or vertical.
|
||||
- **CSV exports**: Download leads, attempts, and replies for client reporting.
|
||||
- **Audit trail**: Every send decision is logged for compliance and accountability.
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. **[Workflow Guide](workflow-guide.md)** - Step-by-step walkthrough from campaign creation to analytics.
|
||||
2. **[Campaign Management](campaign-management.md)** - Creating and organizing campaigns.
|
||||
3. **[Discovery](discovery.md)** - AI-powered opportunity search.
|
||||
4. **[Email Composer](email-composer.md)** - AI email generation and personalization.
|
||||
5. **[Outreach Operations](outreach-operations.md)** - Sending, policy, suppression.
|
||||
6. **[Reply Inbox](reply-inbox.md)** - IMAP monitoring and classification.
|
||||
7. **[Analytics](analytics.md)** - Charts, funnels, and exports.
|
||||
8. **[API Reference](api-reference.md)** - Full endpoint documentation.
|
||||
9. **[Configuration](configuration.md)** - Environment variables and deployment.
|
||||
10. **[Implementation Overview](implementation-overview.md)** - Architecture and internals.
|
||||
|
||||
## Related Features
|
||||
|
||||
- **[SEO Dashboard](../seo-dashboard/overview.md)** - Comprehensive SEO tools and GSC integration.
|
||||
- **[Blog Writer](../blog-writer/overview.md)** - Create content to earn backlinks organically.
|
||||
- **[Content Strategy](../content-strategy/overview.md)** - Strategic planning for link-building campaigns.
|
||||
- **[Subscription](../subscription/overview.md)** - Plan limits and billing.
|
||||
|
||||
---
|
||||
|
||||
*Ready to start building backlinks? Check out the [Workflow Guide](workflow-guide.md) to get started!*
|
||||
109
docs-site/docs/features/backlink-outreach/reply-inbox.md
Normal file
109
docs-site/docs/features/backlink-outreach/reply-inbox.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# Reply Inbox
|
||||
|
||||
The reply inbox monitors your outreach mailbox via IMAP, automatically classifies replies, and deduplicates incoming messages.
|
||||
|
||||
## How It Works
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Poll IMAP Inbox] --> B[Search for New Messages]
|
||||
B --> C[Fetch Message Headers + Body]
|
||||
C --> D{Already Processed?}
|
||||
D -->|Yes| E[Skip Duplicate]
|
||||
D -->|No| F[Find Matching Attempt]
|
||||
F --> G[Classify Reply]
|
||||
G --> H[Store Reply Record]
|
||||
H --> I[Update Lead Status if Interested]
|
||||
|
||||
style A fill:#e3f2fd
|
||||
style G fill:#e8f5e8
|
||||
style E fill:#ffebee
|
||||
```
|
||||
|
||||
## IMAP Configuration
|
||||
|
||||
| Setting | Env Var | Default |
|
||||
|---|---|---|
|
||||
| IMAP host | `IMAP_HOST` | — (required) |
|
||||
| IMAP port | `IMAP_PORT` | `993` |
|
||||
| IMAP username | `IMAP_USER` | — (required) |
|
||||
| IMAP password | `IMAP_PASS` | — (required) |
|
||||
| Fetch limit | `IMAP_FETCH_LIMIT` | `50` |
|
||||
|
||||
!!! tip "Fetch limit"
|
||||
`IMAP_FETCH_LIMIT` controls how many messages are processed per poll cycle. Increase for high-volume mailboxes, decrease to reduce IMAP load. Default is 50.
|
||||
|
||||
## Polling for Replies
|
||||
|
||||
**API:** `POST /api/v1/backlink-outreach/replies/poll`
|
||||
|
||||
The reply monitor:
|
||||
|
||||
1. Connects to IMAP over SSL.
|
||||
2. Sanitizes the `sent_from_email` before searching (prevents IMAP injection).
|
||||
3. Searches for messages sent to your outreach address.
|
||||
4. Fetches up to `IMAP_FETCH_LIMIT` recent messages.
|
||||
5. For each message, checks if it's already been processed (deduplication).
|
||||
6. Matches the reply to an existing outreach attempt by sender email.
|
||||
7. Classifies the reply and stores it.
|
||||
|
||||
### Reply Matching
|
||||
|
||||
Replies are matched to outreach attempts using the `from_email` field:
|
||||
|
||||
- The system looks up `find_attempt_by_from_email(from_email)` to find the most recent outreach attempt sent to that email address.
|
||||
- If no match is found, the reply is still stored but not linked to an attempt.
|
||||
|
||||
### Deduplication
|
||||
|
||||
The system checks `reply_exists(from_email, subject)` before storing a new reply. This prevents duplicate entries when the same message appears in multiple IMAP folders or is fetched in overlapping poll cycles.
|
||||
|
||||
## Auto-Classification
|
||||
|
||||
Replies are automatically classified based on content analysis:
|
||||
|
||||
| Classification | Signals |
|
||||
|---|---|
|
||||
| **Interested** | "sounds good", "tell me more", "interested", "let's do it", "I'd love to" |
|
||||
| **Not interested** | "not interested", "no thanks", "unsubscribe", "remove me", "stop sending" |
|
||||
| **Out of office** | "out of office", "auto-reply", "automated response", "on vacation" |
|
||||
| **Replied** | General reply that doesn't match other categories |
|
||||
|
||||
!!! note "Manual override"
|
||||
Auto-classification is a best-effort guess. You can manually reclassify any reply in the UI by clicking the classification tag and selecting a different one.
|
||||
|
||||
### Auto-Suppression on "Not Interested"
|
||||
|
||||
When a reply is classified as "not interested", the sender's email is **automatically added to the suppression list** to prevent future outreach.
|
||||
|
||||
## Reply Inbox UI
|
||||
|
||||
The inbox shows:
|
||||
|
||||
- **From**: Sender name and email.
|
||||
- **Subject**: Email subject line.
|
||||
- **Classification tag**: Color-coded auto-classification badge.
|
||||
- **Date**: When the reply was received.
|
||||
- **Linked attempt**: The outreach attempt this reply matches (if any).
|
||||
- **Lead status**: Current status of the associated lead.
|
||||
|
||||
### Actions
|
||||
|
||||
| Action | Description |
|
||||
|---|---|
|
||||
| **View** | Read the full reply body. |
|
||||
| **Reclassify** | Change the auto-classification. |
|
||||
| **Update lead status** | Move the lead to "replied" or "placed". |
|
||||
| **Compose follow-up** | Open the email composer pre-filled with a follow-up draft. |
|
||||
|
||||
## Monitoring Best Practices
|
||||
|
||||
1. **Poll regularly**: Set up a scheduled job to call the poll endpoint every 5-15 minutes.
|
||||
2. **Review unclassified**: Check "Replied" (generic) classifications and manually tag them.
|
||||
3. **Act on interested leads quickly**: Respond within 24 hours for best conversion.
|
||||
4. **Check out-of-office dates**: Schedule follow-ups for after the return date.
|
||||
5. **Review suppression entries**: Periodically audit the suppression list for accidental additions.
|
||||
|
||||
---
|
||||
|
||||
*Next: [Analytics](analytics.md) — campaign performance tracking and exports.*
|
||||
120
docs-site/docs/features/backlink-outreach/workflow-guide.md
Normal file
120
docs-site/docs/features/backlink-outreach/workflow-guide.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# Backlink Outreach Workflow Guide
|
||||
|
||||
This guide walks through the complete Backlink Outreach lifecycle from campaign creation to analytics review.
|
||||
|
||||
## 1) Create a Campaign
|
||||
|
||||
Campaigns group your leads, outreach attempts, replies, and analytics together. Every action in the system belongs to a campaign.
|
||||
|
||||
!!! tip "Best practice"
|
||||
Create one campaign per target vertical or client. For example: "SaaS Growth Blogs Q3" or "Fitness Influencer Outreach".
|
||||
|
||||
**What to validate before continuing:**
|
||||
- Campaign name is descriptive enough to distinguish from others.
|
||||
- You have a clear keyword or niche for discovery.
|
||||
|
||||
## 2) Discover Opportunities
|
||||
|
||||
Use AI-powered discovery to find websites that accept guest posts in your niche.
|
||||
|
||||
!!! note "How discovery works"
|
||||
The system combines **Exa neural search** (semantic understanding) with **DuckDuckGo** (broad coverage), scrapes full pages, extracts contact emails, and scores each opportunity for quality and guest-post likelihood.
|
||||
|
||||
**Recommended sequence:**
|
||||
1. Enter a keyword (e.g., "AI marketing", "SaaS growth").
|
||||
2. Click **Discover** to search across multiple query patterns ("write for us", "guest contributor", etc.).
|
||||
3. Review results — check quality score, confidence score, and email detection badges.
|
||||
4. Select a campaign and click **Save to Campaign** to persist leads.
|
||||
|
||||
**What to look for:**
|
||||
- Quality score > 60% — the site is relevant to your keyword.
|
||||
- Confidence score > 50% — the site likely accepts guest posts.
|
||||
- "Has guidelines" badge — the site has a dedicated guest post page.
|
||||
- "Email found" badge — a contact email was extracted.
|
||||
|
||||
## 3) Compose Outreach Emails
|
||||
|
||||
Use the AI email composer to craft personalized outreach messages.
|
||||
|
||||
!!! note "AI generation options"
|
||||
- **Generate**: Create an email from a topic, tone, and optional template.
|
||||
- **Personalize**: Tailor an email to a specific lead (name, site, content topic).
|
||||
- **Subject Lines**: Get 5-10 AI-suggested subject line variants.
|
||||
- **Follow-up**: Generate a polite follow-up referencing the original email.
|
||||
|
||||
**Recommended sequence:**
|
||||
1. Choose a template or start fresh.
|
||||
2. Enter your topic and target site (optional).
|
||||
3. Select a tone (Professional, Friendly, Casual, Formal).
|
||||
4. Click **Generate with AI** to create a subject + body.
|
||||
5. Optionally click **Suggest** for subject line variants.
|
||||
6. Use **Personalize** to tailor the email to a specific lead.
|
||||
7. Preview the email in the live preview pane.
|
||||
|
||||
## 4) Send Outreach
|
||||
|
||||
Once your email is composed, navigate to the Leads tab to send outreach.
|
||||
|
||||
!!! warning "Policy validation"
|
||||
Every send is validated against your daily caps, suppression list, and GDPR rules. EU-domain leads automatically use "consent" as legal basis; others use "legitimate_interest".
|
||||
|
||||
**What happens when you send:**
|
||||
1. Policy is validated (caps, suppression, idempotency, legal basis).
|
||||
2. An outreach attempt is recorded in the database.
|
||||
3. If approved, the email is sent via SMTP with TLS.
|
||||
4. Send counters are incremented **only after successful delivery**.
|
||||
5. Idempotency key is marked to prevent duplicate sends.
|
||||
6. Lead status is updated to "contacted".
|
||||
|
||||
**Daily limits:**
|
||||
- 100 emails per user per day.
|
||||
- 20 emails per domain per day.
|
||||
|
||||
## 5) Monitor Replies
|
||||
|
||||
After sending outreach, monitor replies through the IMAP-powered inbox.
|
||||
|
||||
!!! note "Auto-classification"
|
||||
Replies are automatically classified as:
|
||||
- **Interested** — positive language detected ("sounds good", "tell me more").
|
||||
- **Not interested** — negative language ("not interested", "unsubscribe").
|
||||
- **Out of office** — auto-responder detected.
|
||||
- **Replied** — general reply without strong signals.
|
||||
|
||||
**What to do with classified replies:**
|
||||
- **Interested**: Move the lead to "replied" status, then "placed" after publication.
|
||||
- **Not interested**: Mark as "bounced" or leave as-is. The sender is auto-added to suppression.
|
||||
- **Out of office**: Schedule a follow-up for after their return date.
|
||||
- **Replied**: Read and manually classify, then update lead status.
|
||||
|
||||
## 6) Track Analytics
|
||||
|
||||
Monitor campaign performance with built-in analytics.
|
||||
|
||||
**Key metrics:**
|
||||
- **Send Volume**: Daily email send trend over time.
|
||||
- **Response Rate**: Percentage of sent emails that received a reply.
|
||||
- **Placement Rate**: Percentage of leads that resulted in a published post.
|
||||
- **Conversion Funnel**: Lead count by status stage (discovered → contacted → replied → placed).
|
||||
- **Reply Classification**: Breakdown of reply types.
|
||||
|
||||
**Export options:**
|
||||
- Export Leads as CSV for CRM import.
|
||||
- Export Attempts for audit trails.
|
||||
- Export Replies for analysis in spreadsheets.
|
||||
|
||||
!!! tip "CSV safety"
|
||||
All CSV exports are sanitized against formula injection — cells starting with `=`, `+`, `-`, or `@` are automatically escaped.
|
||||
|
||||
## 7) Iterate and Optimize
|
||||
|
||||
Use analytics insights to improve your outreach:
|
||||
|
||||
1. **Low response rate?** Try different subject lines or tones.
|
||||
2. **High bounce rate?** Improve lead quality filters during discovery.
|
||||
3. **Low placement rate?** Refine your pitch personalization.
|
||||
4. **Many "not interested"?** Adjust your target niche or messaging.
|
||||
|
||||
---
|
||||
|
||||
*Now you know the full workflow! Dive deeper with [Campaign Management](campaign-management.md) or [Discovery](discovery.md).*
|
||||
@@ -214,6 +214,18 @@ nav:
|
||||
- Troubleshooting: user-journeys/enterprise/troubleshooting.md
|
||||
- Advanced Security: user-journeys/enterprise/advanced-security.md
|
||||
- Features:
|
||||
- Backlink Outreach:
|
||||
- Overview: features/backlink-outreach/overview.md
|
||||
- Workflow Guide: features/backlink-outreach/workflow-guide.md
|
||||
- Campaign Management: features/backlink-outreach/campaign-management.md
|
||||
- Discovery: features/backlink-outreach/discovery.md
|
||||
- Email Composer: features/backlink-outreach/email-composer.md
|
||||
- Outreach Operations: features/backlink-outreach/outreach-operations.md
|
||||
- Reply Inbox: features/backlink-outreach/reply-inbox.md
|
||||
- Analytics: features/backlink-outreach/analytics.md
|
||||
- API Reference: features/backlink-outreach/api-reference.md
|
||||
- Configuration: features/backlink-outreach/configuration.md
|
||||
- Implementation Overview: features/backlink-outreach/implementation-overview.md
|
||||
- Blog Writer:
|
||||
- Overview: features/blog-writer/overview.md
|
||||
- Implementation Overview: features/blog-writer/implementation-overview.md
|
||||
@@ -235,6 +247,7 @@ nav:
|
||||
- GSC Integration: features/seo-dashboard/gsc-integration.md
|
||||
- Metadata Generation: features/seo-dashboard/metadata.md
|
||||
- Design Document: features/seo-dashboard/design-document.md
|
||||
- Phase 2A Implementation: ../SEO/PHASE2A_IMPLEMENTATION.md
|
||||
- Content Strategy:
|
||||
- Overview: features/content-strategy/overview.md
|
||||
- Persona Development: features/content-strategy/personas.md
|
||||
|
||||
Reference in New Issue
Block a user