Skip to content

feat: add contacts migrate and emails batch-csv cmds for bulk ops#100

Open
adithyaakrishna wants to merge 3 commits intoresend:mainfrom
adithyaakrishna:feat/bulk-migrate
Open

feat: add contacts migrate and emails batch-csv cmds for bulk ops#100
adithyaakrishna wants to merge 3 commits intoresend:mainfrom
adithyaakrishna:feat/bulk-migrate

Conversation

@adithyaakrishna
Copy link

@adithyaakrishna adithyaakrishna commented Mar 14, 2026

Description:

  • Adds resend contacts migrate for bulk-migrating contacts between segments from a CSV or JSON file
  • Adds resend emails batch-csv for sending bulk emails from a CSV file using a Resend template or inline body with per-row interpolation
  • Adds a RFC 4180-compliant CSV parser as a shared utility

For Migrate:

# Add contacts to a segment from a CSV
resend contacts migrate --file ./contacts.csv --to-segment abc-123

# Move contacts (add + remove) between segments
resend contacts migrate --file ./contacts.csv --to-segment abc-123 --from-segment def-456

# JSON input, custom column, concurrency control
resend contacts migrate --file ./ids.json --to-segment abc-123 --column contact_id --concurrency 10

For Bulk Send:

# Template mode — CSV columns become template variables automatically
resend emails batch-csv --file ./recipients.csv --template-id tmpl_abc --from you@domain.com

# Inline mode — {{placeholder}} interpolation in subject/html/text
resend emails batch-csv --file ./recipients.csv --from you@domain.com \
  --subject "Hello {{name}}" --text "Hi {{name}}, welcome to {{plan}}!"

Summary by cubic

Adds resend contacts migrate and resend emails batch-csv to support bulk contact segment migration and bulk email sending from CSV, with concurrency, interpolation, and compact JSON summaries. Also introduces an RFC 4180 CSV parser shared by both commands.

  • New Features
    • resend contacts migrate: Add or move contacts between segments from CSV or JSON (array of strings or objects). Supports --from-segment, --column, --concurrency, and --yes; validates source/target differ; continues on errors and outputs migrated/failed/total (with error details).
    • resend emails batch-csv: Send from CSV using --template-id (non-to columns become template variables) or inline --subject/--html|--html-file/--text with {{column}} interpolation. Auto-chunks at 100, suffixes per-chunk --idempotency-key, and supports --to-column (default to), --reply-to, --tags, and --batch-validation (strict default or permissive); returns IDs, totals, and indexed errors.
    • CSV utility: Added RFC 4180-compliant parser (src/lib/csv.ts) handling quoted fields, escaped quotes, multiline values, CRLF/LF, and header validation.

Written for commit 937408b. Summary will update on new commits.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

9 issues found across 8 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="tests/lib/csv.test.ts">

<violation number="1" location="tests/lib/csv.test.ts:69">
P2: Test codifies whitespace trimming that contradicts the parser’s RFC 4180 compliance claim, creating a contract mismatch and potential data mutation.</violation>

<violation number="2" location="tests/lib/csv.test.ts:91">
P2: Error-path CSV tests are overly broad (`rejects.toThrow()`), so unrelated exceptions can pass without verifying the intended exit/error contract.</violation>
</file>

<file name="src/commands/contacts/migrate.ts">

<violation number="1" location="src/commands/contacts/migrate.ts:107">
P1: Missing guard for identical `--from-segment` and `--to-segment` can remove contacts from the intended destination segment.</violation>
</file>

<file name="src/lib/csv.ts">

<violation number="1" location="src/lib/csv.ts:54">
P1: CSV rows with mismatched field counts are silently accepted, causing dropped or backfilled data instead of failing fast on malformed input.</violation>

<violation number="2" location="src/lib/csv.ts:57">
P2: CSV parser trims quoted cell values, causing silent data modification instead of preserving quoted content.</violation>
</file>

<file name="tests/commands/emails/batch-csv.test.ts">

<violation number="1" location="tests/commands/emails/batch-csv.test.ts:22">
P2: Batch send mock uses an incorrect nested response shape (`data.data`), which can hide production parsing bugs against Resend’s flat `data` array contract.</violation>
</file>

<file name="src/commands/emails/batch-csv.ts">

<violation number="1" location="src/commands/emails/batch-csv.ts:224">
P2: Inline placeholder interpolation incorrectly excludes the configured recipient column, so `{{to}}` / `{{<to-column>}}` never resolve despite inline docs implying CSV columns are interpolatable.</violation>

<violation number="2" location="src/commands/emails/batch-csv.ts:275">
P2: Chunked batch sends reuse the same idempotency key across multiple API requests, violating Resend guidance that keys be unique per request and risking incorrect idempotency behavior on later chunks.</violation>

<violation number="3" location="src/commands/emails/batch-csv.ts:297">
P2: `strict` mode is documented as "fail all on error", but chunked processing continues sending later batches after a strict-mode batch error, causing unintended partial sends.</violation>
</file>

Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Add one-off context when rerunning by tagging @cubic-dev-ai with guidance or docs links (including llms.txt)
  • Ask questions if you need clarification on any suggestion

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

} from '../../helpers';

const mockBatchSend = vi.fn(async () => ({
data: { data: [{ id: 'abc123' }, { id: 'def456' }] },
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Batch send mock uses an incorrect nested response shape (data.data), which can hide production parsing bugs against Resend’s flat data array contract.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/commands/emails/batch-csv.test.ts, line 22:

<comment>Batch send mock uses an incorrect nested response shape (`data.data`), which can hide production parsing bugs against Resend’s flat `data` array contract.</comment>

<file context>
@@ -0,0 +1,339 @@
+} from '../../helpers';
+
+const mockBatchSend = vi.fn(async () => ({
+  data: { data: [{ id: 'abc123' }, { id: 'def456' }] },
+  error: null,
+}));
</file context>
Fix with Cubic

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a false positive 😶

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 4 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/commands/contacts/migrate.ts">

<violation number="1" location="src/commands/contacts/migrate.ts:114">
P2: A new runtime error code (`invalid_segments`) is emitted but not declared in the command’s documented `errorCodes` list, causing help/docs to be out of sync with actual JSON error behavior.</violation>
</file>

<file name="src/commands/emails/batch-csv.ts">

<violation number="1" location="src/commands/emails/batch-csv.ts:327">
P2: Skipped-row indexes in strict mode are miscomputed for later remaining chunks, causing overlapping/incorrect `errors[].index` values.</violation>
</file>

<file name="src/lib/csv.ts">

<violation number="1" location="src/lib/csv.ts:122">
P1: Unterminated quoted CSV fields are accepted instead of rejected, allowing malformed input to collapse rows and corrupt bulk operations.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant