How I automated 200+ email alias migrations to SimpleLogin with a Bash script

After upgrading to a Premium SimpleLogin.io account, I needed to migrate about 200 email aliases from a different provider. There’s no built-in bulk alias creation feature in SimpleLogin’s UI, and the CSV import was reported as unreliable by multiple users. So I wrote a simple Bash script that automates the whole process via the SimpleLogin API.

The problem

When you switch email alias providers and bring your own domain, you need to recreate all your existing aliases on the new platform. Doing this manually through the web UI for 200+ aliases is tedious and error-prone. The CSV import feature exists but has known issues – several users reported it silently failing with no error messages, or showing “Pending” status that never resolves.

The script

The script uses two SimpleLogin API endpoints:

  1. GET /api/v4/alias/options – retrieves the cryptographically signed suffix for your domain
  2. POST /api/v3/alias/custom/new – creates the alias with the signed suffix

For each alias, it fetches a fresh signed suffix (required by the API for security), creates the alias, and waits 30 seconds to respect the rate limit. Without the delay, SimpleLogin returns HTTP 429 with the friendly message “Whoa, slow down there, pardner!”

#!/bin/bash

# Documentation: https://github.com/simple-login/app/blob/master/docs/api.md
# Author: https://github.com/MyKEms

# Adjust variables
api_key="API_TOKEN"
domain="contoso.com"

# Set your mailbox ID. Retain the square brackets even for a single ID.
# Find it in SimpleLogin dashboard: Mailboxes > Edit > copy number from URL
mailbox_ids="[123456]"

# Alias names you want to create
declare -a aliases=("alias_name_1" "alias_name_2" "alias_name_3")

# Loop through each alias and create via API
for alias in "${aliases[@]}"; do
  # Get the signed_suffix for the domain
  response=$(curl -X GET "https://api.simplelogin.io/api/v4/alias/options" \
    -H "Content-Type: application/json" \
    -H "Authentication: $api_key")

  # Parse the signed_suffix for the specific domain
  signed_suffix=$(echo $response | jq -r \
    --arg domain "@$domain" \
    '.suffixes[] | select(.[0]==$domain) | .[1]')

  # Create the new alias
  create_response=$(curl -s -o /dev/null -w "%{http_code}" \
    -X POST "https://api.simplelogin.io/api/v3/alias/custom/new" \
    -H "Content-Type: application/json" \
    -H "Authentication: $api_key" \
    -d "{\"alias_prefix\": \"$alias\", \"signed_suffix\": \"$signed_suffix\", \"mailbox_ids\": $mailbox_ids}")

  if [ "$create_response" -eq 201 ]; then
    echo "Created alias: $alias@$domain"
  else
    echo "Failed to create alias: $alias@$domain (HTTP $create_response)"
  fi

  # Respect the API rate limit
  echo "Sleeping 30s for rate limit..."
  sleep 30
done

The full script is available as a GitHub Gist.

How to use it

Prerequisites

  • bash, curl, jq
  • A SimpleLogin Premium account with a custom domain configured
  • Your API token (from SimpleLogin dashboard under API Keys)

Step 1: Get your mailbox ID

Go to SimpleLogin dashboard > Mailboxes > click Edit on your target mailbox > copy the number from the URL. That’s your mailbox ID.

Step 2: Prepare your alias list

Replace the aliases array with your actual alias prefixes. If you’re migrating from another provider, you can usually export your aliases as CSV and extract just the prefixes:

# Example: extract alias prefixes from a CSV export
declare -a aliases=($(awk -F'@' '{print $1}' exported_aliases.csv))

Step 3: Run the script

chmod +x migrate_aliases.sh
./migrate_aliases.sh

For 200 aliases with the 30-second delay, the script takes roughly 100 minutes to complete. Just let it run in the background.

Rate limiting

SimpleLogin enforces API rate limits to prevent abuse. The script includes a 30-second sleep between each alias creation. You can try lowering this, but going too fast will result in HTTP 429 responses. I found 30 seconds to be reliable for bulk operations.

Why not CSV import?

SimpleLogin does have a CSV import feature, but at the time of writing it had several issues:

  • Import would show “Pending” status and never complete
  • No error messages when import fails silently
  • Previously deleted aliases in the trash can block new imports (you need to empty the domain’s trash first)

The API approach is more reliable and gives you clear success/failure feedback for each alias.

References