Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.mavera.io/llms.txt

Use this file to discover all available pages before exploring further.

Comment Analysis → Feature Messaging

Scenario

Issue comments contain raw voice-of-the-customer language — how they describe problems, what they wish for. This job pulls comments from recent customer-reported issues, feeds the language into Mavera’s Brand Voice engine, then generates feature messaging that mirrors how customers actually talk. Flow: Jira GET /issue/{id}/comment → aggregate language → Mavera POST /api/v1/brand-voicesPOST /api/v1/generations

Code

import os, requests, time, base64

DOMAIN, EMAIL = os.environ["JIRA_DOMAIN"], os.environ["JIRA_EMAIL"]
TOKEN, MV = os.environ["JIRA_API_TOKEN"], os.environ["MAVERA_API_KEY"]
JB, MB = f"https://{DOMAIN}.atlassian.net/rest/api/3", "https://app.mavera.io/api/v1"
cred = base64.b64encode(f"{EMAIL}:{TOKEN}".encode()).decode()
JH = {"Authorization": f"Basic {cred}", "Content-Type": "application/json"}
MH = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}

r = requests.post(f"{JB}/search", headers=JH, json={
    "jql": 'project = PROJ AND labels = "customer-reported" AND comment IS NOT EMPTY ORDER BY updated DESC',
    "startAt": 0, "maxResults": 30, "fields": ["summary"],
})
if r.status_code == 429:
    time.sleep(int(r.headers.get("Retry-After", 30)))
r.raise_for_status()
issues = r.json().get("issues", [])
print(f"Found {len(issues)} issues with comments")

all_comments = []
for iss in issues:
    time.sleep(0.5)
    cr = requests.get(f"{JB}/issue/{iss['key']}/comment", headers=JH,
        params={"maxResults": 20, "orderBy": "-created"})
    if cr.status_code == 429:
        time.sleep(int(cr.headers.get("Retry-After", 30)))
        continue
    for c in cr.json().get("comments", []):
        text = ""
        if c.get("body") and c["body"].get("content"):
            for block in c["body"]["content"]:
                for item in block.get("content", []):
                    if item.get("text"):
                        text += item["text"] + " "
        if len(text) > 20:
            all_comments.append(f"[{iss['key']}: {iss['fields']['summary']}]\n"
                                f"By {c.get('author',{}).get('displayName','Unknown')}:\n{text.strip()[:500]}")

print(f"Extracted {len(all_comments)} comments")

bv = requests.post(f"{MB}/brand-voices", headers=MH, json={
    "name": "Voice of the Customer — Jira Comments",
    "extracted_content": "\n\n---\n\n".join(all_comments[:80])[:50000],
}).json()
print(f"Brand Voice: {bv['id']}")

time.sleep(0.3)
messaging = requests.post(f"{MB}/generations", headers=MH, json={
    "brand_voice_id": bv["id"],
    "prompt": "Using the customer voice from Jira comments, generate:\n"
              "1. Homepage hero — headline + subheadline in customer language\n"
              "2. 3 feature descriptions written how customers describe value\n"
              "3. 3 testimonial-style quotes echoing actual language patterns\n"
              "4. 5 email subject lines using phrases customers actually use\n"
              "5. 4 pain-to-solution pairs — customer quote → product answer",
}).json()

print(messaging.get("output", messaging.get("content", ""))[:3000])

Example Output

Found 28 issues with comments
Extracted 142 comments
Brand Voice: bv_9pR3x

## Homepage Hero
Headline: "Stop fighting your tools. Start shipping."
Subheadline: "Teams switch to us when they're tired of workarounds."

## Feature Descriptions
### Real-Time Sync
"Everything just stays in sync" — changes propagate instantly
across boards, dashboards, and integrations. No stale data.
### Bulk Operations
"I shouldn't need 45 clicks to update 45 issues." Select, update,
done. Bulk edit fields and transitions in seconds.

## Email Subject Lines
1. "Your team deserves tools that don't fight back"
2. "What if bulk edits actually worked?"
3. "Stop context-switching. Start shipping."
4. "The sync problem your team complains about — solved"
5. "We built what Jira comments kept asking for"

Error Handling

Jira Cloud returns comment bodies in Atlassian Document Format. The code walks body.content[].content[].text. Inline code, mentions, and media nodes need type-specific handling.
Automation bots post comments too. Filter by author.accountType === "atlassian" to include only humans, or maintain a bot exclusion list.
Issues with 100+ comments require pagination via startAt. The code fetches 20 most recent per issue. For thorough analysis, page through all comments.