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:
ajaysi
2026-05-25 17:07:35 +05:30
parent 090d69761f
commit 9b3bec698b
99 changed files with 15892 additions and 1278 deletions

View 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.*

View 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) |

View 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.*

View 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.*

View 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.*

View 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.*

View File

@@ -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).*

View 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.*

View 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!*

View 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.*

View 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).*