作者如何用 GitHub Actions 和 Microsoft Graph 構建自動運行的反向連結拓展系統
How I Built a Self-Running Backlink Outreach Engine with GitHub Actions and Microsoft Graph
作者以 Python、GitHub Actions 和 Microsoft Graph 構建自動運行的反向連結拓展系統,透過 Microsoft 365 郵箱發送及讀取電郵,並每週執行三次。系統會整理回覆、追蹤連結是否上線,並生成每日摘要;有人回覆後不會自動跟進。
Most "link building" advice is either manual grind or spammy tools. I wanted something in between: a small, honest outreach engine that runs itself, sends from a real business mailbox, and reports back every day. Here's how I put it together for my AI consultancy, GetServices.ai.
The goal
- Find relevant sites for each of my properties
- Send a short, personal outreach email from a real address
- Read replies and track which links actually go live
- Give me one daily report instead of another dashboard to babysit
The stack
- Python for the engine itself
- GitHub Actions on a cron schedule (alternate days, three times a week)
- Microsoft Graph API to send and read mail from a Microsoft 365 mailbox
- GitHub Secrets for every credential, nothing in the repo
No paid outreach SaaS. I tried Apollo-style tools and decided I'd rather own the pipeline.
Sending mail through Microsoft Graph
Instead of SMTP passwords, I registered an app in Microsoft Entra ID and gave it two application permissions with admin consent:
Mail.SendMail.Read
The engine uses the client-credentials flow to get a token, then calls Graph:
import msal, requests, os
app = msal.ConfidentialClientApplication(
os.environ["MS_CLIENT_ID"],
authority=f"https://login.microsoftonline.com/{os.environ['MS_TENANT_ID']}",
client_credential=os.environ["MS_CLIENT_SECRET"],
)
token = app.acquire_token_for_client(
scopes=["https://graph.microsoft.com/.default"]
)["access_token"]
def send(to, subject, html):
sender = os.environ["MS_SENDER"]
url = f"https://graph.microsoft.com/v1.0/users/{sender}/sendMail"
payload = {
"message": {
"subject": subject,
"body": {"contentType": "HTML", "content": html},
"toRecipients": [{"emailAddress": {"address": to}}],
},
"saveToSentItems": True,
}
r = requests.post(url, json=payload,
headers={"Authorization": f"Bearer {token}"})
r.raise_for_status()
Tip: in production, scope the app to a single mailbox with an Exchange application access policy so it can't send as anyone else. (Microsoft docs)
Scheduling with GitHub Actions
name: Authority Engine
on:
schedule:
- cron: "17 14 * * 1,3,5" # Mon/Wed/Fri
workflow_dispatch:
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.12" }
- run: pip install msal requests
- run: python authority_engine.py
env:
MS_TENANT_ID: ${{ secrets.MS_TENANT_ID }}
MS_CLIENT_ID: ${{ secrets.MS_CLIENT_ID }}
MS_CLIENT_SECRET: ${{ secrets.MS_CLIENT_SECRET }}
MS_SENDER: ${{ secrets.MS_SENDER }}
Using an odd minute (like :17) instead of :00 avoids the top-of-the-hour queue on shared runners.
Keeping it human (and out of spam folders)
- Small batches. A handful of emails per run, not hundreds.
- One real reason per email. Every pitch points to a specific article that genuinely fits the recipient's page.
- Real footer. Physical business address and a clear way to opt out, as CAN-SPAM requires.
-
Read before you reply. Replies are pulled with
Mail.Readand summarised for me; nothing gets an automatic follow-up once a human answers. - Authenticate your domain. SPF, DKIM and DMARC set up before the first send.
Daily report
Each run writes a small JSON log (sent, replied, link found, link live). A second job turns that into a one-page daily summary. That's the only thing I look at.
Lessons so far
- Test with your own inbox first. My first live send was to my own Gmail to check rendering and headers.
- Secrets in the CLI, not the UI history. Rotate the client secret on a calendar.
- Start with one strong article per site before automating outreach for it. Outreach can't fix thin content.
What's next
I'm folding this into ATLAS AEO, the answer-engine optimisation app we're building at app.getservices.ai, so small businesses can run the same kind of honest outreach without writing the plumbing themselves.
If you've built something similar, I'd love to hear how you handle reply classification and deliverability. Drop a comment!
來源:DEV Community · dev.to