blog and backlinks
This commit is contained in:
47
tmp/clean_articles.py
Normal file
47
tmp/clean_articles.py
Normal file
@@ -0,0 +1,47 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
def clean_articles(directory):
|
||||
for filename in os.listdir(directory):
|
||||
if filename.endswith(".md"):
|
||||
path = os.path.join(directory, filename)
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
# Remove *Target: ...* line
|
||||
content = re.sub(r"^\*Target:.*?\*[\r\n]*", "", content, flags=re.MULTILINE)
|
||||
|
||||
# Remove footer metadata starting with bolded targets or notes
|
||||
# Usually starts after the last separator --- or near the end
|
||||
# Patterns to remove:
|
||||
# - Word count target: ...
|
||||
# - Internal links to add: ...
|
||||
# - Note: AI-assisted draft ...
|
||||
# - Author bio: ... (We might want to keep author bio, but the user said "draft doesn't look good",
|
||||
# so let's remove the "meta" parts and keep only the content.)
|
||||
|
||||
# Remove specific lines
|
||||
content = re.sub(r"\*\*Word count target:\*\*.*", "", content)
|
||||
content = re.sub(r"\*\*Internal links to add:\*\*.*", "", content)
|
||||
content = re.sub(r"\*\*Author bio:\*\*.*", "", content)
|
||||
content = re.sub(r"\*\*Note:\*\* AI-assisted draft.*", "", content)
|
||||
|
||||
# Also catch these patterns without bold
|
||||
content = re.sub(r"\*Target:.*", "", content)
|
||||
content = re.sub(r"Word count target:.*", "", content)
|
||||
content = re.sub(r"Internal links to add:.*", "", content)
|
||||
content = re.sub(r"Author bio:.*", "", content)
|
||||
content = re.sub(r"Note: AI-assisted draft.*", "", content)
|
||||
content = re.sub(r"Screenshots to include:.*", "", content)
|
||||
|
||||
# Clean up trailing whitespace and empty separators at the end
|
||||
content = content.replace("---", "\n---\n") # Ensure space around separators
|
||||
content = re.sub(r"---[\s\n]*$", "", content) # Remove trailing separators
|
||||
content = content.strip()
|
||||
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
print(f"Cleaned {filename}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
clean_articles(r"c:\Users\a931627\Documents\QRMASTER\articles")
|
||||
BIN
tmp/email_log.txt
Normal file
BIN
tmp/email_log.txt
Normal file
Binary file not shown.
117
tmp/send_all_3_email_pitches.py
Normal file
117
tmp/send_all_3_email_pitches.py
Normal file
@@ -0,0 +1,117 @@
|
||||
import smtplib
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.image import MIMEImage
|
||||
import os
|
||||
import markdown
|
||||
import logging
|
||||
import re
|
||||
|
||||
# Setup logging
|
||||
logging.basicConfig(filename=r'c:\Users\a931627\Documents\QRMASTER\tmp\script_log.log', level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s', force=True)
|
||||
|
||||
def get_pitch_data():
|
||||
return [
|
||||
{
|
||||
"filename": "digitalGpoint-dynamic-vs-static-qr-codes.md",
|
||||
"subject": "Pitch: Solving the 'print gap' for small businesses (DigitalGpoint)",
|
||||
"to_email": "digitalgpoint.webmail@gmail.com",
|
||||
"intro": "Hi DigitalGpoint Team,<br><br>I’ve noticed DigitalGpoint covers a lot of practical tools for business growth—especially those that bridge the gap between offline operations and digital simplicity. My latest draft fits right into this space."
|
||||
},
|
||||
{
|
||||
"filename": "techdee-5-qr-code-strategies.md",
|
||||
"subject": "Pitch: Offline-to-Online marketing workflows (Techdee)",
|
||||
"to_email": "Blayget@gmail.com",
|
||||
"intro": "Hi Techdee Team,<br><br>I’ve been following Techdee’s tech and marketing tutorials for a while. Your audience seems to really value actionable, tech-forward tactics that small businesses can actually execute. This piece on QR strategies was written with that utility in mind."
|
||||
},
|
||||
{
|
||||
"filename": "seosandwitch-qr-codes-offline-attribution.md",
|
||||
"subject": "Pitch: Attribution blind spots in physical marketing (SEO Sandwitch)",
|
||||
"to_email": "joydeep@seosandwitch.com",
|
||||
"intro": "Hi Joydeep,<br><br>I’ve been working on a technical deep-dive into how QR codes can bridge the offline attribution gap in local SEO. Given your focus on technical search strategies, I thought this would be a perfect fit for SEO Sandwitch."
|
||||
}
|
||||
]
|
||||
|
||||
def send_premium_email(pitch_data):
|
||||
smtp_host = "smtp.qrmaster.net"
|
||||
smtp_port = 465
|
||||
smtp_user = "timo@qrmaster.net"
|
||||
smtp_pass = "fiesta"
|
||||
bcc_email = "knuth.timo@gmail.com"
|
||||
articles_dir = r"c:\Users\a931627\Documents\QRMASTER\articles"
|
||||
images_dir = r"c:\Users\a931627\Documents\QRMASTER\assets\images"
|
||||
|
||||
logging.info(f"Starting real email dispatch for the {len(pitch_data)} guest post pitches.")
|
||||
|
||||
for item in pitch_data:
|
||||
to_email = item["to_email"]
|
||||
file_path = os.path.join(articles_dir, item["filename"])
|
||||
if not os.path.exists(file_path):
|
||||
logging.error(f"File not found: {item['filename']}")
|
||||
continue
|
||||
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
article_content = f.read()
|
||||
|
||||
article_html = markdown.markdown(article_content, extensions=['tables', 'fenced_code', 'nl2br'])
|
||||
|
||||
# Find images and prepare attachments
|
||||
img_tags = re.findall(r'<img [^>]*src="([^"]+)"[^>]*>', article_html)
|
||||
attachments = []
|
||||
for img_src in img_tags:
|
||||
img_filename = os.path.basename(img_src)
|
||||
img_path = os.path.join(images_dir, img_filename)
|
||||
|
||||
if os.path.exists(img_path):
|
||||
cid = img_filename.replace(".", "_")
|
||||
article_html = article_html.replace(img_src, f"cid:{cid}")
|
||||
|
||||
with open(img_path, "rb") as img_f:
|
||||
img_data = img_f.read()
|
||||
mime_img = MIMEImage(img_data)
|
||||
mime_img.add_header('Content-ID', f'<{cid}>')
|
||||
mime_img.add_header('Content-Disposition', 'inline', filename=img_filename)
|
||||
attachments.append(mime_img)
|
||||
else:
|
||||
logging.warning(f"Image not found: {img_path}")
|
||||
|
||||
html_template = f"""
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif;">
|
||||
<div style="max-width: 600px; margin: auto; padding: 20px; border: 1px solid #ccc;">
|
||||
<div style="background: #f4f4f4; padding: 15px; margin-bottom: 20px;">{item["intro"]}</div>
|
||||
<div>{article_html}</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
message = MIMEMultipart("related")
|
||||
message["From"] = smtp_user
|
||||
message["To"] = to_email
|
||||
message["Bcc"] = bcc_email
|
||||
message["Subject"] = item["subject"]
|
||||
|
||||
msg_alternative = MIMEMultipart("alternative")
|
||||
message.attach(msg_alternative)
|
||||
msg_alternative.attach(MIMEText(html_template, "html"))
|
||||
|
||||
for att in attachments:
|
||||
message.attach(att)
|
||||
|
||||
try:
|
||||
with smtplib.SMTP_SSL(smtp_host, smtp_port) as server:
|
||||
server.login(smtp_user, smtp_pass)
|
||||
# Send to To and Bcc
|
||||
recipients = [to_email, bcc_email]
|
||||
server.sendmail(smtp_user, recipients, message.as_string())
|
||||
logging.info(f"Successfully sent: {item['filename']} to {to_email} (BCC'd {bcc_email})")
|
||||
print(f"SENT: {item['filename']} to {to_email}")
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to send {item['filename']} to {to_email}: {e}")
|
||||
print(f"FAILED: {item['filename']} to {to_email}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
pitches = get_pitch_data()
|
||||
send_premium_email(pitches)
|
||||
178
tmp/test_email.py
Normal file
178
tmp/test_email.py
Normal file
@@ -0,0 +1,178 @@
|
||||
import smtplib
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
import os
|
||||
import markdown
|
||||
|
||||
def send_premium_test_email():
|
||||
# SMTP Settings
|
||||
smtp_host = "smtp.qrmaster.net"
|
||||
smtp_port = 465
|
||||
smtp_user = "timo@qrmaster.net"
|
||||
smtp_pass = "fiesta"
|
||||
|
||||
# Recipient
|
||||
to_email = "knuth.timo@gmail.com"
|
||||
|
||||
# Article File Path
|
||||
article_path = r"c:\Users\a931627\Documents\QRMASTER\articles\seosandwitch-qr-codes-offline-attribution.md"
|
||||
|
||||
with open(article_path, "r", encoding="utf-8") as f:
|
||||
article_content = f.read()
|
||||
|
||||
# Professional Markdown to HTML conversion
|
||||
# Uses 'tables' and 'fenced_code' for perfect formatting
|
||||
article_html = markdown.markdown(article_content, extensions=['tables', 'fenced_code', 'nl2br'])
|
||||
|
||||
subject = "Solving the 'print gap' for small businesses (Draft Included)"
|
||||
|
||||
# HTML Template - "Premium Digital Document"
|
||||
html_template = f"""
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Lora:ital,wght@0,400;0,700;1,400&display=swap');
|
||||
|
||||
body {{
|
||||
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #2D3748;
|
||||
background-color: #F7FAFC;
|
||||
padding: 40px 20px;
|
||||
margin: 0;
|
||||
}}
|
||||
.container {{
|
||||
background-color: #ffffff;
|
||||
max-width: 760px;
|
||||
margin: 0 auto;
|
||||
padding: 60px;
|
||||
border: 1px solid #E2E8F0;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.05);
|
||||
}}
|
||||
.pitch-section {{
|
||||
margin-bottom: 50px;
|
||||
font-size: 16px;
|
||||
border-bottom: 2px solid #EDF2F7;
|
||||
padding-bottom: 40px;
|
||||
}}
|
||||
.draft-metadata {{
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
padding: 15px;
|
||||
background: #F8FAFC;
|
||||
border: 1px solid #E2E8F0;
|
||||
font-size: 13px;
|
||||
color: #64748B;
|
||||
border-radius: 4px;
|
||||
}}
|
||||
.article-content {{
|
||||
font-family: 'Lora', Georgia, 'Times New Roman', serif;
|
||||
font-size: 18px;
|
||||
color: #1A202C;
|
||||
line-height: 1.7;
|
||||
}}
|
||||
|
||||
/* Professional Markdown Overrides */
|
||||
.article-content h1 {{ font-family: 'Inter', sans-serif; font-size: 32px; font-weight: 600; margin-top: 0; }}
|
||||
.article-content h2 {{ font-family: 'Inter', sans-serif; font-size: 24px; font-weight: 600; margin-top: 40px; border-bottom: 1px solid #E2E8F0; padding-bottom: 8px; }}
|
||||
.article-content h3 {{ font-family: 'Inter', sans-serif; font-size: 20px; font-weight: 600; margin-top: 30px; }}
|
||||
|
||||
.article-content table {{
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 30px 0;
|
||||
font-size: 15px;
|
||||
font-family: 'Inter', sans-serif;
|
||||
}}
|
||||
.article-content th {{
|
||||
background-color: #F1F5F9;
|
||||
text-align: left;
|
||||
padding: 12px;
|
||||
border: 1px solid #CBD5E1;
|
||||
font-weight: 600;
|
||||
}}
|
||||
.article-content td {{
|
||||
padding: 12px;
|
||||
border: 1px solid #E2E8F0;
|
||||
}}
|
||||
.article-content tr:nth-child(even) {{ background-color: #F8FAFC; }}
|
||||
|
||||
.article-content blockquote {{
|
||||
border-left: 4px solid #3182CE;
|
||||
margin: 30px 0;
|
||||
padding: 10px 20px;
|
||||
background: #EBF8FF;
|
||||
font-style: italic;
|
||||
}}
|
||||
|
||||
.article-content pre {{
|
||||
background: #1A202C;
|
||||
color: #E2E8F0;
|
||||
padding: 20px;
|
||||
border-radius: 6px;
|
||||
overflow-x: auto;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}}
|
||||
|
||||
.footer-note {{
|
||||
margin-top: 60px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #E2E8F0;
|
||||
font-size: 13px;
|
||||
color: #94A3B8;
|
||||
text-align: center;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="pitch-section">
|
||||
Hi Timo,<br><br>
|
||||
I noticed your list of free resources for small businesses is one of the more practical ones out there. One category that's often missing: <b>QR code generators</b>.<br><br>
|
||||
Instead of just sending a link, I've drafted a comprehensive guide on bridging the 'print gap' using dynamic indicators. I'd love to see this featured on <i>SEO Sandwitch</i> if it aligns with your upcoming content calendar.<br><br>
|
||||
The full draft preview is below.<br><br>
|
||||
Best,<br>
|
||||
<b>Timo</b><br>
|
||||
(Writer & Strategist)
|
||||
</div>
|
||||
|
||||
<div class="draft-metadata">
|
||||
<div><b>STATUS:</b> Finished Draft</div>
|
||||
<div><b>FORMAT:</b> Case Study / Guide</div>
|
||||
<div><b>LENGTH:</b> ~2,400 Words</div>
|
||||
</div>
|
||||
|
||||
<div class="article-content">
|
||||
{article_html}
|
||||
</div>
|
||||
|
||||
<div class="footer-note">
|
||||
This is a private preview intended for the editorial team at SEO Sandwitch.
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
message = MIMEMultipart()
|
||||
message["From"] = smtp_user
|
||||
message["To"] = to_email
|
||||
message["Subject"] = subject
|
||||
message.attach(MIMEText(html_template, "html"))
|
||||
|
||||
try:
|
||||
print(f"Connecting to {smtp_host}:{smtp_port} (PREMIUM MODE)...")
|
||||
with smtplib.SMTP_SSL(smtp_host, smtp_port) as server:
|
||||
server.login(smtp_user, smtp_pass)
|
||||
print("Login successful. Sending premium document email...")
|
||||
server.sendmail(smtp_user, to_email, message.as_string())
|
||||
print(f"Premium email sent successfully to {to_email}!")
|
||||
except Exception as e:
|
||||
print(f"Failed to send email: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
send_premium_test_email()
|
||||
Reference in New Issue
Block a user