Cron Job Send Newsletter Every Tuesday
Schedule your weekly newsletter to send every Tuesday at 8am via Resend, Postmark, or Mailchimp — fully automated.
What this schedule does
The expression 0 8 * * 2 fires at exactly 8:00 AM every Tuesday. The five fields mean: minute=0, hour=8, any day of month, any month, day 2 (Tuesday). It never runs on any other day of the week.
Tuesday morning is a popular choice for newsletters — subscribers are past the Monday rush and still early in their week. The 8am window catches people before their morning standup and typically yields higher open rates than midday or end-of-week sends.
How to send a newsletter with a script
A newsletter send script fetches your subscriber list, renders your content, and calls your email provider's API. The pattern works identically with Resend, Postmark, Mailchimp, or any transactional email service that exposes a REST API.
The script below uses Resend (a developer-friendly email API). Swap the API call for Postmark's /email/batch or Mailchimp's /campaigns/{id}/actions/send endpoint if needed.
#!/usr/bin/env python3
"""Send weekly newsletter via Resend API — runs every Tuesday at 8am."""
import os
import json
import urllib.request
import urllib.error
RESEND_API_KEY = os.environ["RESEND_API_KEY"]
FROM_EMAIL = os.environ.get("FROM_EMAIL", "newsletter@yourdomain.com")
RESEND_URL = "https://api.resend.com/emails"
def get_subscribers() -> list[dict]:
"""
Return your subscriber list. In production, query your database or
call your list provider (ConvertKit, Buttondown, etc.).
"""
return [
{"email": "alice@example.com", "name": "Alice"},
{"email": "bob@example.com", "name": "Bob"},
]
def render_html(name: str) -> str:
"""Build the email HTML. Replace this with your template engine."""
return f"""
<html><body>
<h2>Hey {name}, here's your Tuesday brief</h2>
<p>This week's highlights:</p>
<ul>
<li>Story one — summary here</li>
<li>Story two — summary here</li>
</ul>
<p><small><a href="{{unsubscribe_url}}">Unsubscribe</a></small></p>
</body></html>
"""
def send_email(to_email: str, to_name: str) -> None:
payload = json.dumps({
"from": FROM_EMAIL,
"to": [to_email],
"subject": "Your Tuesday Newsletter",
"html": render_html(to_name),
}).encode()
req = urllib.request.Request(
RESEND_URL,
data=payload,
headers={
"Authorization": f"Bearer {RESEND_API_KEY}",
"Content-Type": "application/json",
},
method="POST",
)
try:
with urllib.request.urlopen(req) as resp:
result = json.loads(resp.read())
print(f"Sent to {to_email}: id={result.get('id')}")
except urllib.error.HTTPError as e:
print(f"Failed to send to {to_email}: {e.code} {e.read().decode()}")
def main():
subscribers = get_subscribers()
print(f"Sending newsletter to {len(subscribers)} subscribers...")
for sub in subscribers:
send_email(sub["email"], sub["name"])
print("Done.")
if __name__ == "__main__":
main()
Platform snippets
# crontab -e 0 8 * * 2 /usr/bin/python3 /path/to/send_newsletter.py
on:
schedule:
- cron: '0 8 * * 2' # UTC — adjust for your timezone
jobs:
send-newsletter:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Send newsletter
run: python send_newsletter.py
env:
RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }}
FROM_EMAIL: newsletter@yourdomain.com
# EventBridge cron (6 fields — adds year, UTC) cron(0 8 ? * TUE *)
apiVersion: batch/v1
kind: CronJob
metadata:
name: newsletter-sender
spec:
schedule: "0 8 * * 2"
jobTemplate:
spec:
template:
spec:
containers:
- name: sender
image: your-image:latest
command: ["python", "send_newsletter.py"]
env:
- name: RESEND_API_KEY
valueFrom:
secretKeyRef:
name: newsletter-secrets
key: resend-api-key
restartPolicy: OnFailure
Timezone note
Standard crontab uses the system timezone. GitHub Actions and AWS EventBridge run in UTC — if you're on US Eastern time (UTC-5), use 0 13 * * 2 to fire at 8am ET. If you're on US Pacific time (UTC-8), use 0 16 * * 2. Kubernetes 1.27+ supports spec.timeZone: "America/New_York" to avoid the manual offset math.