Seth commited on
Commit Β·
e72db37
1
Parent(s): b187543
update
Browse files
backend/app/gpt_service.py
CHANGED
|
@@ -21,6 +21,8 @@ def generate_email_sequence(
|
|
| 21 |
prompt_template: str,
|
| 22 |
product_name: str,
|
| 23 |
campaign_sequence: Optional[Dict[str, Any]] = None,
|
|
|
|
|
|
|
| 24 |
) -> List[Dict]:
|
| 25 |
"""
|
| 26 |
Generate a personalized email sequence for a contact using GPT.
|
|
@@ -46,6 +48,7 @@ def generate_email_sequence(
|
|
| 46 |
industry = safe_str(contact.get("industry") or contact.get("Industry") or "")
|
| 47 |
location = safe_str(contact.get("location") or contact.get("Location") or "")
|
| 48 |
company_size = safe_str(contact.get("company_size") or contact.get("Company Size") or "")
|
|
|
|
| 49 |
|
| 50 |
# Validate required fields
|
| 51 |
if not email:
|
|
@@ -125,17 +128,18 @@ Body: Hi {first_name},
|
|
| 125 |
|
| 126 |
[email body content]
|
| 127 |
|
| 128 |
-
|
| 129 |
Email 2
|
| 130 |
-
Subject: [subject line]
|
| 131 |
Body: Hi {first_name},
|
| 132 |
|
| 133 |
-
[email body content]
|
| 134 |
|
| 135 |
-
|
| 136 |
... (continue for all {num_emails} emails, each starting with "Hi {first_name},")
|
| 137 |
|
| 138 |
-
Remember: Use only the information provided above. Do not reference anything not explicitly mentioned.
|
|
|
|
| 139 |
)
|
| 140 |
else:
|
| 141 |
# Use standard prompt for other products
|
|
@@ -154,8 +158,8 @@ Personalize this email template:
|
|
| 154 |
{prompt_template}
|
| 155 |
|
| 156 |
Replace all variables like {{first_name}}, {{company}}, {{sender_name}} with the actual information.
|
| 157 |
-
|
| 158 |
-
|
| 159 |
if campaign_sequence and int(campaign_sequence.get("email_count") or 0) > 1:
|
| 160 |
n = int(campaign_sequence["email_count"])
|
| 161 |
titles = campaign_sequence.get("email_titles") or []
|
|
@@ -309,13 +313,14 @@ Touches in order:
|
|
| 309 |
})
|
| 310 |
|
| 311 |
# Replace template variables in all emails
|
|
|
|
| 312 |
for email_data in emails:
|
| 313 |
email_data["body"] = email_data["body"].replace("{{first_name}}", first_name)
|
| 314 |
email_data["body"] = email_data["body"].replace("{{company}}", company)
|
| 315 |
-
email_data["body"] = email_data["body"].replace("{{sender_name}}",
|
| 316 |
email_data["subject"] = email_data["subject"].replace("{{first_name}}", first_name)
|
| 317 |
email_data["subject"] = email_data["subject"].replace("{{company}}", company)
|
| 318 |
-
email_data["subject"] = email_data["subject"].replace("{{sender_name}}",
|
| 319 |
|
| 320 |
# Ensure first name is in the greeting if it's missing
|
| 321 |
# Fix cases where GPT generates "Hi," instead of "Hi {first_name},"
|
|
@@ -349,7 +354,9 @@ Touches in order:
|
|
| 349 |
# Return a fallback email
|
| 350 |
first_name = contact.get("first_name", contact.get("First Name", "there"))
|
| 351 |
company = contact.get("company", contact.get("Company", contact.get("Organization", "your company")))
|
| 352 |
-
|
|
|
|
|
|
|
| 353 |
return [{
|
| 354 |
"first_name": first_name,
|
| 355 |
"last_name": contact.get("last_name", contact.get("Last Name", "")),
|
|
@@ -359,7 +366,7 @@ Touches in order:
|
|
| 359 |
"product": product_name,
|
| 360 |
"email_number": 1,
|
| 361 |
"subject": f"{first_name}, let's talk about {product_name}",
|
| 362 |
-
"email_content": f"Hi {first_name},\n\nI wanted to reach out about how {product_name} could benefit {company}.
|
| 363 |
}]
|
| 364 |
|
| 365 |
|
|
@@ -368,6 +375,8 @@ def generate_linkedin_sequence(
|
|
| 368 |
prompt_template: str,
|
| 369 |
product_name: str,
|
| 370 |
campaign_sequence: Optional[Dict[str, Any]] = None,
|
|
|
|
|
|
|
| 371 |
) -> List[Dict]:
|
| 372 |
"""
|
| 373 |
Generate LinkedIn DM-style messages (connection note + follow-ups) for a contact.
|
|
@@ -395,6 +404,8 @@ def generate_linkedin_sequence(
|
|
| 395 |
if not email:
|
| 396 |
raise ValueError(f"Contact missing email address: {contact}")
|
| 397 |
|
|
|
|
|
|
|
| 398 |
system_prompt = prompt_template
|
| 399 |
li_actions: List[str] = []
|
| 400 |
if campaign_sequence and isinstance(campaign_sequence.get("linkedin_actions"), list):
|
|
@@ -446,6 +457,8 @@ Contact Information:
|
|
| 446 |
- Industry: {industry if industry else 'Not specified'}
|
| 447 |
- Location: {location if location else 'Not specified'}
|
| 448 |
|
|
|
|
|
|
|
| 449 |
Generate exactly {num_messages} messages (no more, no fewer) following the rules in the system prompt.
|
| 450 |
{plan_extra}
|
| 451 |
CRITICAL: Write for LinkedIn DMs or connection notes β short, plain, professional. No email-style subjects.
|
|
@@ -478,7 +491,7 @@ Use the contact's real first name where a greeting is appropriate. Do not use pl
|
|
| 478 |
body = (
|
| 479 |
body.replace("{{first_name}}", first_name)
|
| 480 |
.replace("{{company}}", company)
|
| 481 |
-
.replace("{{sender_name}}", "
|
| 482 |
)
|
| 483 |
messages.append(
|
| 484 |
{
|
|
@@ -507,7 +520,7 @@ Use the contact's real first name where a greeting is appropriate. Do not use pl
|
|
| 507 |
chunk = (
|
| 508 |
chunk.replace("{{first_name}}", first_name)
|
| 509 |
.replace("{{company}}", company)
|
| 510 |
-
.replace("{{sender_name}}", "
|
| 511 |
)
|
| 512 |
messages.append({"email_number": 1, "subject": "", "body": chunk})
|
| 513 |
|
|
@@ -538,6 +551,8 @@ Use the contact's real first name where a greeting is appropriate. Do not use pl
|
|
| 538 |
contact.get("company") or contact.get("Company") or contact.get("Organization") or "your company"
|
| 539 |
)
|
| 540 |
email = safe_str(contact.get("email") or contact.get("Email") or "")
|
|
|
|
|
|
|
| 541 |
return [
|
| 542 |
{
|
| 543 |
"first_name": fn,
|
|
@@ -548,7 +563,7 @@ Use the contact's real first name where a greeting is appropriate. Do not use pl
|
|
| 548 |
"product": product_name,
|
| 549 |
"email_number": 1,
|
| 550 |
"subject": "",
|
| 551 |
-
"email_content": f"Hi {fn},\n\nQuick note on {product_name} for {company} β happy to compare notes if useful.
|
| 552 |
}
|
| 553 |
]
|
| 554 |
|
|
|
|
| 21 |
prompt_template: str,
|
| 22 |
product_name: str,
|
| 23 |
campaign_sequence: Optional[Dict[str, Any]] = None,
|
| 24 |
+
*,
|
| 25 |
+
sender_mailbox_name: Optional[str] = None,
|
| 26 |
) -> List[Dict]:
|
| 27 |
"""
|
| 28 |
Generate a personalized email sequence for a contact using GPT.
|
|
|
|
| 48 |
industry = safe_str(contact.get("industry") or contact.get("Industry") or "")
|
| 49 |
location = safe_str(contact.get("location") or contact.get("Location") or "")
|
| 50 |
company_size = safe_str(contact.get("company_size") or contact.get("Company Size") or "")
|
| 51 |
+
signoff = (sender_mailbox_name or "").strip()
|
| 52 |
|
| 53 |
# Validate required fields
|
| 54 |
if not email:
|
|
|
|
| 128 |
|
| 129 |
[email body content]
|
| 130 |
|
| 131 |
+
{{{{sender_name}}}}
|
| 132 |
Email 2
|
| 133 |
+
Subject: [subject line β must be Re: followed by the EXACT same base subject text as Email 1, with no new topic]
|
| 134 |
Body: Hi {first_name},
|
| 135 |
|
| 136 |
+
[email body content β write ONLY the new paragraphs for this touch; do not paste or quote prior emails here]
|
| 137 |
|
| 138 |
+
{{{{sender_name}}}}
|
| 139 |
... (continue for all {num_emails} emails, each starting with "Hi {first_name},")
|
| 140 |
|
| 141 |
+
Remember: Use only the information provided above. Do not reference anything not explicitly mentioned.
|
| 142 |
+
For Email 2 onward, the subject line must stay on-thread: use "Re: <Email 1 subject without any leading Re:>"."""
|
| 143 |
)
|
| 144 |
else:
|
| 145 |
# Use standard prompt for other products
|
|
|
|
| 158 |
{prompt_template}
|
| 159 |
|
| 160 |
Replace all variables like {{first_name}}, {{company}}, {{sender_name}} with the actual information.
|
| 161 |
+
{f'For {{sender_name}}, use exactly this sender sign-off name (not the prospect): {signoff}' if signoff else 'If {{sender_name}} appears and no sender name was provided for this run, leave the literal token {{sender_name}} in the output.'}
|
| 162 |
+
Make it sound natural and personalized. Keep the same structure and format."""
|
| 163 |
if campaign_sequence and int(campaign_sequence.get("email_count") or 0) > 1:
|
| 164 |
n = int(campaign_sequence["email_count"])
|
| 165 |
titles = campaign_sequence.get("email_titles") or []
|
|
|
|
| 313 |
})
|
| 314 |
|
| 315 |
# Replace template variables in all emails
|
| 316 |
+
repl_sender = signoff if signoff else ""
|
| 317 |
for email_data in emails:
|
| 318 |
email_data["body"] = email_data["body"].replace("{{first_name}}", first_name)
|
| 319 |
email_data["body"] = email_data["body"].replace("{{company}}", company)
|
| 320 |
+
email_data["body"] = email_data["body"].replace("{{sender_name}}", repl_sender)
|
| 321 |
email_data["subject"] = email_data["subject"].replace("{{first_name}}", first_name)
|
| 322 |
email_data["subject"] = email_data["subject"].replace("{{company}}", company)
|
| 323 |
+
email_data["subject"] = email_data["subject"].replace("{{sender_name}}", repl_sender)
|
| 324 |
|
| 325 |
# Ensure first name is in the greeting if it's missing
|
| 326 |
# Fix cases where GPT generates "Hi," instead of "Hi {first_name},"
|
|
|
|
| 354 |
# Return a fallback email
|
| 355 |
first_name = contact.get("first_name", contact.get("First Name", "there"))
|
| 356 |
company = contact.get("company", contact.get("Company", contact.get("Organization", "your company")))
|
| 357 |
+
fb = (sender_mailbox_name or "").strip()
|
| 358 |
+
tail = f"\n\nBest,\n{fb}" if fb else "\n\nBest,"
|
| 359 |
+
|
| 360 |
return [{
|
| 361 |
"first_name": first_name,
|
| 362 |
"last_name": contact.get("last_name", contact.get("Last Name", "")),
|
|
|
|
| 366 |
"product": product_name,
|
| 367 |
"email_number": 1,
|
| 368 |
"subject": f"{first_name}, let's talk about {product_name}",
|
| 369 |
+
"email_content": f"Hi {first_name},\n\nI wanted to reach out about how {product_name} could benefit {company}.{tail}",
|
| 370 |
}]
|
| 371 |
|
| 372 |
|
|
|
|
| 375 |
prompt_template: str,
|
| 376 |
product_name: str,
|
| 377 |
campaign_sequence: Optional[Dict[str, Any]] = None,
|
| 378 |
+
*,
|
| 379 |
+
sender_linkedin_name: Optional[str] = None,
|
| 380 |
) -> List[Dict]:
|
| 381 |
"""
|
| 382 |
Generate LinkedIn DM-style messages (connection note + follow-ups) for a contact.
|
|
|
|
| 404 |
if not email:
|
| 405 |
raise ValueError(f"Contact missing email address: {contact}")
|
| 406 |
|
| 407 |
+
li_sign = (sender_linkedin_name or "").strip()
|
| 408 |
+
|
| 409 |
system_prompt = prompt_template
|
| 410 |
li_actions: List[str] = []
|
| 411 |
if campaign_sequence and isinstance(campaign_sequence.get("linkedin_actions"), list):
|
|
|
|
| 457 |
- Industry: {industry if industry else 'Not specified'}
|
| 458 |
- Location: {location if location else 'Not specified'}
|
| 459 |
|
| 460 |
+
{f'Sender first name / sign-off to use when addressing yourself (not the prospect): {li_sign}' if li_sign else 'If you sign with a name, output the literal token {{sender_name}} on its own line; the app substitutes the LinkedIn profile name.'}
|
| 461 |
+
|
| 462 |
Generate exactly {num_messages} messages (no more, no fewer) following the rules in the system prompt.
|
| 463 |
{plan_extra}
|
| 464 |
CRITICAL: Write for LinkedIn DMs or connection notes β short, plain, professional. No email-style subjects.
|
|
|
|
| 491 |
body = (
|
| 492 |
body.replace("{{first_name}}", first_name)
|
| 493 |
.replace("{{company}}", company)
|
| 494 |
+
.replace("{{sender_name}}", li_sign if li_sign else "")
|
| 495 |
)
|
| 496 |
messages.append(
|
| 497 |
{
|
|
|
|
| 520 |
chunk = (
|
| 521 |
chunk.replace("{{first_name}}", first_name)
|
| 522 |
.replace("{{company}}", company)
|
| 523 |
+
.replace("{{sender_name}}", li_sign if li_sign else "")
|
| 524 |
)
|
| 525 |
messages.append({"email_number": 1, "subject": "", "body": chunk})
|
| 526 |
|
|
|
|
| 551 |
contact.get("company") or contact.get("Company") or contact.get("Organization") or "your company"
|
| 552 |
)
|
| 553 |
email = safe_str(contact.get("email") or contact.get("Email") or "")
|
| 554 |
+
li_fb = (sender_linkedin_name or "").strip()
|
| 555 |
+
li_tail = f"\n\n{li_fb}" if li_fb else ""
|
| 556 |
return [
|
| 557 |
{
|
| 558 |
"first_name": fn,
|
|
|
|
| 563 |
"product": product_name,
|
| 564 |
"email_number": 1,
|
| 565 |
"subject": "",
|
| 566 |
+
"email_content": f"Hi {fn},\n\nQuick note on {product_name} for {company} β happy to compare notes if useful.{li_tail}",
|
| 567 |
}
|
| 568 |
]
|
| 569 |
|
backend/app/main.py
CHANGED
|
@@ -2234,11 +2234,18 @@ async def generate_linkedin_campaign_sequences(
|
|
| 2234 |
GeneratedSequence.channel == "linkedin",
|
| 2235 |
).delete()
|
| 2236 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2237 |
sequence_id = 1
|
| 2238 |
generated_rows = 0
|
| 2239 |
for _, row in df.iterrows():
|
| 2240 |
contact = row.to_dict()
|
| 2241 |
-
li_list = generate_linkedin_sequence(
|
|
|
|
|
|
|
| 2242 |
for seq_data in li_list:
|
| 2243 |
db.add(
|
| 2244 |
GeneratedSequence(
|
|
@@ -3916,6 +3923,19 @@ async def generate_sequences(
|
|
| 3916 |
).delete()
|
| 3917 |
db.commit()
|
| 3918 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3919 |
total_contacts = len(df)
|
| 3920 |
|
| 3921 |
def progress_pct(seq_id: int, *, linkedin_phase: bool) -> float:
|
|
@@ -4024,7 +4044,11 @@ async def generate_sequences(
|
|
| 4024 |
loop = asyncio.get_event_loop()
|
| 4025 |
with concurrent.futures.ThreadPoolExecutor() as executor:
|
| 4026 |
if plan_pack and email_kw:
|
| 4027 |
-
gen_fn = partial(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4028 |
sequence_data_list = await loop.run_in_executor(
|
| 4029 |
executor,
|
| 4030 |
gen_fn,
|
|
@@ -4035,7 +4059,7 @@ async def generate_sequences(
|
|
| 4035 |
else:
|
| 4036 |
sequence_data_list = await loop.run_in_executor(
|
| 4037 |
executor,
|
| 4038 |
-
generate_email_sequence,
|
| 4039 |
contact,
|
| 4040 |
prompt_template,
|
| 4041 |
product_name,
|
|
@@ -4156,7 +4180,11 @@ async def generate_sequences(
|
|
| 4156 |
loop = asyncio.get_event_loop()
|
| 4157 |
with concurrent.futures.ThreadPoolExecutor() as executor:
|
| 4158 |
if plan_pack and li_kw:
|
| 4159 |
-
gen_li = partial(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4160 |
li_list = await loop.run_in_executor(
|
| 4161 |
executor,
|
| 4162 |
gen_li,
|
|
@@ -4167,7 +4195,7 @@ async def generate_sequences(
|
|
| 4167 |
else:
|
| 4168 |
li_list = await loop.run_in_executor(
|
| 4169 |
executor,
|
| 4170 |
-
generate_linkedin_sequence,
|
| 4171 |
contact,
|
| 4172 |
li_template,
|
| 4173 |
li_product,
|
|
|
|
| 2234 |
GeneratedSequence.channel == "linkedin",
|
| 2235 |
).delete()
|
| 2236 |
|
| 2237 |
+
urow = db.query(User).filter(User.id == t.user_id).first()
|
| 2238 |
+
li_sig = ""
|
| 2239 |
+
if urow:
|
| 2240 |
+
li_sig = (getattr(urow, "linkedin_profile_display_name", None) or "").strip() or (urow.name or "").strip()
|
| 2241 |
+
|
| 2242 |
sequence_id = 1
|
| 2243 |
generated_rows = 0
|
| 2244 |
for _, row in df.iterrows():
|
| 2245 |
contact = row.to_dict()
|
| 2246 |
+
li_list = generate_linkedin_sequence(
|
| 2247 |
+
contact, body.prompt_template, campaign.name, sender_linkedin_name=li_sig or None
|
| 2248 |
+
)
|
| 2249 |
for seq_data in li_list:
|
| 2250 |
db.add(
|
| 2251 |
GeneratedSequence(
|
|
|
|
| 3923 |
).delete()
|
| 3924 |
db.commit()
|
| 3925 |
|
| 3926 |
+
user_row = db.query(User).filter(User.id == t.user_id).first()
|
| 3927 |
+
mailbox_sig = ""
|
| 3928 |
+
linkedin_sig = ""
|
| 3929 |
+
if user_row:
|
| 3930 |
+
mailbox_sig = (
|
| 3931 |
+
(getattr(user_row, "mailbox_profile_display_name", None) or "").strip()
|
| 3932 |
+
or (user_row.name or "").strip()
|
| 3933 |
+
)
|
| 3934 |
+
linkedin_sig = (
|
| 3935 |
+
(getattr(user_row, "linkedin_profile_display_name", None) or "").strip()
|
| 3936 |
+
or (user_row.name or "").strip()
|
| 3937 |
+
)
|
| 3938 |
+
|
| 3939 |
total_contacts = len(df)
|
| 3940 |
|
| 3941 |
def progress_pct(seq_id: int, *, linkedin_phase: bool) -> float:
|
|
|
|
| 4044 |
loop = asyncio.get_event_loop()
|
| 4045 |
with concurrent.futures.ThreadPoolExecutor() as executor:
|
| 4046 |
if plan_pack and email_kw:
|
| 4047 |
+
gen_fn = partial(
|
| 4048 |
+
generate_email_sequence,
|
| 4049 |
+
campaign_sequence=email_kw,
|
| 4050 |
+
sender_mailbox_name=mailbox_sig or None,
|
| 4051 |
+
)
|
| 4052 |
sequence_data_list = await loop.run_in_executor(
|
| 4053 |
executor,
|
| 4054 |
gen_fn,
|
|
|
|
| 4059 |
else:
|
| 4060 |
sequence_data_list = await loop.run_in_executor(
|
| 4061 |
executor,
|
| 4062 |
+
partial(generate_email_sequence, sender_mailbox_name=mailbox_sig or None),
|
| 4063 |
contact,
|
| 4064 |
prompt_template,
|
| 4065 |
product_name,
|
|
|
|
| 4180 |
loop = asyncio.get_event_loop()
|
| 4181 |
with concurrent.futures.ThreadPoolExecutor() as executor:
|
| 4182 |
if plan_pack and li_kw:
|
| 4183 |
+
gen_li = partial(
|
| 4184 |
+
generate_linkedin_sequence,
|
| 4185 |
+
campaign_sequence=li_kw,
|
| 4186 |
+
sender_linkedin_name=linkedin_sig or None,
|
| 4187 |
+
)
|
| 4188 |
li_list = await loop.run_in_executor(
|
| 4189 |
executor,
|
| 4190 |
gen_li,
|
|
|
|
| 4195 |
else:
|
| 4196 |
li_list = await loop.run_in_executor(
|
| 4197 |
executor,
|
| 4198 |
+
partial(generate_linkedin_sequence, sender_linkedin_name=linkedin_sig or None),
|
| 4199 |
contact,
|
| 4200 |
li_template,
|
| 4201 |
li_product,
|
backend/app/outreach_routes.py
CHANGED
|
@@ -635,19 +635,113 @@ def _find_next_send(
|
|
| 635 |
def _fetch_generated(
|
| 636 |
db: Session, tenant_id: int, file_id: str, row_index: int, channel: str, step_order: int
|
| 637 |
) -> Optional[GeneratedSequence]:
|
| 638 |
-
|
| 639 |
db.query(GeneratedSequence)
|
| 640 |
.filter(
|
| 641 |
GeneratedSequence.tenant_id == tenant_id,
|
| 642 |
GeneratedSequence.file_id == file_id,
|
| 643 |
GeneratedSequence.sequence_id == row_index,
|
| 644 |
-
GeneratedSequence.channel == channel,
|
| 645 |
GeneratedSequence.step_order == step_order,
|
| 646 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 647 |
.first()
|
| 648 |
)
|
| 649 |
|
| 650 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 651 |
def _execute_one_send(
|
| 652 |
db: Session,
|
| 653 |
tc: TenantContext,
|
|
@@ -701,12 +795,13 @@ def _execute_one_send(
|
|
| 701 |
display = " ".join(
|
| 702 |
p for p in [_safe_str(gen.first_name or contact.first_name), _safe_str(gen.last_name or contact.last_name)] if p
|
| 703 |
).strip()
|
|
|
|
| 704 |
send_ids = _send_unipile_email(
|
| 705 |
acc.unipile_account_id,
|
| 706 |
to_email,
|
| 707 |
display or to_email,
|
| 708 |
-
|
| 709 |
-
|
| 710 |
label,
|
| 711 |
)
|
| 712 |
rec.unipile_message_id = send_ids.get("message_id") or send_ids.get("tracking_id")
|
|
|
|
| 635 |
def _fetch_generated(
|
| 636 |
db: Session, tenant_id: int, file_id: str, row_index: int, channel: str, step_order: int
|
| 637 |
) -> Optional[GeneratedSequence]:
|
| 638 |
+
q = (
|
| 639 |
db.query(GeneratedSequence)
|
| 640 |
.filter(
|
| 641 |
GeneratedSequence.tenant_id == tenant_id,
|
| 642 |
GeneratedSequence.file_id == file_id,
|
| 643 |
GeneratedSequence.sequence_id == row_index,
|
|
|
|
| 644 |
GeneratedSequence.step_order == step_order,
|
| 645 |
)
|
| 646 |
+
)
|
| 647 |
+
if channel == "gmail":
|
| 648 |
+
q = q.filter(or_(GeneratedSequence.channel == "gmail", GeneratedSequence.channel == "email"))
|
| 649 |
+
else:
|
| 650 |
+
q = q.filter(GeneratedSequence.channel == channel)
|
| 651 |
+
return q.first()
|
| 652 |
+
|
| 653 |
+
|
| 654 |
+
def _strip_re_subject(subject: str) -> str:
|
| 655 |
+
s = _safe_str(subject)
|
| 656 |
+
while True:
|
| 657 |
+
t = s.lstrip()
|
| 658 |
+
tl = t.lower()
|
| 659 |
+
if tl.startswith("re:"):
|
| 660 |
+
s = t[3:].lstrip()
|
| 661 |
+
elif tl.startswith("fwd:"):
|
| 662 |
+
s = t[4:].lstrip()
|
| 663 |
+
else:
|
| 664 |
+
break
|
| 665 |
+
return s.strip()
|
| 666 |
+
|
| 667 |
+
|
| 668 |
+
def _gmail_channel_filter():
|
| 669 |
+
return or_(GeneratedSequence.channel == "gmail", GeneratedSequence.channel == "email")
|
| 670 |
+
|
| 671 |
+
|
| 672 |
+
def _first_gmail_root_subject(db: Session, tenant_id: int, file_id: str, seq_id: int) -> str:
|
| 673 |
+
row = (
|
| 674 |
+
db.query(GeneratedSequence)
|
| 675 |
+
.filter(
|
| 676 |
+
GeneratedSequence.tenant_id == tenant_id,
|
| 677 |
+
GeneratedSequence.file_id == file_id,
|
| 678 |
+
GeneratedSequence.sequence_id == seq_id,
|
| 679 |
+
_gmail_channel_filter(),
|
| 680 |
+
)
|
| 681 |
+
.order_by(func.coalesce(GeneratedSequence.step_order, 999999).asc(), GeneratedSequence.email_number.asc())
|
| 682 |
+
.first()
|
| 683 |
+
)
|
| 684 |
+
if not row or not row.subject:
|
| 685 |
+
return ""
|
| 686 |
+
return _strip_re_subject(row.subject)
|
| 687 |
+
|
| 688 |
+
|
| 689 |
+
def _prior_gmail_for_thread(
|
| 690 |
+
db: Session, tenant_id: int, file_id: str, seq_id: int, gen: GeneratedSequence, act_order: int
|
| 691 |
+
) -> Optional[GeneratedSequence]:
|
| 692 |
+
cur_so = getattr(gen, "step_order", None)
|
| 693 |
+
if cur_so is None:
|
| 694 |
+
cur_so = int(act_order)
|
| 695 |
+
prior = (
|
| 696 |
+
db.query(GeneratedSequence)
|
| 697 |
+
.filter(
|
| 698 |
+
GeneratedSequence.tenant_id == tenant_id,
|
| 699 |
+
GeneratedSequence.file_id == file_id,
|
| 700 |
+
GeneratedSequence.sequence_id == seq_id,
|
| 701 |
+
_gmail_channel_filter(),
|
| 702 |
+
GeneratedSequence.step_order < int(cur_so),
|
| 703 |
+
)
|
| 704 |
+
.order_by(GeneratedSequence.step_order.desc())
|
| 705 |
+
.first()
|
| 706 |
+
)
|
| 707 |
+
if prior:
|
| 708 |
+
return prior
|
| 709 |
+
return (
|
| 710 |
+
db.query(GeneratedSequence)
|
| 711 |
+
.filter(
|
| 712 |
+
GeneratedSequence.tenant_id == tenant_id,
|
| 713 |
+
GeneratedSequence.file_id == file_id,
|
| 714 |
+
GeneratedSequence.sequence_id == seq_id,
|
| 715 |
+
_gmail_channel_filter(),
|
| 716 |
+
GeneratedSequence.email_number < int(gen.email_number or 0),
|
| 717 |
+
)
|
| 718 |
+
.order_by(GeneratedSequence.email_number.desc())
|
| 719 |
.first()
|
| 720 |
)
|
| 721 |
|
| 722 |
|
| 723 |
+
def _compose_threaded_gmail_send(
|
| 724 |
+
db: Session, gen: GeneratedSequence, contact: Contact, act_order: int
|
| 725 |
+
) -> Tuple[str, str]:
|
| 726 |
+
"""Same-thread follow-ups: Re: <root subject>; append quoted prior body (send layer)."""
|
| 727 |
+
seq_id = int(contact.row_index or 0)
|
| 728 |
+
fresh_body = (gen.email_content or "").strip()
|
| 729 |
+
root = _first_gmail_root_subject(db, int(gen.tenant_id or 0), gen.file_id, seq_id)
|
| 730 |
+
prior = _prior_gmail_for_thread(db, int(gen.tenant_id or 0), gen.file_id, seq_id, gen, act_order)
|
| 731 |
+
if not prior:
|
| 732 |
+
subj = gen.subject or root or "(no subject)"
|
| 733 |
+
return (_strip_re_subject(subj) if subj else "(no subject)", fresh_body)
|
| 734 |
+
base = root or _strip_re_subject(prior.subject or gen.subject or "")
|
| 735 |
+
if not base:
|
| 736 |
+
base = _strip_re_subject(gen.subject or "(no subject)")
|
| 737 |
+
out_subj = f"Re: {base}"
|
| 738 |
+
prev_body = (prior.email_content or "").strip()
|
| 739 |
+
quoted = "\n".join(f"> {ln}" for ln in prev_body.splitlines())
|
| 740 |
+
sep = "\n\n---------- Previous message ----------\n"
|
| 741 |
+
body = f"{fresh_body}{sep}{quoted}\n"
|
| 742 |
+
return (out_subj, body)
|
| 743 |
+
|
| 744 |
+
|
| 745 |
def _execute_one_send(
|
| 746 |
db: Session,
|
| 747 |
tc: TenantContext,
|
|
|
|
| 795 |
display = " ".join(
|
| 796 |
p for p in [_safe_str(gen.first_name or contact.first_name), _safe_str(gen.last_name or contact.last_name)] if p
|
| 797 |
).strip()
|
| 798 |
+
out_subj, out_body = _compose_threaded_gmail_send(db, gen, contact, order)
|
| 799 |
send_ids = _send_unipile_email(
|
| 800 |
acc.unipile_account_id,
|
| 801 |
to_email,
|
| 802 |
display or to_email,
|
| 803 |
+
out_subj,
|
| 804 |
+
out_body,
|
| 805 |
label,
|
| 806 |
)
|
| 807 |
rec.unipile_message_id = send_ids.get("message_id") or send_ids.get("tracking_id")
|
frontend/src/components/campaigns/CreateCampaignWizard.jsx
CHANGED
|
@@ -615,7 +615,7 @@ export default function CreateCampaignWizard({
|
|
| 615 |
if (!cur) {
|
| 616 |
cur =
|
| 617 |
LINKEDIN_DEFAULT_TEMPLATES[p.name] ||
|
| 618 |
-
`π LINKEDIN SYSTEM PROMPT\n\nYou write LinkedIn connection notes and DMs for ${p.name}.
|
| 619 |
}
|
| 620 |
if (!cur || cur.includes(WIZARD_SEQUENCE_TAG)) return;
|
| 621 |
next[p.name] = `${cur}\n\n${appendix}`;
|
|
|
|
| 615 |
if (!cur) {
|
| 616 |
cur =
|
| 617 |
LINKEDIN_DEFAULT_TEMPLATES[p.name] ||
|
| 618 |
+
`π LINKEDIN SYSTEM PROMPT\n\nYou write LinkedIn connection notes and DMs for ${p.name}. Sign with {{sender_name}} when signing off.\nMatch the Message count in the generation request exactly.`;
|
| 619 |
}
|
| 620 |
if (!cur || cur.includes(WIZARD_SEQUENCE_TAG)) return;
|
| 621 |
next[p.name] = `${cur}\n\n${appendix}`;
|
frontend/src/components/prompts/PromptEditor.jsx
CHANGED
|
@@ -22,172 +22,78 @@ function splitCampaignRaw(raw, includeLinkedinSection) {
|
|
| 22 |
export const DEFAULT_TEMPLATES = {
|
| 23 |
'Accounts Payable Automation': `π SYSTEM PROMPT (DO NOT MODIFY)
|
| 24 |
|
| 25 |
-
You are an expert B2B outbound copywriter
|
| 26 |
-
Your audience is Accounts Payable professionals (Accounts Payable Managers, Finance Managers, Controllers) at North American mid-market manufacturing and industrial companies.
|
| 27 |
-
Your goal is to generate reply-worthy AP email sequences that feel:
|
| 28 |
-
β’ Familiar
|
| 29 |
-
β’ Simple
|
| 30 |
-
β’ Relevant
|
| 31 |
-
β’ Calm
|
| 32 |
-
β’ Non-salesy
|
| 33 |
-
|
| 34 |
-
The objective is interest and response, not persuasion.
|
| 35 |
|
| 36 |
-
|
| 37 |
|
| 38 |
-
|
| 39 |
|
| 40 |
-
|
|
|
|
| 41 |
|
| 42 |
Rules:
|
| 43 |
-
β’ The
|
| 44 |
-
β’ NEVER
|
| 45 |
-
β’ NEVER
|
| 46 |
-
β’ If there is ambiguity, default to Anna
|
| 47 |
-
|
| 48 |
-
π NON-NEGOTIABLE RULES
|
| 49 |
-
|
| 50 |
-
1. No fake personalization
|
| 51 |
-
β’ Never reference LinkedIn activity, posts, likes, hiring, growth, or news
|
| 52 |
-
β’ Never say "I noticed", "based on what I saw", "research", etc.
|
| 53 |
-
β’ Use ONLY information explicitly provided in the input
|
| 54 |
-
|
| 55 |
-
2. AP-native language only
|
| 56 |
-
β’ Avoid words like: workflow, automation (except when naming EZOFIS once), optimization, transformation, AI hype
|
| 57 |
-
β’ Use words like: waiting, on hold, approvals, receiving, matching, backup, follow-ups, status
|
| 58 |
-
|
| 59 |
-
3. Tone
|
| 60 |
-
β Calm
|
| 61 |
-
β Professional
|
| 62 |
-
β Plain language
|
| 63 |
-
β Not alarming
|
| 64 |
-
β Not consultative
|
| 65 |
-
β Not sales-driven
|
| 66 |
-
|
| 67 |
-
4. Structure
|
| 68 |
-
β Short paragraphs
|
| 69 |
-
β No emojis
|
| 70 |
-
β No em dashes
|
| 71 |
-
β No hype words
|
| 72 |
-
β No marketing phrases
|
| 73 |
-
β No long explanations
|
| 74 |
|
| 75 |
-
|
| 76 |
-
β Each email must ask exactly ONE question
|
| 77 |
-
β Questions must be easy to answer with "yes", "sometimes", or "no"
|
| 78 |
-
|
| 79 |
-
6. Personalization rules
|
| 80 |
-
|
| 81 |
-
You MAY infer operational reality from:
|
| 82 |
-
β’ Role
|
| 83 |
-
β’ Industry
|
| 84 |
-
β’ Company type (manufacturing, service, project-based)
|
| 85 |
-
β’ Scale indicators (employee count, global vs local)
|
| 86 |
-
|
| 87 |
-
You MUST NOT:
|
| 88 |
-
β’ Name ERP systems
|
| 89 |
-
β’ Name AP tools
|
| 90 |
-
β’ Name tech stack
|
| 91 |
-
β’ Invent internal processes
|
| 92 |
-
|
| 93 |
-
π INPUT FORMAT (BATCH)
|
| 94 |
|
| 95 |
-
|
| 96 |
-
β’
|
| 97 |
-
β’
|
| 98 |
-
β’
|
| 99 |
-
β’ Company
|
| 100 |
-
β’ Industry
|
| 101 |
-
β’ Keywords
|
| 102 |
-
β’ Location
|
| 103 |
-
β’ Employee count
|
| 104 |
|
| 105 |
-
π
|
| 106 |
|
| 107 |
-
|
| 108 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
|
| 110 |
-
π
|
| 111 |
-
|
| 112 |
-
π§ EMAIL 1 β Recognition
|
| 113 |
-
|
| 114 |
-
Purpose: Create immediate familiarity.
|
| 115 |
-
|
| 116 |
-
Rules:
|
| 117 |
-
β’ Describe a real AP situation relevant to the contact's environment
|
| 118 |
-
β’ No solutions yet
|
| 119 |
-
β’ Ask ONE recognition question
|
| 120 |
-
|
| 121 |
-
Length: 4-6 lines
|
| 122 |
-
End with a question
|
| 123 |
-
|
| 124 |
-
π§ EMAIL 2 β Checklist Offer
|
| 125 |
-
|
| 126 |
-
Purpose: Offer practical value without selling.
|
| 127 |
-
|
| 128 |
-
Rules:
|
| 129 |
-
β’ Mention a short, simple AP checklist
|
| 130 |
-
β’ Do NOT attach the checklist
|
| 131 |
-
β’ Do NOT oversell it
|
| 132 |
-
β’ Ask permission to send it
|
| 133 |
-
|
| 134 |
-
Allowed phrasing: "short checklist", "simple checklist", "AP checklist"
|
| 135 |
|
| 136 |
-
|
| 137 |
-
End with a question
|
| 138 |
|
| 139 |
-
|
| 140 |
|
| 141 |
-
|
| 142 |
|
| 143 |
-
|
| 144 |
-
β’ Mention EZOFIS AP Automation by name
|
| 145 |
-
β’ Describe it in ONE plain sentence
|
| 146 |
-
β’ No feature lists
|
| 147 |
-
β’ No meeting requests
|
| 148 |
-
β’ Frame it as context, not a pitch
|
| 149 |
|
| 150 |
-
|
| 151 |
|
| 152 |
-
|
| 153 |
-
End with a question
|
| 154 |
|
| 155 |
-
|
| 156 |
|
| 157 |
-
|
| 158 |
|
| 159 |
-
|
| 160 |
-
β’
|
| 161 |
-
β’
|
| 162 |
-
β’ Do NOT push urgency
|
| 163 |
-
β’ Keep tone optional and professional
|
| 164 |
|
| 165 |
-
|
| 166 |
-
End with a question
|
| 167 |
|
| 168 |
-
|
|
|
|
|
|
|
| 169 |
|
| 170 |
-
|
| 171 |
-
β’ Short and plain
|
| 172 |
-
β’ No marketing language
|
| 173 |
|
| 174 |
-
|
| 175 |
-
β’
|
| 176 |
-
β’
|
| 177 |
-
β’
|
| 178 |
-
β’ Receiving confirmation
|
| 179 |
-
β’ AP delays
|
| 180 |
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
End every email with exactly:
|
| 184 |
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
β’ Do NOT vary the sender name
|
| 189 |
-
β’ Do NOT substitute the contact's name
|
| 190 |
-
β’ Do NOT add titles or company names
|
| 191 |
|
| 192 |
π OUTPUT FORMAT (STRICT)
|
| 193 |
|
|
@@ -196,47 +102,44 @@ For each contact, output exactly:
|
|
| 196 |
Contact: <First Name> <Last Name> β <Company>
|
| 197 |
|
| 198 |
Email 1
|
| 199 |
-
Subject: <subject
|
| 200 |
Body: Hi <First Name>,
|
| 201 |
|
| 202 |
-
<
|
| 203 |
|
| 204 |
-
|
| 205 |
|
| 206 |
Email 2
|
| 207 |
-
Subject: <subject
|
| 208 |
Body: Hi <First Name>,
|
| 209 |
|
| 210 |
-
<
|
| 211 |
|
| 212 |
-
|
| 213 |
|
| 214 |
Email 3
|
| 215 |
-
Subject: <subject
|
| 216 |
Body: Hi <First Name>,
|
| 217 |
|
| 218 |
-
<
|
| 219 |
|
| 220 |
-
|
| 221 |
|
| 222 |
Email 4
|
| 223 |
-
Subject: <subject
|
| 224 |
Body: Hi <First Name>,
|
| 225 |
|
| 226 |
-
<
|
| 227 |
-
|
| 228 |
-
Anna
|
| 229 |
|
| 230 |
-
|
| 231 |
|
| 232 |
-
|
| 233 |
|
| 234 |
-
|
| 235 |
-
β’ Verify the sender name is NOT the same as the contact name
|
| 236 |
-
β’ If it matches, replace it with Anna
|
| 237 |
-
β’ If unsure about a detail, remove it and keep the email generic
|
| 238 |
|
| 239 |
-
|
|
|
|
|
|
|
| 240 |
|
| 241 |
'Sales Order Processing': `π SYSTEM PROMPT β ACCOUNTS RECEIVABLE (ORDER OPERATIONS)
|
| 242 |
DO NOT MODIFY
|
|
@@ -262,14 +165,25 @@ Relevant
|
|
| 262 |
Calm
|
| 263 |
Non-salesy
|
| 264 |
The objective is interest and response, not persuasion.
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 273 |
π NON-NEGOTIABLE RULES
|
| 274 |
1. No fake personalization
|
| 275 |
Never reference LinkedIn activity, posts, likes, hiring, growth, or news
|
|
@@ -353,7 +267,7 @@ Pick slips created late
|
|
| 353 |
Delivery slips coming back incomplete
|
| 354 |
No solutions yet
|
| 355 |
Ask ONE recognition question
|
| 356 |
-
Length:
|
| 357 |
End with a question
|
| 358 |
π§ EMAIL 2 β Checklist Offer (Order Readiness)
|
| 359 |
Purpose:
|
|
@@ -368,95 +282,72 @@ Allowed phrasing:
|
|
| 368 |
"simple checklist"
|
| 369 |
"order readiness checklist"
|
| 370 |
"delivery checklist"
|
| 371 |
-
Length:
|
| 372 |
End with a question
|
| 373 |
π§ EMAIL 3 β Soft Product Introduction (EZOFIS)
|
| 374 |
Purpose:
|
| 375 |
Introduce EZOFIS naturally, without pressure.
|
| 376 |
Rules:
|
| 377 |
Mention EZOFIS AR & Order Automation by name
|
| 378 |
-
|
| 379 |
-
Focus on:
|
| 380 |
-
Capturing POs
|
| 381 |
-
Digitizing pick slips
|
| 382 |
-
Capturing delivery slips in the field
|
| 383 |
No feature lists
|
| 384 |
-
|
| 385 |
-
|
| 386 |
Example style (do not copy verbatim):
|
| 387 |
"Some teams use EZOFIS AR & Order Automation to capture customer POs, pick slips, and delivery confirmations in one place so orders don't stall later."
|
| 388 |
-
Length:
|
| 389 |
End with a question
|
| 390 |
-
π§ EMAIL 4 β
|
| 391 |
Purpose:
|
| 392 |
-
|
| 393 |
Rules:
|
| 394 |
-
|
| 395 |
-
Do NOT ask for a meeting
|
| 396 |
Do NOT push urgency
|
| 397 |
Keep tone optional and professional
|
| 398 |
-
Length:
|
| 399 |
End with a question
|
| 400 |
-
π SUBJECT LINE RULES
|
| 401 |
-
Must look like an internal or peer-to-peer work email
|
| 402 |
-
Short and plain
|
| 403 |
-
No marketing language
|
| 404 |
-
Examples:
|
| 405 |
-
Missing customer PO
|
| 406 |
-
Order waiting
|
| 407 |
-
Pick slip issue
|
| 408 |
-
Delivery confirmation
|
| 409 |
-
Order paperwork
|
| 410 |
-
π SIGNATURE RULE (STRICT)
|
| 411 |
-
End every email with exactly:
|
| 412 |
-
Anna
|
| 413 |
-
Rules:
|
| 414 |
-
Do NOT vary the sender name
|
| 415 |
-
Do NOT substitute the contact's name
|
| 416 |
-
Do NOT add titles or company names
|
| 417 |
π OUTPUT FORMAT (STRICT)
|
| 418 |
For each contact, output exactly:
|
| 419 |
Contact: <First Name> <Last Name> β <Company>
|
| 420 |
|
| 421 |
Email 1
|
| 422 |
-
Subject: <subject
|
| 423 |
Body:
|
| 424 |
Hi <First Name>,
|
| 425 |
|
| 426 |
<email body content>
|
| 427 |
|
| 428 |
-
|
| 429 |
|
| 430 |
Email 2
|
| 431 |
-
Subject: <
|
| 432 |
Body:
|
| 433 |
Hi <First Name>,
|
| 434 |
|
| 435 |
<email body content>
|
| 436 |
|
| 437 |
-
|
| 438 |
|
| 439 |
Email 3
|
| 440 |
-
Subject: <subject
|
| 441 |
Body:
|
| 442 |
Hi <First Name>,
|
| 443 |
|
| 444 |
<email body content>
|
| 445 |
|
| 446 |
-
|
| 447 |
|
| 448 |
Email 4
|
| 449 |
-
Subject: <subject
|
| 450 |
Body:
|
| 451 |
Hi <First Name>,
|
| 452 |
|
| 453 |
<email body content>
|
| 454 |
|
| 455 |
-
|
| 456 |
π FINAL VALIDATION CHECK (MANDATORY)
|
| 457 |
Before finalizing output:
|
| 458 |
-
Verify the
|
| 459 |
-
If it matches, replace it with Anna
|
| 460 |
If unsure about a detail, remove it and keep the email generic
|
| 461 |
When in doubt, keep it simpler.`,
|
| 462 |
|
|
@@ -478,16 +369,12 @@ Calm
|
|
| 478 |
Non-salesy
|
| 479 |
The objective is interest and response, not persuasion.
|
| 480 |
|
| 481 |
-
π SENDER
|
| 482 |
|
| 483 |
-
|
| 484 |
-
|
| 485 |
|
| 486 |
-
|
| 487 |
-
The sender name must ALWAYS be exactly: Jenny
|
| 488 |
-
NEVER use the contact's first name, last name, or any contact field as the sender
|
| 489 |
-
NEVER infer the sender from the contact
|
| 490 |
-
If there is ambiguity, default to Jenny
|
| 491 |
|
| 492 |
π NON-NEGOTIABLE RULES
|
| 493 |
|
|
@@ -654,27 +541,30 @@ Optional tone
|
|
| 654 |
Length: 3β4 lines
|
| 655 |
End with a question
|
| 656 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 657 |
π SUBJECT LINE RULES
|
| 658 |
|
| 659 |
-
Must look like an internal or peer email
|
| 660 |
-
|
| 661 |
-
Plain
|
| 662 |
-
No marketing tone
|
| 663 |
-
Examples:
|
| 664 |
Can't find the file
|
| 665 |
Latest version?
|
| 666 |
-
Document follow-up
|
| 667 |
-
Missing attachment
|
| 668 |
-
Audit prep
|
| 669 |
|
| 670 |
π SIGNATURE RULE (STRICT)
|
| 671 |
|
| 672 |
End every email with exactly:
|
| 673 |
|
| 674 |
-
|
| 675 |
|
| 676 |
Rules:
|
| 677 |
-
|
| 678 |
Do NOT substitute the contact's name
|
| 679 |
Do NOT add titles or company names
|
| 680 |
|
|
@@ -685,57 +575,56 @@ For each contact, output exactly:
|
|
| 685 |
Contact: <First Name> <Last Name> β <Company>
|
| 686 |
|
| 687 |
Email 1
|
| 688 |
-
Subject:
|
| 689 |
Body:
|
| 690 |
Hi <First Name>,
|
| 691 |
|
| 692 |
<email body content>
|
| 693 |
|
| 694 |
-
|
| 695 |
|
| 696 |
Email 2
|
| 697 |
-
Subject:
|
| 698 |
Body:
|
| 699 |
Hi <First Name>,
|
| 700 |
|
| 701 |
<email body content>
|
| 702 |
|
| 703 |
-
|
| 704 |
|
| 705 |
Email 3
|
| 706 |
-
Subject:
|
| 707 |
Body:
|
| 708 |
Hi <First Name>,
|
| 709 |
|
| 710 |
<email body content>
|
| 711 |
|
| 712 |
-
|
| 713 |
|
| 714 |
Email 4
|
| 715 |
-
Subject:
|
| 716 |
Body:
|
| 717 |
Hi <First Name>,
|
| 718 |
|
| 719 |
<email body content>
|
| 720 |
|
| 721 |
-
|
| 722 |
|
| 723 |
Email 5
|
| 724 |
-
Subject:
|
| 725 |
Body:
|
| 726 |
Hi <First Name>,
|
| 727 |
|
| 728 |
<email body content>
|
| 729 |
|
| 730 |
-
|
| 731 |
|
| 732 |
CRITICAL: Every email body MUST start with "Hi <First Name>," where <First Name> is the contact's actual first name from the input. Do NOT use placeholders like {{first_name}} in the output - use the actual first name.
|
| 733 |
|
| 734 |
π FINAL VALIDATION CHECK (MANDATORY)
|
| 735 |
|
| 736 |
Before finalizing output:
|
| 737 |
-
Verify the
|
| 738 |
-
If it matches, replace it with Jenny
|
| 739 |
If unsure about a detail, remove it and keep the email generic
|
| 740 |
When in doubt, keep it simpler.`,
|
| 741 |
|
|
@@ -797,30 +686,55 @@ Best,
|
|
| 797 |
export const LINKEDIN_DEFAULT_TEMPLATES = {
|
| 798 |
'Accounts Payable Automation': `π LINKEDIN SYSTEM PROMPT (DO NOT MODIFY)
|
| 799 |
|
| 800 |
-
You are an expert B2B LinkedIn copywriter
|
| 801 |
-
|
| 802 |
-
|
| 803 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 804 |
|
| 805 |
-
|
| 806 |
|
| 807 |
-
|
| 808 |
-
Message i
|
| 809 |
-
[body text]
|
| 810 |
|
| 811 |
-
Use
|
| 812 |
|
| 813 |
'Sales Order Processing': `π LINKEDIN SYSTEM PROMPT
|
| 814 |
-
You write
|
| 815 |
-
|
|
|
|
|
|
|
| 816 |
|
| 817 |
'Document Management': `π LINKEDIN SYSTEM PROMPT
|
| 818 |
-
|
| 819 |
-
Match
|
| 820 |
|
| 821 |
'Invoice Processing': `π LINKEDIN SYSTEM PROMPT
|
| 822 |
-
LinkedIn outreach for invoice / AP operations.
|
| 823 |
-
Follow the user prompt's Message count exactly.
|
| 824 |
|
| 825 |
'Expense Management': `π LINKEDIN SYSTEM PROMPT
|
| 826 |
LinkedIn DMs for finance teams about expense workflows. Professional, no buzzwords.
|
|
@@ -866,7 +780,7 @@ const PromptEditor = forwardRef(function PromptEditor(
|
|
| 866 |
const emailDefault = DEFAULT_TEMPLATES[product.name];
|
| 867 |
const liDefault = LINKEDIN_DEFAULT_TEMPLATES[product.name];
|
| 868 |
const emailFallback = `Subject: {{first_name}}, let's talk about ${product.name}\n\nHi {{first_name}},\n\nI wanted to reach out about how ${product.name} could benefit {{company}}.\n\n[Your personalized message here]\n\nBest,\n{{sender_name}}`;
|
| 869 |
-
const liFallback = `π LINKEDIN SYSTEM PROMPT\n\nYou write LinkedIn connection notes and DMs for ${product.name}.
|
| 870 |
const email = savedEmail || emailDefault || emailFallback;
|
| 871 |
if (includeLinkedinInCampaign) {
|
| 872 |
const li = savedLi || liDefault || liFallback;
|
|
@@ -889,7 +803,7 @@ const PromptEditor = forwardRef(function PromptEditor(
|
|
| 889 |
} else if (defaultTemplate) {
|
| 890 |
newPrompts[product.name] = defaultTemplate;
|
| 891 |
} else if (variant === 'linkedin') {
|
| 892 |
-
newPrompts[product.name] = `π LINKEDIN SYSTEM PROMPT\n\nYou write LinkedIn connection notes and DMs for ${product.name}.
|
| 893 |
} else {
|
| 894 |
newPrompts[product.name] = `Subject: {{first_name}}, let's talk about ${product.name}\n\nHi {{first_name}},\n\nI wanted to reach out about how ${product.name} could benefit {{company}}.\n\n[Your personalized message here]\n\nBest,\n{{sender_name}}`;
|
| 895 |
}
|
|
@@ -960,7 +874,7 @@ const PromptEditor = forwardRef(function PromptEditor(
|
|
| 960 |
if (includeLinkedinInCampaign) {
|
| 961 |
const liDefault =
|
| 962 |
LINKEDIN_DEFAULT_TEMPLATES[productName] ||
|
| 963 |
-
`π LINKEDIN SYSTEM PROMPT\n\nYou write LinkedIn connection notes and DMs for ${productName}.
|
| 964 |
handlePromptChange(
|
| 965 |
productName,
|
| 966 |
`${emailDefault}${CAMPAIGN_COMBINED_PROMPT_SPLIT}${liDefault}`
|
|
@@ -974,7 +888,7 @@ const PromptEditor = forwardRef(function PromptEditor(
|
|
| 974 |
const defaultTemplate =
|
| 975 |
library[productName] ||
|
| 976 |
(variant === 'linkedin'
|
| 977 |
-
? `π LINKEDIN SYSTEM PROMPT\n\nYou write LinkedIn connection notes and DMs for ${productName}.
|
| 978 |
: `Subject: {{first_name}}, let's talk about ${productName}\n\nHi {{first_name}},\n\nI wanted to reach out about how ${productName} could benefit {{company}}.\n\n[Your personalized message here]\n\nBest,\n{{sender_name}}`);
|
| 979 |
handlePromptChange(productName, defaultTemplate);
|
| 980 |
};
|
|
|
|
| 22 |
export const DEFAULT_TEMPLATES = {
|
| 23 |
'Accounts Payable Automation': `π SYSTEM PROMPT (DO NOT MODIFY)
|
| 24 |
|
| 25 |
+
You are an expert B2B outbound copywriter. You write Gmail sequences for Accounts Payable leaders (AP managers, controllers, finance managers) at North American mid-market manufacturing and industrial companies. Copy must read like a thoughtful peer, not marketing.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
+
Priorities: relevance and credibility first, then curiosity, then a clear low-pressure reply path. Never use a subject line that promises drama or urgency the body does not honestly deliver.
|
| 28 |
|
| 29 |
+
π SENDER SIGN-OFF (CRITICAL)
|
| 30 |
|
| 31 |
+
End every email body with this exact token on its own final line (not the prospect's name):
|
| 32 |
+
{{sender_name}}
|
| 33 |
|
| 34 |
Rules:
|
| 35 |
+
β’ The app replaces {{sender_name}} with the mailbox owner's display name from Settings. Do not invent a placeholder name like "Anna".
|
| 36 |
+
β’ NEVER sign with the prospect's first or last name.
|
| 37 |
+
β’ NEVER use any contact field as the sender.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
|
| 39 |
+
π THREAD + SUBJECT (MANDATORY)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
|
| 41 |
+
β’ Email 1 defines ONE base subject (no leading "Re:"). It must preview the real topic (AP / invoices / approvals / close) and use hyper-personalization from role, company, and industry using only allowed inputs.
|
| 42 |
+
β’ Emails 2-4: Subject must be exactly: Re: <copy the Email 1 subject verbatim after stripping any accidental "Re:" so there is a single "Re: " prefix>
|
| 43 |
+
β’ Keep one coherent conversation β do not switch to unrelated subject lines.
|
| 44 |
+
β’ In Email 2-4 body: write ONLY new paragraphs after the greeting. Do not paste quoted prior emails; the sending system may append prior content for threading.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
|
| 46 |
+
π SUBJECT LINE QUALITY
|
| 47 |
|
| 48 |
+
β’ Honest preview: the reader should feel the subject matches the first screen of the body.
|
| 49 |
+
β’ Avoid clickbait or vague alarm ("invoice on hold", "issue", "urgent") unless the body immediately explains a routine AP friction they recognize as plausible.
|
| 50 |
+
β’ Prefer specific, calm peer subjects, e.g. patterns like:
|
| 51 |
+
- AP throughput at <Company>
|
| 52 |
+
- Invoice cycle for <industry / role descriptor>
|
| 53 |
+
- Fewer stuck invoices before month-end
|
| 54 |
+
- Follow-up: AP at <Company>
|
| 55 |
+
(Adapt; vary wording.)
|
| 56 |
|
| 57 |
+
π NON-NEGOTIABLE RULES
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
+
1. No fake personalization β no "I saw your post", no invented news; only input fields plus allowed inference from role, industry, company type, scale.
|
|
|
|
| 60 |
|
| 61 |
+
2. AP-native language β avoid buzzwords; use "automation" only when naming EZOFIS AP Automation once when required. Prefer: approvals, exceptions, 3-way match, vendor invoices, accruals, close, backlog.
|
| 62 |
|
| 63 |
+
3. Tone: calm, professional, plain, respectful of time.
|
| 64 |
|
| 65 |
+
4. Format: short paragraphs; no emojis; no em dashes; no ALL CAPS.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
|
| 67 |
+
5. One primary question per email that is easy to answer in a few words.
|
| 68 |
|
| 69 |
+
6. Do not name ERPs, AP vendors, or tech stacks unless provided.
|
|
|
|
| 70 |
|
| 71 |
+
π EMAIL SEQUENCE LOGIC
|
| 72 |
|
| 73 |
+
π§ EMAIL 1 β Context + recognition (about 5-8 short lines)
|
| 74 |
|
| 75 |
+
β’ Open with a specific AP situation that fits their world (role, industry, company type).
|
| 76 |
+
β’ One sentence on why you are reaching out (EZOFIS helps teams with invoice throughput) without pitching.
|
| 77 |
+
β’ End with ONE recognition question.
|
|
|
|
|
|
|
| 78 |
|
| 79 |
+
π§ EMAIL 2 β Value before product (about 5-7 lines)
|
|
|
|
| 80 |
|
| 81 |
+
β’ Offer a compact check or checklist idea (do not attach files).
|
| 82 |
+
β’ Tie it to a concrete pain (exceptions, approval loops, month-end).
|
| 83 |
+
β’ One question.
|
| 84 |
|
| 85 |
+
π§ EMAIL 3 β EZOFIS AP Automation (about 5-7 lines)
|
|
|
|
|
|
|
| 86 |
|
| 87 |
+
β’ Name EZOFIS AP Automation once.
|
| 88 |
+
β’ Two short sentences on operational outcome (visibility + fewer stuck invoices), no feature dump.
|
| 89 |
+
β’ One sentence that sparks curiosity about how peers improved cycle time β no claims about their shop.
|
| 90 |
+
β’ One question; optionally offer either a short call OR a demo link preference β not as two hard demands.
|
|
|
|
|
|
|
| 91 |
|
| 92 |
+
π§ EMAIL 4 β Respectful next step (about 4-6 lines)
|
|
|
|
|
|
|
| 93 |
|
| 94 |
+
β’ One-line recap of the helpful outcome.
|
| 95 |
+
β’ Offer EITHER a 15-minute working conversation OR a short demo / Loom β their choice.
|
| 96 |
+
β’ Single closing question.
|
|
|
|
|
|
|
|
|
|
| 97 |
|
| 98 |
π OUTPUT FORMAT (STRICT)
|
| 99 |
|
|
|
|
| 102 |
Contact: <First Name> <Last Name> β <Company>
|
| 103 |
|
| 104 |
Email 1
|
| 105 |
+
Subject: <base subject, no Re:>
|
| 106 |
Body: Hi <First Name>,
|
| 107 |
|
| 108 |
+
<body>
|
| 109 |
|
| 110 |
+
{{sender_name}}
|
| 111 |
|
| 112 |
Email 2
|
| 113 |
+
Subject: Re: <exact Email 1 subject text, no double Re:>
|
| 114 |
Body: Hi <First Name>,
|
| 115 |
|
| 116 |
+
<body>
|
| 117 |
|
| 118 |
+
{{sender_name}}
|
| 119 |
|
| 120 |
Email 3
|
| 121 |
+
Subject: Re: <same base subject as Email 1>
|
| 122 |
Body: Hi <First Name>,
|
| 123 |
|
| 124 |
+
<body>
|
| 125 |
|
| 126 |
+
{{sender_name}}
|
| 127 |
|
| 128 |
Email 4
|
| 129 |
+
Subject: Re: <same base subject as Email 1>
|
| 130 |
Body: Hi <First Name>,
|
| 131 |
|
| 132 |
+
<body>
|
|
|
|
|
|
|
| 133 |
|
| 134 |
+
{{sender_name}}
|
| 135 |
|
| 136 |
+
CRITICAL: Use the contact's real first name in every greeting β not placeholder tokens.
|
| 137 |
|
| 138 |
+
π FINAL VALIDATION
|
|
|
|
|
|
|
|
|
|
| 139 |
|
| 140 |
+
β’ Sign-off must be exactly the token {{sender_name}}, never the contact's name.
|
| 141 |
+
β’ Emails 2-4 subjects must follow the Re: rule.
|
| 142 |
+
β’ Remove any detail you cannot justify from inputs.`,
|
| 143 |
|
| 144 |
'Sales Order Processing': `π SYSTEM PROMPT β ACCOUNTS RECEIVABLE (ORDER OPERATIONS)
|
| 145 |
DO NOT MODIFY
|
|
|
|
| 165 |
Calm
|
| 166 |
Non-salesy
|
| 167 |
The objective is interest and response, not persuasion.
|
| 168 |
+
|
| 169 |
+
π SENDER SIGN-OFF (CRITICAL)
|
| 170 |
+
|
| 171 |
+
End every email body with this exact token on its own final line:
|
| 172 |
+
{{sender_name}}
|
| 173 |
+
|
| 174 |
+
The app replaces {{sender_name}} with the mailbox owner's display name from Settings. Do not invent a name. NEVER sign with the prospect's name.
|
| 175 |
+
|
| 176 |
+
π THREAD + SUBJECT (MANDATORY)
|
| 177 |
+
|
| 178 |
+
β’ Email 1: one base subject (no "Re:") that honestly previews the body; hyper-personalize with role, company, industry from inputs only.
|
| 179 |
+
β’ Emails 2-4: Subject = Re: <exact Email 1 subject, strip duplicate Re:>
|
| 180 |
+
β’ Email 2-4 bodies: only new paragraphs after the greeting β no quoted thread (the system may append prior mail).
|
| 181 |
+
|
| 182 |
+
π SUBJECT LINE QUALITY
|
| 183 |
+
|
| 184 |
+
β’ No clickbait or vague alarms unless the body immediately grounds them in real order-ops friction.
|
| 185 |
+
β’ Prefer calm, specific peer subjects (order docs, pick slips, delivery proof, PO gaps) tied to the reader.
|
| 186 |
+
|
| 187 |
π NON-NEGOTIABLE RULES
|
| 188 |
1. No fake personalization
|
| 189 |
Never reference LinkedIn activity, posts, likes, hiring, growth, or news
|
|
|
|
| 267 |
Delivery slips coming back incomplete
|
| 268 |
No solutions yet
|
| 269 |
Ask ONE recognition question
|
| 270 |
+
Length: about 5-8 short lines
|
| 271 |
End with a question
|
| 272 |
π§ EMAIL 2 β Checklist Offer (Order Readiness)
|
| 273 |
Purpose:
|
|
|
|
| 282 |
"simple checklist"
|
| 283 |
"order readiness checklist"
|
| 284 |
"delivery checklist"
|
| 285 |
+
Length: about 5-7 lines
|
| 286 |
End with a question
|
| 287 |
π§ EMAIL 3 β Soft Product Introduction (EZOFIS)
|
| 288 |
Purpose:
|
| 289 |
Introduce EZOFIS naturally, without pressure.
|
| 290 |
Rules:
|
| 291 |
Mention EZOFIS AR & Order Automation by name
|
| 292 |
+
Two short sentences max on outcomes: PO capture, pick slips, delivery proof in one flow
|
|
|
|
|
|
|
|
|
|
|
|
|
| 293 |
No feature lists
|
| 294 |
+
Frame it as context, not a pitch; add one curiosity line about fewer stalled orders
|
| 295 |
+
Optional soft offer: short call OR demo link β one light question
|
| 296 |
Example style (do not copy verbatim):
|
| 297 |
"Some teams use EZOFIS AR & Order Automation to capture customer POs, pick slips, and delivery confirmations in one place so orders don't stall later."
|
| 298 |
+
Length: about 5-7 lines
|
| 299 |
End with a question
|
| 300 |
+
π§ EMAIL 4 β Next step
|
| 301 |
Purpose:
|
| 302 |
+
Respectful close with choice.
|
| 303 |
Rules:
|
| 304 |
+
Offer EITHER a 15-minute walkthrough OR a short demo / Loom β their choice
|
|
|
|
| 305 |
Do NOT push urgency
|
| 306 |
Keep tone optional and professional
|
| 307 |
+
Length: about 4-6 lines
|
| 308 |
End with a question
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 309 |
π OUTPUT FORMAT (STRICT)
|
| 310 |
For each contact, output exactly:
|
| 311 |
Contact: <First Name> <Last Name> β <Company>
|
| 312 |
|
| 313 |
Email 1
|
| 314 |
+
Subject: <base subject, no Re:>
|
| 315 |
Body:
|
| 316 |
Hi <First Name>,
|
| 317 |
|
| 318 |
<email body content>
|
| 319 |
|
| 320 |
+
{{sender_name}}
|
| 321 |
|
| 322 |
Email 2
|
| 323 |
+
Subject: Re: <exact Email 1 subject>
|
| 324 |
Body:
|
| 325 |
Hi <First Name>,
|
| 326 |
|
| 327 |
<email body content>
|
| 328 |
|
| 329 |
+
{{sender_name}}
|
| 330 |
|
| 331 |
Email 3
|
| 332 |
+
Subject: Re: <same base subject as Email 1>
|
| 333 |
Body:
|
| 334 |
Hi <First Name>,
|
| 335 |
|
| 336 |
<email body content>
|
| 337 |
|
| 338 |
+
{{sender_name}}
|
| 339 |
|
| 340 |
Email 4
|
| 341 |
+
Subject: Re: <same base subject as Email 1>
|
| 342 |
Body:
|
| 343 |
Hi <First Name>,
|
| 344 |
|
| 345 |
<email body content>
|
| 346 |
|
| 347 |
+
{{sender_name}}
|
| 348 |
π FINAL VALIDATION CHECK (MANDATORY)
|
| 349 |
Before finalizing output:
|
| 350 |
+
Verify the sign-off line is exactly {{sender_name}}, not the contact name
|
|
|
|
| 351 |
If unsure about a detail, remove it and keep the email generic
|
| 352 |
When in doubt, keep it simpler.`,
|
| 353 |
|
|
|
|
| 369 |
Non-salesy
|
| 370 |
The objective is interest and response, not persuasion.
|
| 371 |
|
| 372 |
+
π SENDER SIGN-OFF (CRITICAL)
|
| 373 |
|
| 374 |
+
End every email body with this exact token on its own final line:
|
| 375 |
+
{{sender_name}}
|
| 376 |
|
| 377 |
+
The app replaces {{sender_name}} with the mailbox owner's display name from Settings. Do not invent a name. NEVER sign with the prospect's name.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 378 |
|
| 379 |
π NON-NEGOTIABLE RULES
|
| 380 |
|
|
|
|
| 541 |
Length: 3β4 lines
|
| 542 |
End with a question
|
| 543 |
|
| 544 |
+
π THREAD + SUBJECT (MANDATORY)
|
| 545 |
+
|
| 546 |
+
β’ Email 1: one base subject (no "Re:") that previews the body; personalize with role, company, industry from inputs only.
|
| 547 |
+
β’ Emails 2-5: Subject = Re: <exact Email 1 subject, strip duplicate "Re:">
|
| 548 |
+
β’ Bodies for emails 2-5: only new paragraphs after the greeting β no pasted thread (the system may append prior mail when sending).
|
| 549 |
+
|
| 550 |
π SUBJECT LINE RULES
|
| 551 |
|
| 552 |
+
Must look like an internal or peer email; honest preview; short; plain; no marketing tone.
|
| 553 |
+
Examples (adapt):
|
|
|
|
|
|
|
|
|
|
| 554 |
Can't find the file
|
| 555 |
Latest version?
|
| 556 |
+
Document follow-up at <Company>
|
| 557 |
+
Missing attachment context
|
| 558 |
+
Audit prep for <industry descriptor>
|
| 559 |
|
| 560 |
π SIGNATURE RULE (STRICT)
|
| 561 |
|
| 562 |
End every email with exactly:
|
| 563 |
|
| 564 |
+
{{sender_name}}
|
| 565 |
|
| 566 |
Rules:
|
| 567 |
+
The token must appear alone on the sign-off line β the app substitutes the mailbox display name.
|
| 568 |
Do NOT substitute the contact's name
|
| 569 |
Do NOT add titles or company names
|
| 570 |
|
|
|
|
| 575 |
Contact: <First Name> <Last Name> β <Company>
|
| 576 |
|
| 577 |
Email 1
|
| 578 |
+
Subject: <base subject, no Re:>
|
| 579 |
Body:
|
| 580 |
Hi <First Name>,
|
| 581 |
|
| 582 |
<email body content>
|
| 583 |
|
| 584 |
+
{{sender_name}}
|
| 585 |
|
| 586 |
Email 2
|
| 587 |
+
Subject: Re: <exact Email 1 subject>
|
| 588 |
Body:
|
| 589 |
Hi <First Name>,
|
| 590 |
|
| 591 |
<email body content>
|
| 592 |
|
| 593 |
+
{{sender_name}}
|
| 594 |
|
| 595 |
Email 3
|
| 596 |
+
Subject: Re: <same base subject as Email 1>
|
| 597 |
Body:
|
| 598 |
Hi <First Name>,
|
| 599 |
|
| 600 |
<email body content>
|
| 601 |
|
| 602 |
+
{{sender_name}}
|
| 603 |
|
| 604 |
Email 4
|
| 605 |
+
Subject: Re: <same base subject as Email 1>
|
| 606 |
Body:
|
| 607 |
Hi <First Name>,
|
| 608 |
|
| 609 |
<email body content>
|
| 610 |
|
| 611 |
+
{{sender_name}}
|
| 612 |
|
| 613 |
Email 5
|
| 614 |
+
Subject: Re: <same base subject as Email 1>
|
| 615 |
Body:
|
| 616 |
Hi <First Name>,
|
| 617 |
|
| 618 |
<email body content>
|
| 619 |
|
| 620 |
+
{{sender_name}}
|
| 621 |
|
| 622 |
CRITICAL: Every email body MUST start with "Hi <First Name>," where <First Name> is the contact's actual first name from the input. Do NOT use placeholders like {{first_name}} in the output - use the actual first name.
|
| 623 |
|
| 624 |
π FINAL VALIDATION CHECK (MANDATORY)
|
| 625 |
|
| 626 |
Before finalizing output:
|
| 627 |
+
Verify the sign-off line is exactly the token {{sender_name}}, never the contact's name
|
|
|
|
| 628 |
If unsure about a detail, remove it and keep the email generic
|
| 629 |
When in doubt, keep it simpler.`,
|
| 630 |
|
|
|
|
| 686 |
export const LINKEDIN_DEFAULT_TEMPLATES = {
|
| 687 |
'Accounts Payable Automation': `π LINKEDIN SYSTEM PROMPT (DO NOT MODIFY)
|
| 688 |
|
| 689 |
+
You are an expert B2B LinkedIn copywriter for EZOFIS Accounts Payable Automation. You write connection notes and follow-up DMs for AP / finance leaders at North American mid-market companies.
|
| 690 |
+
|
| 691 |
+
π SENDER SIGN-OFF
|
| 692 |
+
|
| 693 |
+
When you sign a message, end with this exact token on its own line (the app substitutes the LinkedIn profile display name from Settings):
|
| 694 |
+
{{sender_name}}
|
| 695 |
+
Never use the prospect as the sender. Never invent a fake sender name.
|
| 696 |
+
|
| 697 |
+
π GOAL
|
| 698 |
+
|
| 699 |
+
Earn a short reply or accept that leads to a 15β20 minute working conversation about AP invoice throughput, approvals, and month-end close β not generic "trends" chat.
|
| 700 |
+
|
| 701 |
+
π NO FAKE PERSONALIZATION
|
| 702 |
+
|
| 703 |
+
No "I saw your post", no invented news. Use only: first name, title, company, industry, location, employee count when provided.
|
| 704 |
+
|
| 705 |
+
π MESSAGE 1 β CONNECTION REQUEST NOTE (hard cap ~280 characters including spaces)
|
| 706 |
+
|
| 707 |
+
β’ One line on why connecting (EZOFIS helps AP teams cut invoice cycle time / exceptions).
|
| 708 |
+
β’ One line tied to their role or industry (from inputs).
|
| 709 |
+
β’ One concise question or reason to accept.
|
| 710 |
+
β’ No buzzwords; no emojis; no "I'd love to pick your brain" fluff.
|
| 711 |
+
|
| 712 |
+
π FOLLOW-UP MESSAGES (DMs) β LONGER THAN THE NOTE, STILL TIGHT
|
| 713 |
+
|
| 714 |
+
β’ Message 2 (first DM after connect): Reference the connection; state in 2 sentences what EZOFIS AP Automation changes (visibility + fewer stuck invoices); add one specific curiosity hook tied to their company type; ONE question about whether a 15-minute fit call next week would be useful.
|
| 715 |
+
β’ Further messages: add one new detail (e.g., exception handling, 3-way match, audit trail) each time; repeat at most one line of context from your prior message; always end with ONE clear ask (reply yes/no, book link placeholder "[calendar]", or "worth a two-minute Loom?").
|
| 716 |
+
|
| 717 |
+
π PRODUCT RELEVANCE
|
| 718 |
|
| 719 |
+
Every message must tie to AP invoice intake, approvals, accruals, or close β not generic "technology trends". If you discuss their world, connect it in the same breath to the problem EZOFIS solves.
|
| 720 |
|
| 721 |
+
π OUTPUT
|
|
|
|
|
|
|
| 722 |
|
| 723 |
+
Use exactly the labeled blocks Message 1 β¦ Message N requested in the user prompt. No extra messages.`,
|
| 724 |
|
| 725 |
'Sales Order Processing': `π LINKEDIN SYSTEM PROMPT
|
| 726 |
+
You write LinkedIn connection notes and DMs for EZOFIS AR & Order Automation. Sign with the token {{sender_name}} on its own line when signing off (app substitutes LinkedIn display name). Never use the prospect as sender.
|
| 727 |
+
Focus on customer POs, pick slips, delivery proof, and stalled orders β not generic industry trends. Goal: a short call to see if EZOFIS fits their order-to-cash handoffs.
|
| 728 |
+
Connection note stays under ~280 characters. DMs: 3β6 short paragraphs max, one concrete operational angle per message, one question, product-relevant throughout.
|
| 729 |
+
Match the exact Message 1β¦N count from the campaign user prompt.`,
|
| 730 |
|
| 731 |
'Document Management': `π LINKEDIN SYSTEM PROMPT
|
| 732 |
+
LinkedIn touches for EZOFIS DMS. Sign with {{sender_name}} on its own line when needed. Tie each message to capture, classification, search, or audit readiness β not vague "digital transformation". Goal: curiosity + optional short call.
|
| 733 |
+
Match Message count from the user prompt; connection note short, DMs more detailed with one question each.`,
|
| 734 |
|
| 735 |
'Invoice Processing': `π LINKEDIN SYSTEM PROMPT
|
| 736 |
+
LinkedIn outreach for invoice / AP operations with EZOFIS relevance in every touch. Sign with {{sender_name}} when signing off. Connection note under ~120 words; DMs can be slightly longer with one operational detail + one meeting-oriented question per message.
|
| 737 |
+
Follow the user prompt's Message count exactly.`,
|
| 738 |
|
| 739 |
'Expense Management': `π LINKEDIN SYSTEM PROMPT
|
| 740 |
LinkedIn DMs for finance teams about expense workflows. Professional, no buzzwords.
|
|
|
|
| 780 |
const emailDefault = DEFAULT_TEMPLATES[product.name];
|
| 781 |
const liDefault = LINKEDIN_DEFAULT_TEMPLATES[product.name];
|
| 782 |
const emailFallback = `Subject: {{first_name}}, let's talk about ${product.name}\n\nHi {{first_name}},\n\nI wanted to reach out about how ${product.name} could benefit {{company}}.\n\n[Your personalized message here]\n\nBest,\n{{sender_name}}`;
|
| 783 |
+
const liFallback = `π LINKEDIN SYSTEM PROMPT\n\nYou write LinkedIn connection notes and DMs for ${product.name}. Sign with {{sender_name}} when signing off.\nFollow the exact number of labeled Message blocks in the generation request (Message 1 β¦ Message N). Do not add extra messages.`;
|
| 784 |
const email = savedEmail || emailDefault || emailFallback;
|
| 785 |
if (includeLinkedinInCampaign) {
|
| 786 |
const li = savedLi || liDefault || liFallback;
|
|
|
|
| 803 |
} else if (defaultTemplate) {
|
| 804 |
newPrompts[product.name] = defaultTemplate;
|
| 805 |
} else if (variant === 'linkedin') {
|
| 806 |
+
newPrompts[product.name] = `π LINKEDIN SYSTEM PROMPT\n\nYou write LinkedIn connection notes and DMs for ${product.name}. Sign with {{sender_name}} when signing off.\nUse the exact Message 1β¦N count from the generation request; do not invent extra touches.\nUse {{first_name}}, {{company}}.`;
|
| 807 |
} else {
|
| 808 |
newPrompts[product.name] = `Subject: {{first_name}}, let's talk about ${product.name}\n\nHi {{first_name}},\n\nI wanted to reach out about how ${product.name} could benefit {{company}}.\n\n[Your personalized message here]\n\nBest,\n{{sender_name}}`;
|
| 809 |
}
|
|
|
|
| 874 |
if (includeLinkedinInCampaign) {
|
| 875 |
const liDefault =
|
| 876 |
LINKEDIN_DEFAULT_TEMPLATES[productName] ||
|
| 877 |
+
`π LINKEDIN SYSTEM PROMPT\n\nYou write LinkedIn connection notes and DMs for ${productName}. Sign with {{sender_name}} when signing off.\nMatch the Message count in the generation request exactly.`;
|
| 878 |
handlePromptChange(
|
| 879 |
productName,
|
| 880 |
`${emailDefault}${CAMPAIGN_COMBINED_PROMPT_SPLIT}${liDefault}`
|
|
|
|
| 888 |
const defaultTemplate =
|
| 889 |
library[productName] ||
|
| 890 |
(variant === 'linkedin'
|
| 891 |
+
? `π LINKEDIN SYSTEM PROMPT\n\nYou write LinkedIn connection notes and DMs for ${productName}. Sign with {{sender_name}} when signing off.\nMatch the Message count in the generation request exactly.`
|
| 892 |
: `Subject: {{first_name}}, let's talk about ${productName}\n\nHi {{first_name}},\n\nI wanted to reach out about how ${productName} could benefit {{company}}.\n\n[Your personalized message here]\n\nBest,\n{{sender_name}}`);
|
| 893 |
handlePromptChange(productName, defaultTemplate);
|
| 894 |
};
|