Format the CSV right (before you upload)#
The single biggest cause of "import failed for 1,247 rows" is malformed CSV. Get these right and the import flows smoothly:
| Requirement |
What it means |
| UTF-8 encoding |
Save the file as UTF-8 — Excel defaults to "CSV (Comma delimited)" which is often Windows-1252 and breaks accented characters. In Excel: Save As → CSV UTF-8. In Numbers: Export → CSV with default encoding. |
| Column headers in row 1 |
The first row is field names: email,first_name,last_name,city. Lowercase, snake_case, no spaces. |
| One email per row |
Don't pack john@example.com, jane@example.com into a single cell. One row per subscriber. |
| Quoted strings if they contain commas |
A city value of Berlin, DE must be quoted: "Berlin, DE". Most spreadsheet tools do this automatically; hand-rolled CSV files often forget. |
| Email column required |
Every row must have a non-empty email. AcelleMail rejects rows with blank email. |
| No extra whitespace |
Leading/trailing spaces in emails or field values are stripped, but john@example.com (with trailing space) might cause confusion in audit logs. Trim before export. |
| Date format consistent |
If you have a signup_date column, use ISO 8601 (2026-05-19) — most reliable. Mixed 5/19/2026 + 19/05/2026 will trip the parser. |
The 6-click import flow (same for every source)#
Once you have the CSV ready, AcelleMail's import wizard is the same regardless of where the file came from.
1. Open the destination list#
In the left sidebar, click Audience → choose the list that will receive these subscribers (or create one — New list button top-right).

The list detail page shows current subscriber counts and a per-list toolbar:

2. Click "Import" in the list toolbar#
The wizard entrypoint is right there on the list detail page:

3. Upload your CSV#
Drop the file into the upload area:

AcelleMail parses the file and confirms detection — rows-counted + sample preview:

4. Map the columns#
The wizard auto-detects standard columns (email, first_name, last_name) and shows green Mapped to EMAIL chips. For non-standard columns, pick the destination from the dropdown:

5. Pick duplicate handling#
On the same screen, choose what AcelleMail does when a row's email already exists in this list:
- Skip — keep the existing subscriber, don't overwrite anything
- Update — overwrite name / tags / custom fields with values from this CSV
- Unsubscribe — mark the existing row as unsubscribed (rare; for re-importing an opted-out list)
6. Run the import#
Click Start import. The job runs in the background — you can close the popup, work elsewhere, return to Audience → [list] → Import to see progress:

Pending → Running → Complete, with rows-imported / rows-skipped / errors counts per job.
What happens after Start import#
The job runs in the background. For a 10k-row file, expect 30-90 seconds. For 100k rows, 5-15 minutes. AcelleMail processes in batches of ~500 with database transactions per batch — safe to close the browser tab; the job continues server-side.
The Import history view in the list shows progress per batch:
- Pending — job queued, not yet processing
- Running — worker actively reading the CSV
- Complete — done; click to see the result summary
Result summary breaks down:
- Rows imported — net successful new subscribers + updates
- Rows skipped — duplicates handled per your chosen rule
- Rows with errors — bad emails, invalid date formats, FK violations — with a downloadable error CSV showing exactly which rows failed and why
Download the error CSV, fix the rows in your spreadsheet, re-import just those rows.
Common CSV import errors and fixes#
| Error in error.csv |
What it means |
Fix |
Invalid email format |
Row has missing @, malformed domain, or trailing whitespace |
Validate email syntax in spreadsheet before upload (Excel: use regex) |
Email already exists (with Skip mode) |
Subscriber on this list already; per skip-rule, not overwritten |
Expected; not an error — just informational |
Email already exists (with Update mode) |
Should be auto-updating; if it's an error here, the import ran in Skip mode |
Re-run with duplicate handling = Update |
Field 'birthday' has invalid format |
Mixed date formats in CSV |
Normalize all dates to ISO 8601 before upload |
Field 'lead_score' must be numeric |
A non-number value in a Number-type field |
Check CSV — strip non-numeric values or change the field type |
Required field 'email' is empty |
Row has no email |
Remove the row or fix the value |
Charset error: invalid UTF-8 sequence at row 4523 |
File saved as Windows-1252 or other encoding |
Re-export as UTF-8 |
Cannot map column 'birth_date' to field 'birthday' |
CSV header name differs from field name; not auto-detected |
Use the field mapping step to manually map |
Common UI patterns#
Duplicate handling cheat sheet#
| Choose |
When to use |
| Skip |
Importing a fresh batch of leads where existing subscribers shouldn't have their names/tags overwritten |
| Update |
Re-importing the same list from a master CRM where the CRM is the source of truth |
| Unsubscribe |
Importing a bounced-list export to mark addresses inactive (rare; usually you'd just delete them) |
When to import vs. when to use the API#
| Scenario |
Use |
| One-time migration from another platform |
CSV import (fastest, easiest) |
| Daily sync from a CRM-of-record |
API + scheduled cron (idempotent, automatic) |
| New batch of leads (every week or month) |
CSV import works fine, OR set up CSV-upload-to-folder integration via your storage tool |
| Real-time subscribe (signup form) |
AcelleMail's built-in signup forms OR your custom form posting to AcelleMail's API |
Pre-import sanity check#
Open the CSV in your spreadsheet tool. Five quick checks before uploading:
- Row count matches expectations (a leading blank row will offset everything)
- Column headers are present and snake_case (
email, not Email Address)
- Email column has no blanks (sort by email; blanks float to top)
- No duplicate emails within the CSV itself (de-dupe via spreadsheet first; AcelleMail will skip dupes but it's cleaner to remove them upfront)
- Try a 10-row test import first if the CSV is large — verify field mapping works, then re-run with the full file
Common UI signals + fixes#
| Symptom |
Likely cause |
UI fix |
| Import stuck on "Pending" for >10 minutes |
Queue worker isn't running |
Operator: check that queue:work is running (see setup queue workers) |
| Import shows "Complete" but list count didn't increase |
All rows skipped due to duplicates |
Check duplicate handling — should be Update if you want overwrite |
| Custom field column not detected during mapping |
Column header doesn't match field tag exactly |
Either rename header in CSV, OR manually map in step 4 |
| Wizard rejects the file at upload step |
Wrong file type (xlsx not csv), or too large (>50MB default) |
Export as plain CSV from your spreadsheet tool; for >50MB, split into multiple files |
| Some subscribers imported, others not |
Mixed error types in single file (some invalid emails, some duplicates) |
Download error CSV → fix → re-import; the successful rows aren't re-counted |
| Tags column joins as single string |
Tag values need to be comma-separated within the cell: tag1,tag2,tag3 |
Re-format the tags column in your spreadsheet |
Advanced: bulk-update strategies, API-driven sync, audit + recovery
For large lists or ongoing-sync scenarios, the CSV-through-UI path is one tool among several.
Bulk update via re-import:
If you need to update a single field across thousands of subscribers (e.g. reset everyone's frequency to weekly), the easiest path:
- Export the list to CSV (Audience → list → Export → All fields)
- Open in spreadsheet, edit the column for the values you want
- Re-import with duplicate handling = Update
AcelleMail processes the file in 5-30 minutes depending on size. The advantage over API is no scripting required; the downside is no per-row tracking of which value changed.
Scripted update via API (when you need fine control):
ACELLE_TOKEN="..."
# Set everyone's 'frequency' to 'weekly' in one list:
curl -X POST "https://acellemail.com/api/v1/lists/{list_uid}/subscribers/bulk-update" \
-H "Authorization: Bearer $ACELLE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"filter": {"status": "subscribed"},
"updates": {"fields": {"frequency": "weekly"}}
}'
Returns {updated_count, failed_count} synchronously. Handle 429 with backoff for large updates.
Idempotent CSV import pipeline:
When syncing from a CRM nightly, the standard pattern is:
# 1. Export CRM contacts to CSV
crm-export --tag=marketing --output=/tmp/contacts.csv
# 2. Pre-validate (optional but recommended)
csvkit validate /tmp/contacts.csv
# 3. Upload to AcelleMail
curl -X POST "https://acellemail.com/api/v1/lists/{list_uid}/imports" \
-H "Authorization: Bearer $ACELLE_TOKEN" \
-F "file=@/tmp/contacts.csv" \
-F "duplicate_handling=update" \
-F "field_mapping={\"email\":\"email\",\"first_name\":\"first_name\"}"
# 4. Poll for completion + result
import_uid="..." # from response
curl -H "Authorization: Bearer $ACELLE_TOKEN" \
"https://acellemail.com/api/v1/imports/$import_uid"
The full audit trail (which rows were updated, which were skipped, which failed) is in the response JSON.
Resumable imports for very large files:
For files >100k rows, the wizard can run for 15+ minutes. If the queue worker is restarted mid-import, AcelleMail's import job is designed to resume from the last completed batch — no rows are double-processed. The audit trail shows the exact row offset where the resume happened.
Pre-import deduplication:
If your CSV has internal duplicates (same email twice within the file), AcelleMail processes the rows in order and the LAST row's values win for the duplicate. To control which row wins, sort the CSV in your spreadsheet first — most recent record last.
For server-side dedup, use the API in a loop with upsert: true and last_updated_at field comparison.
Recovery from a bad import:
If you imported with wrong field mapping (e.g. mapped first_name to email and now have garbage), the recovery path:
- Don't panic — the original data is still there in your spreadsheet
- AcelleMail keeps per-import audit logs (admin → Audit log → filter by import event)
- Bulk-update the affected field back to correct values via API or re-import
There's no automatic "undo last import" — recovery is always forward-looking. Always test with a 10-row sample first on a non-critical list before running a 100k import.
Field type changes:
If you've imported 100k subscribers with birthday as a Text field, then realize you want it as a Date field — the change is in two steps:
- Audience → list → Fields → edit
birthday → change type Text → Date. AcelleMail re-validates all existing values; rows where the value doesn't parse remain as-is until you fix them.
- Either bulk-update the bad rows via API, or export → fix in spreadsheet → re-import.
Custom-field discovery during import:
If your CSV has a column AcelleMail doesn't know about (e.g. lifetime_value and the list doesn't have that field yet), the field-mapping step in the wizard offers Create new field on import. Pick the field type (Number / Text / Date) → AcelleMail creates the field on the list + maps the column to it in one click. Useful for one-time migrations from systems with custom schemas.
Compliance audit trail:
Every import is logged with:
- Who triggered it (admin user ID)
- When (timestamp)
- Which file (filename + hash)
- How many rows added / updated / skipped / errored
- The full error CSV is retained 90 days (configurable in admin settings)
For GDPR audit requests ("show me when you obtained this subscriber's consent"), the import audit log answers it — the import row references the original CSV which presumably has the source/opt-in date column.
Related articles#