Skip to content

How to Warm Up Your Email Domain

Nylas allows you to use your own custom email domain as well as a free nylas.email domain to send transactional emails. If you’ve already tried sending a few emails with your new domain and noticed they have been going to spam, the reason for this is because you have not established a strong sender reputation with providers like Gmail and Microsoft. This is where warming up your email domain comes into play.

Email domain warm-up is the gradual process of building trust for a new or inactive sending domain by steadily increasing both email sending volume and recipient engagement over time. This includes growing not only the number of emails sent, but also positive interactions such as opens, clicks, and replies.

By ramping up activity in a controlled and intentional way, domain warm-up helps establish a strong sender reputation with email service providers and improves long-term deliverability.

Mailbox providers use sophisticated algorithms to fight spam and phishing. When they see a new sending domain suddenly blasting hundreds or thousands of emails, they get suspicious. Without an established reputation, your emails are more likely to be filtered out or rejected.

Warming your domain helps:

  • Build sender reputation steadily
  • Improve inbox placement rates
  • Avoid blocks, throttling, or spam folder placement
  • Protect your brand and IP from blacklists
  1. Start Small and Slow

Begin sending very low volumes per day and gradually increase over weeks. Sudden spikes signal spammy behavior. Here is an example of a schedule to follow over 4 weeks assuming a daily target of 500-1000 emails during a typical 8-hour send window. Within each week, it is best to gradually increase the number every day or so by 10-20%.

WeekTotal Emails per DayEmails per Hour (Approx.)Notes
Week 15 - 151 - 2Start very low, focus on highly engaged recipients
Week 230 - 754 - 10Gradually increase volume, maintain steady increase
Week 3150 - 30020 - 40Moderate volume
Week 4500 - 1,000+60 - 125+Full ramp-up to target volume
  1. Spread Sends Throughout the Day

Avoid sending all your emails at once. Spread sends evenly across business hours (e.g. 9AM - 5PM) with random delays between each message. The goal is to mimic natural sending patterns while establishing consistency.

  1. Use Real, Engaged Recipients

Send only to contacts who expect your emails and are likely to open, click, or reply. High engagement improves your reputation quickly. The table below summarizes different engagment types and the impact it has to your domain reputation. During your warm up period, let your email recipients know they should click links and reply to emails as this is the biggest positive signal that your content is not spam.

Engagement TypeDescriptionSignal to Mailbox Providers
OpenRecipient opens the emailPositive: indicates interest and legitimacy
ClickRecipient clicks on links inside the emailStrong positive: signals active engagement
ReplyRecipient replies to the emailVery strong positive: shows two-way interaction
Mark as SpamRecipient marks email as spamStrong negative: harms sender reputation
Delete Without ReadingRecipient deletes email without openingNegative: signals low relevance or trust
Move to Inbox/FolderRecipient moves email from spam to inboxPositive: improves sender reputation
ForwardRecipient forwards email to othersPositive: extends reach and trust
Bounce (Hard/Soft)Email fails to deliver (invalid address, etc.)Negative: especially hard bounces harm reputation
  1. Content Matters

Transactional emails (like password resets, order confirmations) have higher trust and open rates than marketing blasts. Avoid spammy or promotional content during warm-up. No all-caps, excessive links, or aggressive sales language early on. Introduce slight variations to the email content while maintaining consistent branding.

You can use the following python script to start warming up your new email domain. The script is meant to be run daily and takes care of sending a specified number of emails throughout a given time window using the Nylas Transactional Send API. It does NOT automate engagment of these emails which is equally important in the warm-up process so make sure you choose recipients who will engage. You can use a cron job that is set up to run it at the start of your business day (e.g. 9AM). At the end of every week, you should update the script’s total daily target to match the schedule you are following.

You will need to replace the appropriate lines of code with your:

  • Email domain
  • API key
  • Recipients list: Use real people who will engage with your emails. High engagement is key to a good domain reputation. Make sure to use a variety of email providers.
  • Daily email target: Use the schedule suggested above. You should update the script every few days or at the end of every week with the new daily target.
  • Send window: The length of time during which sends can happen. We recommend using the time period you expect emails to be sent at. For most scenarios, this is during business hours so your send window should be 8-10 hours.
  • Email content: We recommend using content that mimics what you will be actually sending. This might be booking confirmation, password resets, etc. See the Transaction Send API docs for more information on what you can include in the request.
import requests
import json
import time
import random
import math
# ============ VARIABLES TO UPDATE ============
# Update all the variables in this section. Also update the payload which is outside of
# this section, further down in the script.
base_url = "https://api.us.nylas.com"
email_domain = "your-subdomain.nylas.email"
api_key = "your-api-key"
# List of recipients to send emails to. Use recipients that you know will engage with the
# email. You should include a mix of different email providers.
recipients = [
{"name": "John Smith", "email": "[email protected]"},
{"name": "Jane Doe", "email": "[email protected]"},
{"name": "Bob", "email": "[email protected]"},
]
send_window = 8 # In hours. Use 8-10 hour window to mimic business hours.
total_emails_to_send = 10 # Your daily send target.
# ============================================
url = f'{base_url}/v3/domains/{email_domain}/messages/send'
headers = {
"Authorization": f"Bearer {api_key}",
'Content-Type': 'application/json',
}
emails_per_hour = math.ceil(total_emails_to_send / send_window)
print(f"Script started! Sending {total_emails_to_send} emails over {send_window} hours")
for hour in range(1, send_window + 1):
print(f"Sending emails for hour {hour} of {send_window}")
start_time = time.time()
if len(recipients) < emails_per_hour:
# Allow for duplicates if there are less recipients than emails to send.
random_recipients = random.choices(recipients, k=emails_per_hour)
else:
random_recipients = random.sample(recipients, emails_per_hour)
for recipient in random_recipients:
# Update this payload with your own email content and "from" information.
# See Transactional Send API docs for more info.
payload = {
"to": [recipient],
"from": {
"name": "ACME Corporation",
"email": "[email protected]"
},
"template": {
"id": "your-template-id",
"variables": {
"name": recipient["name"],
"email": recipient["email"],
"booking": {
"start_time": 1768856240,
"end_time": 1768856240,
"location": "123 Main St",
}
}
}
}
response = requests.post(url, headers=headers, data=json.dumps(payload))
if response.ok:
print(f"Successfully sent email to {recipient['email']}")
else:
print(f"Could not send email to {recipient['email']}")
print(response.text)
# Randomly send the emails throughout the hour. We will wait at least 1 minute between each send
# but the wait time will be longer if there are less emails to send in that hour.
max_wait_time_minutes = max(1, math.ceil(60 / emails_per_hour))
jitter_seconds = random.uniform(0, 10)
random_delay_seconds = random.uniform(60, max_wait_time_minutes * 60) + jitter_seconds
print(f"Waiting {random_delay_seconds / 60} minutes before sending next email")
time.sleep(random_delay_seconds)
# Once all emails are sent for the hour, we wait until the next hour to start sending again.
time_till_next_hour = 3600 - (time.time() - start_time)
if time_till_next_hour > 0:
print(f"Waiting {time_till_next_hour / 60} minutes until next hour")
time.sleep(time_till_next_hour)