Decide what’s worth importing
Apply the same capture rules used for live traffic:- Profiles for current customers, including email. Consider excluding long-churned customers you will not contact; they count toward the plan’s profile limit, and messages for new customers beyond that limit are skipped.
- Lifecycle history from your billing system: trials, subscriptions, upgrades, and cancellations. Billing exports work well because their timestamps are usually reliable.
- Skip synthetic reconstructions. If the original timestamp is unavailable, do not guess.
Use real timestamps
Every method accepts atimestamp so events land at their true time:
- Numbers are epoch milliseconds, not seconds. Epoch seconds parse as January 1970 without erroring. When in doubt, pass a native
Date/datetimeor an ISO 8601 string. - Python
datetimes must be timezone-aware. A naive datetime is interpreted as the machine’s local time — construct withtzinfoset to the source’s real zone.
profile before their events so the timeline has traits and email from the start. Events for a new userId still create the customer, and a later profile fills in the traits.
Work within the rate limit
Each write key accepts 600 messages per 60-second sliding window by default; some plans have a higher limit. The SDKs retry occasional429 responses, but a sustained import should stay below the limit. A pace of 500 messages per minute leaves headroom:
At that rate, 1,000 messages take about 2 minutes, 10,000 take about 20 minutes, and 100,000 take about 3.5 hours.
onError for an import. A non-retryable failure drops the batch, and the callback identifies the rows that need to be sent again.
Dry-run against the test environment
Run a representative sample with aglyph_test_... key before the live import. Do not send the full dataset because the test environment stops accepting requests after it reaches 5,000 stored events. Check timestamps, time zones, event names, and profile traits in the resulting timelines.
Make it re-runnable (or don’t run it twice)
The SDKs generate a freshmessageId per call, so re-running an import script duplicates every event. Pick one of:
- Run it exactly once. Fine for a one-off import — keep the script’s output log as your record.
- Track your progress. Record the last imported row (or mark rows as sent) so a crashed run resumes instead of restarting.
- Use the HTTP API with deterministic message IDs. Derive
messageIdfrom your source data —import-invoice-1042— and the server’s idempotency makes the whole import safely re-runnable:
Verify
After the import, check several known customers from different parts of the dataset. Backfilled events sort bytimestamp, so older history appears below recent activity. Resolve gaps, incorrect ordering, or 1970 timestamps before importing another source. See Troubleshooting.