Step 1 — Create the IAM user
The credential is yours, and the bucket is yours. Create an IAM user in your own AWS account, scoped to the one bucket you want GetDialed to write into, and hand over its access-key pair. Nothing else in your account is reachable with it.The IAM policy
Attach this inline to that user, substituting your bucket name. Inline rather than a shared managed policy, so the grant cannot be widened later by a change somebody makes for a different reason./* is. A policy that puts all five actions on one ARN grants nothing useful, whichever ARN it picks.
s3:ListBucket is on the list even though nothing lists your bucket, and it is not decorative.Without it, S3 answers 403 AccessDenied for an object that simply is not there — instead of 404. That is deliberate on S3’s part: it stops a caller who is not allowed to see inside a bucket from probing it for what it contains, one key at a time.The cost is that a credential lacking s3:ListBucket makes “your object is missing” and “your credential is wrong” the same answer, and nothing on our side can tell them apart to tell you. Granting it on the bucket ARN is what lets a failed fetch or delete be diagnosed at all.It grants no ability to read, write or remove anything — those are the object actions in the second statement.s3:AbortMultipartUpload is there because a large file is uploaded in parts. If an upload fails part way through, the parts have to be cleaned up — and an upload that cannot abort leaves them behind in your bucket, billed to you, invisible in a normal object listing.
Nothing wider is needed. No s3:*, no second bucket, no account-wide "Resource": "*", and no iam or sts statement of any kind. sts:GetCallerIdentity — the call that identifies which AWS account the key belongs to — requires no permissions at all and cannot be denied by policy, so granting it would only imply that it needed granting.
Step 2 — Create the credential
Supply the key pair and the bucket’s region.aws_default_bucket is optional but worth setting: with it, a step that omits bucket uses it, so the bucket name lives in one place rather than in every step.
id; it is the connection_id the S3 step selects.
Check the grant before you build a flow on it:
Step 3 — Build the flow
Four tasks. The first two are the Five9 report you already run; the last two are new.What each of the two new steps is doing
create_file writes the set out and hands on a reference. output_mode: "record_set" on the report step already staged the rows as a sealed record set and reported its record_set_id; create_file reads that set as it runs and reports file_id, size_bytes, sha256 and row_count. A report with three million rows costs the flow exactly what a report with three costs it — see files.
format: "ndjson" is the shape to pick here. One JSON record per line is what Snowpipe reads a line at a time, so a partially-written or very large file is never a problem for the loader.
compress: "gzip" stores the file compressed, and its name gains .gz. That suffix is how Snowflake detects the compression on load — there is nothing to configure at the Snowflake end, and nothing to decompress in between.
aws__s3__put streams the file into your bucket. It takes the file_id from the previous step, not its contents. The key is the full path within the bucket and you write it out yourself, deliberately: Snowpipe watches a prefix, so the path is part of the integration rather than a detail for the platform to invent.
The key above ends in {{ system.execution_id }}, which is unique to the run — see expressions for the other identifiers available. To partition the prefix by date instead, pass the load date in with the trigger and format it in the key:
Step 4 — Point Snowpipe at the prefix
Nothing more is needed from GetDialed. On the Snowflake side, create a stage over the same bucket and prefix and a pipe that auto-ingests from it, with a JSON file format for the NDJSON you wrote. Leave the file format’s compression setting on its automatic default, which is what reads the.gz suffix create_file gave the object — that is the whole reason the suffix is worth having. Consult Snowflake’s own documentation for the storage integration, stage, file format and pipe: that half lives entirely in your Snowflake account, and its options change on Snowflake’s schedule rather than ours.
The object landing in the bucket is the integration. There is no connector to install and no credential of yours that GetDialed holds for Snowflake.
Confirming a load
Two things are worth recording per run, and both come back from the steps you already have.row_count from create_file reconciles against the report’s own row_count — if they disagree, the file is not what the report said it was. And sha256 is computed over the bytes as stored, so anything that downloads the object can compute the same value and prove it received the file unchanged.
To check a file before uploading it — skipping an empty load, for instance — put getdialed__files__get_file_metadata between the two steps and branch on size_bytes.
Cleaning up. The stored file is kept until you delete it. If the copy in your bucket is the one that matters, either add
getdialed__files__delete_file as a final step, or set expires_in_days on create_file to 1, 7, 30 or 90 and let it lapse on its own. Deleting the stored file does not touch the object you uploaded.Next steps
Files
Formats, compression, tags, retention, and all six file steps.
Credentials
The AWS credential method in full, including S3-compatible storage.
Schedule a flow
Make it nightly.
Record transforms
Filter or reshape the report’s rows before writing the file.