happyDeliver/api/openapi.yaml

1184 lines
34 KiB
YAML

openapi: 3.0.3
info:
title: happyDeliver API
description: Email Deliverability Testing Platform API
version: 0.1.0
contact:
name: happyDomain team
url: https://github.com/happyDomain/happydeliver
email: contact+api@happydomain.org
license:
name: GNU Affero General Public License v3.0 or later
url: https://spdx.org/licenses/AGPL-3.0-or-later.html
servers:
- url: http://localhost:8080/api
description: Local development server
- url: https://api.example.com/api
description: Production server
tags:
- name: tests
description: Test management operations
- name: reports
description: Report retrieval operations
- name: health
description: Service health and status
paths:
/test:
post:
tags:
- tests
summary: Create a new deliverability test
description: Generates a unique test email address for sending test emails. No database record is created until an email is received.
operationId: createTest
responses:
'201':
description: Test email address generated successfully
content:
application/json:
schema:
$ref: '#/components/schemas/TestResponse'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/test/{id}:
get:
tags:
- tests
summary: Get test status
description: Check if a report exists for the given test ID (base32-encoded). Returns pending if no report exists, analyzed if a report is available.
operationId: getTest
parameters:
- name: id
in: path
required: true
schema:
type: string
pattern: '^[a-z0-9-]+$'
description: Base32-encoded test ID (with hyphens)
responses:
'200':
description: Test status retrieved successfully
content:
application/json:
schema:
$ref: '#/components/schemas/Test'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/report/{id}:
get:
tags:
- reports
summary: Get detailed report
description: Retrieve comprehensive deliverability analysis report
operationId: getReport
parameters:
- name: id
in: path
required: true
schema:
type: string
pattern: '^[a-z0-9-]+$'
description: Base32-encoded test ID (with hyphens)
responses:
'200':
description: Report retrieved successfully
content:
application/json:
schema:
$ref: '#/components/schemas/Report'
'404':
description: Report not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/report/{id}/raw:
get:
tags:
- reports
summary: Get raw annotated email
description: Retrieve the original email with headers added by filters
operationId: getRawEmail
parameters:
- name: id
in: path
required: true
schema:
type: string
pattern: '^[a-z0-9-]+$'
description: Base32-encoded test ID (with hyphens)
responses:
'200':
description: Raw email retrieved successfully
content:
text/plain:
schema:
type: string
'404':
description: Email not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/report/{id}/reanalyze:
post:
tags:
- reports
summary: Reanalyze email and regenerate report
description: Re-run the analysis on the stored raw email to regenerate the report with the latest analyzer version. This is useful after analyzer improvements or bug fixes.
operationId: reanalyzeReport
parameters:
- name: id
in: path
required: true
schema:
type: string
pattern: '^[a-z0-9-]+$'
description: Base32-encoded test ID (with hyphens)
responses:
'200':
description: Report regenerated successfully
content:
application/json:
schema:
$ref: '#/components/schemas/Report'
'404':
description: Email not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500':
description: Internal server error during reanalysis
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/domain:
post:
tags:
- tests
summary: Test a domain's email configuration
description: Analyzes DNS records (MX, SPF, DMARC, BIMI) for a domain without requiring an actual email to be sent. Returns results immediately.
operationId: testDomain
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/DomainTestRequest'
responses:
'200':
description: Domain test completed successfully
content:
application/json:
schema:
$ref: '#/components/schemas/DomainTestResponse'
'400':
description: Invalid request
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/status:
get:
tags:
- health
summary: Service health check
description: Get service health status and component versions
operationId: getStatus
responses:
'200':
description: Service status
content:
application/json:
schema:
$ref: '#/components/schemas/Status'
components:
schemas:
Test:
type: object
required:
- id
- email
- status
properties:
id:
type: string
pattern: '^[a-z0-9-]+$'
description: Unique test identifier (base32-encoded with hyphens)
example: "krfwg4z-amrqw4z-zmorsw2-djmfzgk-3a"
email:
type: string
format: email
description: Unique test email address
example: "test-krfwg4z-amrqw4z-zmorsw2-djmfzgk-3a@example.com"
status:
type: string
enum: [pending, analyzed]
description: Current test status (pending = no report yet, analyzed = report available)
example: "analyzed"
TestResponse:
type: object
required:
- id
- email
- status
properties:
id:
type: string
pattern: '^[a-z0-9-]+$'
description: Unique test identifier (base32-encoded with hyphens)
example: "krfwg4z-amrqw4z-zmorsw2-djmfzgk-3a"
email:
type: string
format: email
example: "test-krfwg4z-amrqw4z-zmorsw2-djmfzgk-3a@example.com"
status:
type: string
enum: [pending]
example: "pending"
message:
type: string
example: "Send your test email to the address above"
Report:
type: object
required:
- id
- test_id
- score
- grade
- created_at
properties:
id:
type: string
pattern: '^[a-z0-9-]+$'
description: Report identifier (base32-encoded with hyphens)
test_id:
type: string
pattern: '^[a-z0-9-]+$'
description: Associated test ID (base32-encoded with hyphens)
score:
type: integer
minimum: 0
maximum: 100
description: Overall deliverability score as percentage (0-100)
example: 85
grade:
type: string
enum: [A+, A, B, C, D, E, F]
description: Letter grade representation of the score (A+ is best, F is worst)
example: "A"
summary:
$ref: '#/components/schemas/ScoreSummary'
authentication:
$ref: '#/components/schemas/AuthenticationResults'
spamassassin:
$ref: '#/components/schemas/SpamAssassinResult'
dns_results:
$ref: '#/components/schemas/DNSResults'
blacklists:
type: object
additionalProperties:
type: array
items:
$ref: '#/components/schemas/BlacklistCheck'
description: Map of IP addresses to their blacklist check results (array of checks per IP)
example:
"192.0.2.1":
- rbl: "zen.spamhaus.org"
listed: false
- rbl: "bl.spamcop.net"
listed: false
content_analysis:
$ref: '#/components/schemas/ContentAnalysis'
header_analysis:
$ref: '#/components/schemas/HeaderAnalysis'
raw_headers:
type: string
description: Raw email headers
created_at:
type: string
format: date-time
ScoreSummary:
type: object
required:
- dns_score
- dns_grade
- authentication_score
- authentication_grade
- spam_score
- spam_grade
- blacklist_score
- blacklist_grade
- header_score
- header_grade
- content_score
- content_grade
properties:
dns_score:
type: integer
minimum: 0
maximum: 100
description: DNS records score (in percentage)
example: 42
dns_grade:
type: string
enum: [A+, A, B, C, D, E, F]
description: Letter grade representation of the score (A+ is best, F is worst)
example: "A"
authentication_score:
type: integer
minimum: 0
maximum: 100
description: SPF/DKIM/DMARC score (in percentage)
example: 28
authentication_grade:
type: string
enum: [A+, A, B, C, D, E, F]
description: Letter grade representation of the score (A+ is best, F is worst)
example: "A"
spam_score:
type: integer
minimum: 0
maximum: 100
description: SpamAssassin score (in percentage)
example: 15
spam_grade:
type: string
enum: [A+, A, B, C, D, E, F]
description: Letter grade representation of the score (A+ is best, F is worst)
example: "A"
blacklist_score:
type: integer
minimum: 0
maximum: 100
description: Blacklist check score (in percentage)
example: 20
blacklist_grade:
type: string
enum: [A+, A, B, C, D, E, F]
description: Letter grade representation of the score (A+ is best, F is worst)
example: "A"
header_score:
type: integer
minimum: 0
maximum: 100
description: Header quality score (in percentage)
example: 9
header_grade:
type: string
enum: [A+, A, B, C, D, E, F]
description: Letter grade representation of the score (A+ is best, F is worst)
example: "A"
content_score:
type: integer
minimum: 0
maximum: 100
description: Content quality score (in percentage)
example: 18
content_grade:
type: string
enum: [A+, A, B, C, D, E, F]
description: Letter grade representation of the score (A+ is best, F is worst)
example: "A"
ContentAnalysis:
type: object
properties:
has_html:
type: boolean
description: Whether email contains HTML part
example: true
has_plaintext:
type: boolean
description: Whether email contains plaintext part
example: true
html_issues:
type: array
items:
$ref: '#/components/schemas/ContentIssue'
description: Issues found in HTML content
links:
type: array
items:
$ref: '#/components/schemas/LinkCheck'
description: Analysis of links found in the email
images:
type: array
items:
$ref: '#/components/schemas/ImageCheck'
description: Analysis of images in the email
text_to_image_ratio:
type: number
format: float
description: Ratio of text to images (higher is better)
example: 0.75
has_unsubscribe_link:
type: boolean
description: Whether email contains an unsubscribe link
example: true
unsubscribe_methods:
type: array
items:
type: string
enum: [link, mailto, list-unsubscribe-header, one-click]
description: Available unsubscribe methods
example: ["link", "list-unsubscribe-header"]
ContentIssue:
type: object
required:
- type
- severity
- message
properties:
type:
type: string
enum: [broken_html, missing_alt, excessive_images, obfuscated_url, suspicious_link, dangerous_html]
description: Type of content issue
example: "missing_alt"
severity:
type: string
enum: [critical, high, medium, low, info]
description: Issue severity
example: "medium"
message:
type: string
description: Human-readable description
example: "3 images are missing alt attributes"
location:
type: string
description: Where the issue was found
example: "HTML body line 42"
advice:
type: string
description: How to fix this issue
example: "Add descriptive alt text to all images for better accessibility and deliverability"
LinkCheck:
type: object
required:
- url
- status
properties:
url:
type: string
format: uri
description: The URL found in the email
example: "https://example.com/page"
status:
type: string
enum: [valid, broken, suspicious, redirected, timeout]
description: Link validation status
example: "valid"
http_code:
type: integer
description: HTTP status code received
example: 200
redirect_chain:
type: array
items:
type: string
description: URLs in the redirect chain, if any
example: ["https://example.com", "https://www.example.com"]
is_shortened:
type: boolean
description: Whether this is a URL shortener
example: false
ImageCheck:
type: object
required:
- has_alt
properties:
src:
type: string
description: Image source URL or path
example: "https://example.com/logo.png"
has_alt:
type: boolean
description: Whether image has alt attribute
example: true
alt_text:
type: string
description: Alt text content
example: "Company Logo"
is_tracking_pixel:
type: boolean
description: Whether this appears to be a tracking pixel (1x1 image)
example: false
HeaderAnalysis:
type: object
properties:
has_mime_structure:
type: boolean
description: Whether body has a MIME structure
example: true
headers:
type: object
additionalProperties:
$ref: '#/components/schemas/HeaderCheck'
description: Map of header names to their check results (e.g., "from", "to", "dkim-signature")
example:
from:
present: true
value: "sender@example.com"
valid: true
importance: "required"
date:
present: true
value: "Mon, 1 Jan 2024 12:00:00 +0000"
valid: true
importance: "required"
received_chain:
type: array
items:
$ref: '#/components/schemas/ReceivedHop'
description: Chain of Received headers showing email path
domain_alignment:
$ref: '#/components/schemas/DomainAlignment'
issues:
type: array
items:
$ref: '#/components/schemas/HeaderIssue'
description: Issues found in headers
HeaderCheck:
type: object
required:
- present
properties:
present:
type: boolean
description: Whether the header is present
example: true
value:
type: string
description: Header value
example: "sender@example.com"
valid:
type: boolean
description: Whether the value is valid/well-formed
example: true
importance:
type: string
enum: [required, recommended, optional, newsletter]
description: How important this header is for deliverability
example: "required"
issues:
type: array
items:
type: string
description: Any issues with this header
example: ["Invalid date format"]
ReceivedHop:
type: object
properties:
from:
type: string
description: Sending server hostname
example: "mail.example.com"
by:
type: string
description: Receiving server hostname
example: "mx.receiver.com"
with:
type: string
description: Protocol used
example: "ESMTPS"
id:
type: string
description: Message ID at this hop
timestamp:
type: string
format: date-time
description: When this hop occurred
ip:
type: string
description: IP address of the sending server (IPv4 or IPv6)
example: "192.0.2.1"
reverse:
type: string
description: Reverse DNS (PTR record) for the IP address
example: "mail.example.com"
DomainAlignment:
type: object
properties:
from_domain:
type: string
description: Domain from From header
example: "example.com"
from_org_domain:
type: string
description: Organizational domain extracted from From header (using Public Suffix List)
example: "example.com"
return_path_domain:
type: string
description: Domain from Return-Path header
example: "example.com"
return_path_org_domain:
type: string
description: Organizational domain extracted from Return-Path header (using Public Suffix List)
example: "example.com"
dkim_domains:
type: array
items:
type: string
description: Domains from DKIM signatures
example: ["example.com"]
aligned:
type: boolean
description: Whether all domains align (strict alignment - exact match)
example: true
relaxed_aligned:
type: boolean
description: Whether domains satisfy relaxed alignment (organizational domain match)
example: true
issues:
type: array
items:
type: string
description: Alignment issues
example: ["Return-Path domain does not match From domain"]
HeaderIssue:
type: object
required:
- header
- severity
- message
properties:
header:
type: string
description: Header name
example: "Date"
severity:
type: string
enum: [critical, high, medium, low, info]
description: Issue severity
example: "medium"
message:
type: string
description: Human-readable description
example: "Date header is in the future"
advice:
type: string
description: How to fix this issue
example: "Ensure your mail server clock is synchronized with NTP"
AuthenticationResults:
type: object
properties:
spf:
$ref: '#/components/schemas/AuthResult'
dkim:
type: array
items:
$ref: '#/components/schemas/AuthResult'
dmarc:
$ref: '#/components/schemas/AuthResult'
bimi:
$ref: '#/components/schemas/AuthResult'
arc:
$ref: '#/components/schemas/ARCResult'
iprev:
$ref: '#/components/schemas/IPRevResult'
x_google_dkim:
$ref: '#/components/schemas/AuthResult'
description: Google-specific DKIM authentication result (x-google-dkim)
x_aligned_from:
$ref: '#/components/schemas/AuthResult'
description: X-Aligned-From authentication result (checks address alignment)
AuthResult:
type: object
required:
- result
properties:
result:
type: string
enum: [pass, fail, invalid, missing, none, neutral, softfail, temperror, permerror, declined]
description: Authentication result
example: "pass"
domain:
type: string
description: Domain being authenticated
example: "example.com"
selector:
type: string
description: DKIM selector (for DKIM only)
example: "default"
details:
type: string
description: Additional details about the result
ARCResult:
type: object
required:
- result
properties:
result:
type: string
enum: [pass, fail, none]
description: Overall ARC chain validation result
example: "pass"
chain_valid:
type: boolean
description: Whether the ARC chain signatures are valid
example: true
chain_length:
type: integer
description: Number of ARC sets in the chain
example: 2
details:
type: string
description: Additional details about ARC validation
example: "ARC chain valid with 2 intermediaries"
IPRevResult:
type: object
required:
- result
properties:
result:
type: string
enum: [pass, fail, temperror, permerror]
description: IP reverse DNS lookup result
example: "pass"
ip:
type: string
description: IP address that was checked
example: "195.110.101.58"
hostname:
type: string
description: Hostname from reverse DNS lookup (PTR record)
example: "authsmtp74.register.it"
details:
type: string
description: Additional details about the IP reverse lookup
example: "smtp.remote-ip=195.110.101.58 (authsmtp74.register.it)"
SpamAssassinResult:
type: object
required:
- score
- required_score
- is_spam
- test_details
properties:
version:
type: string
description: SpamAssassin version
example: "SpamAssassin 4.0.1"
score:
type: number
format: float
description: SpamAssassin spam score
example: 2.3
required_score:
type: number
format: float
description: Threshold for spam classification
example: 5.0
is_spam:
type: boolean
description: Whether message is classified as spam
example: false
tests:
type: array
items:
type: string
description: List of triggered SpamAssassin tests
example: ["BAYES_00", "DKIM_SIGNED"]
test_details:
type: object
additionalProperties:
$ref: '#/components/schemas/SpamTestDetail'
description: Map of test names to their detailed results
example:
BAYES_00:
name: "BAYES_00"
score: -1.9
description: "Bayes spam probability is 0 to 1%"
DKIM_SIGNED:
name: "DKIM_SIGNED"
score: 0.1
description: "Message has a DKIM or DK signature, not necessarily valid"
report:
type: string
description: Full SpamAssassin report
SpamTestDetail:
type: object
required:
- name
- score
properties:
name:
type: string
description: Test name
example: "BAYES_00"
score:
type: number
format: float
description: Score contribution of this test
example: -1.9
description:
type: string
description: Human-readable description of what this test checks
example: "Bayes spam probability is 0 to 1%"
DNSResults:
type: object
required:
- from_domain
properties:
from_domain:
type: string
description: From Domain name
example: "example.com"
rp_domain:
type: string
description: Return Path Domain name
example: "example.com"
from_mx_records:
type: array
items:
$ref: '#/components/schemas/MXRecord'
description: MX records for the From domain
rp_mx_records:
type: array
items:
$ref: '#/components/schemas/MXRecord'
description: MX records for the Return-Path domain
spf_records:
type: array
items:
$ref: '#/components/schemas/SPFRecord'
description: SPF records found (includes resolved include directives)
dkim_records:
type: array
items:
$ref: '#/components/schemas/DKIMRecord'
description: DKIM records found
dmarc_record:
$ref: '#/components/schemas/DMARCRecord'
bimi_record:
$ref: '#/components/schemas/BIMIRecord'
ptr_records:
type: array
items:
type: string
description: PTR (reverse DNS) records for the sender IP address
example: ["mail.example.com", "smtp.example.com"]
ptr_forward_records:
type: array
items:
type: string
description: A or AAAA records resolved from the PTR hostnames (forward confirmation)
example: ["192.0.2.1", "2001:db8::1"]
errors:
type: array
items:
type: string
description: DNS lookup errors
MXRecord:
type: object
required:
- host
- priority
- valid
properties:
host:
type: string
description: MX hostname
example: "mail.example.com"
priority:
type: integer
format: uint16
description: MX priority (lower is higher priority)
example: 10
valid:
type: boolean
description: Whether the MX record is valid
example: true
error:
type: string
description: Error message if validation failed
example: "Failed to lookup MX records"
SPFRecord:
type: object
required:
- valid
properties:
domain:
type: string
description: Domain this SPF record belongs to
example: "example.com"
record:
type: string
description: SPF record content
example: "v=spf1 include:_spf.example.com ~all"
valid:
type: boolean
description: Whether the SPF record is valid
example: true
all_qualifier:
type: string
enum: ["+", "-", "~", "?"]
description: "Qualifier for the 'all' mechanism: + (pass), - (fail), ~ (softfail), ? (neutral)"
example: "~"
error:
type: string
description: Error message if validation failed
example: "No SPF record found"
DKIMRecord:
type: object
required:
- selector
- domain
- valid
properties:
selector:
type: string
description: DKIM selector
example: "default"
domain:
type: string
description: Domain name
example: "example.com"
record:
type: string
description: DKIM record content
example: "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA..."
valid:
type: boolean
description: Whether the DKIM record is valid
example: true
error:
type: string
description: Error message if validation failed
example: "No DKIM record found"
DMARCRecord:
type: object
required:
- valid
properties:
record:
type: string
description: DMARC record content
example: "v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com"
policy:
type: string
enum: [none, quarantine, reject, unknown]
description: DMARC policy
example: "quarantine"
subdomain_policy:
type: string
enum: [none, quarantine, reject, unknown]
description: DMARC subdomain policy (sp tag) - policy for subdomains if different from main policy
example: "quarantine"
percentage:
type: integer
minimum: 0
maximum: 100
description: Percentage of messages subjected to filtering (pct tag, default 100)
example: 100
spf_alignment:
type: string
enum: [relaxed, strict]
description: SPF alignment mode (aspf tag)
example: "relaxed"
dkim_alignment:
type: string
enum: [relaxed, strict]
description: DKIM alignment mode (adkim tag)
example: "relaxed"
valid:
type: boolean
description: Whether the DMARC record is valid
example: true
error:
type: string
description: Error message if validation failed
example: "No DMARC record found"
BIMIRecord:
type: object
required:
- selector
- domain
- valid
properties:
selector:
type: string
description: BIMI selector
example: "default"
domain:
type: string
description: Domain name
example: "example.com"
record:
type: string
description: BIMI record content
example: "v=BIMI1; l=https://example.com/logo.svg"
logo_url:
type: string
format: uri
description: URL to the brand logo (SVG)
example: "https://example.com/logo.svg"
vmc_url:
type: string
format: uri
description: URL to Verified Mark Certificate (optional)
example: "https://example.com/vmc.pem"
valid:
type: boolean
description: Whether the BIMI record is valid
example: true
error:
type: string
description: Error message if validation failed
example: "No BIMI record found"
BlacklistCheck:
type: object
required:
- rbl
- listed
properties:
rbl:
type: string
description: RBL/DNSBL name
example: "zen.spamhaus.org"
listed:
type: boolean
description: Whether IP is listed
example: false
response:
type: string
description: RBL response code or message
example: "127.0.0.2"
error:
type: string
description: RBL error if any
Status:
type: object
required:
- status
- version
properties:
status:
type: string
enum: [healthy, degraded, unhealthy]
description: Overall service status
example: "healthy"
version:
type: string
description: Service version
example: "0.1.0-dev"
components:
type: object
properties:
database:
type: string
enum: [up, down]
example: "up"
mta:
type: string
enum: [up, down]
example: "up"
uptime:
type: integer
description: Service uptime in seconds
example: 3600
Error:
type: object
required:
- error
- message
properties:
error:
type: string
description: Error code
example: "not_found"
message:
type: string
description: Human-readable error message
example: "Test not found"
details:
type: string
description: Additional error details
DomainTestRequest:
type: object
required:
- domain
properties:
domain:
type: string
pattern: '^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$'
description: Domain name to test (e.g., example.com)
example: "example.com"
DomainTestResponse:
type: object
required:
- domain
- score
- grade
- dns_results
properties:
domain:
type: string
description: The tested domain name
example: "example.com"
score:
type: integer
minimum: 0
maximum: 100
description: Overall domain configuration score (0-100)
example: 85
grade:
type: string
enum: [A+, A, B, C, D, E, F]
description: Letter grade representation of the score
example: "A"
dns_results:
$ref: '#/components/schemas/DNSResults'