SharePoint to Amazon RDS (SQL Server, Postgres, MySQL engines)

SharePoint lists in an Amazon RDS instance running SQL Server, PostgreSQL or MySQL, reached from inside your VPC with TLS switched on.

Book a call

Book a call to talk through your use case, see further walkthroughs or demos, and set up a trial in your own environment.

Last updated

One way: SharePoint stays the system of record, and SPSync keeps a read-only copy of your lists current in Amazon RDS.

Status: Shipped · product: SPSync for Relational Databases

On AWS, a managed database is often already there: an RDS instance that patches itself, takes its own backups and fails over to a standby. SPSync can write SharePoint list data into that instance, whichever engine it runs, so the lists sit beside the rest of your AWS data without a new service to run. This page covers the PostgreSQL engine in most detail, and notes where SQL Server, MySQL or MariaDB on RDS behave differently.

How do I connect SPSync to Amazon RDS?

Every engine takes a connection string from an environment variable and a database username and password.

Engine on RDSWhat to add to the connection string
SQL ServerEncrypt=yes, plus TrustServerCertificate=yes unless the RDS certificate bundle is in the host’s trust store
PostgreSQLsslmode=require, or sslmode=verify-full with sslrootcert pointing at the regional RDS bundle
MySQL or MariaDBssl_ca pointing at the downloaded regional RDS bundle

On RDS for PostgreSQL the rds.force_ssl parameter decides whether the server refuses plain connections. It defaults to on for PostgreSQL 15 and later and to off for 14 and older, so an older instance accepts an unencrypted sync unless the string asks for TLS. Set sslmode either way, and the behaviour stays the same after a major version upgrade.

If the instance is private, the machine running the sync needs a route into the VPC and a security group rule that lets it in on the engine’s port. SPSync writes to RDS through the same SQL Server, PostgreSQL and MySQL destinations it uses everywhere else. Its automated tests run those engines in containers, and the RDS settings above come from the AWS documentation, so check the first connection from the sync host with the engine’s own client.

A user for the sync on RDS for PostgreSQL

The master user can do everything, which is more than the sync needs. Create a login that owns two schemas: the one the tables go into, and a second with _staging on the end, where each run stages changed rows before merging them into the real tables.

create role spsync_writer login password 'from-your-secrets-store';
create schema procurement authorization spsync_writer;
create schema procurement_staging authorization spsync_writer;

Then set the replication’s dataset_name to procurement. On PostgreSQL 15 and later, users other than the database owner can no longer create tables in the public schema, so a sync pointed at public with a fresh login fails on its first CREATE TABLE. A schema of its own avoids that and keeps the SharePoint tables apart from your app’s.

SQL Server and MySQL on RDS

The same list replicates to the other engines with the same table names, and a few engine details differ.

RDS for SQL Server. The master user is not a member of sysadmin, but it can create logins, which is all the sync needs. Create a login, a user in the target database and the two schemas, then give the user the right to create tables and set dataset_name to sharepoint:

create login spsync_writer with password = 'from-your-secrets-store';
go
use procurement;
go
create user spsync_writer for login spsync_writer;
go
create schema sharepoint authorization spsync_writer;
go
create schema sharepoint_staging authorization spsync_writer;
go
grant create table to spsync_writer;
go

The connection string needs Encrypt=yes. To refuse unencrypted clients on the server side as well, set rds.force_ssl in the instance’s parameter group. Text arrives as nvarchar, so names in any script survive the trip.

RDS for MySQL and MariaDB. A MySQL “schema” is a database, so the sync writes to the database the connection string names and stages rows in a second database with _staging on the end. Grant the user CREATE, ALTER, DROP, INDEX, SELECT, INSERT, UPDATE and DELETE on both. The require_secure_transport parameter makes TLS mandatory on the server; the sync’s ssl_ca setting makes it verify the server whether or not that parameter is on.

Column types on each engine

A purchase-request list on a site replicated with the prefix fin, with a Supplier lookup, an Approver person column, an Estimate number and a multi-choice Categories column, becomes fin_purchase_requests on any engine. The lookup arrives as supplier_id, which joins to fin_suppliers when you replicate the Suppliers list too. approver_user_id points at the people table, sp_users. Categories becomes fin_purchase_requests__categories, one row per value. The types follow the engine:

SharePoint columnPostgreSQLSQL ServerMySQL
Single line of textcharacter varyingnvarchar(4000)TEXT
Choicecharacter varyingnvarchar(255)VARCHAR(255)
Numberdouble precisionfloatDOUBLE
Date and timetimestamptzdatetime2DATETIME(6)
Personuuid key into sp_usersuniqueidentifier keyVARCHAR(36) key

On MySQL, text that has no natural length stays TEXT, which MySQL will not index without a prefix you set yourself, while choice values and IDs get a bounded VARCHAR you can index directly. When a SharePoint column changes type, SPSync adds a _v2 column beside the original on all three engine families, so a report built on the old column keeps running.

Worked example: spend by category and approver

On the PostgreSQL engine, open requests by category with the approver’s name:

select c.value as category,
u.display_name as approver,
count(*) as requests,
sum(r.estimate) as estimated_spend
from fin_purchase_requests r
join fin_purchase_requests__categories c on c.sp_item_id = r.sp_item_id
left join sp_users u on u.id = r.approver_user_id
where r.sp_deleted_at is null
group by c.value, u.display_name
order by estimated_spend desc nulls last;

The same query runs on SQL Server with one change: drop nulls last, since SQL Server sorts nulls first in ascending order and last in descending order without being told. On MySQL, remove nulls last as well. The tables, keys and joins are the same on every engine, which is what lets a team move the copy from one RDS engine to another by pointing the replication somewhere new and running a full load.

Read replicas, agents and MCP

RDS read replicas give reporting tools and AI agents somewhere to read that is not the instance the sync writes to. An agent connects through an MCP server for the engine you run: a Postgres MCP server for RDS for PostgreSQL, a SQL Server one for RDS for SQL Server. SPSync’s own read-only spsync mcp serve is another option. Point whichever you use at the replica’s endpoint with a login that can only SELECT the procurement schema, and the agent can answer spend questions without any Graph permission. SharePoint item permissions are not copied to RDS, so that login’s grants, and row-level security on PostgreSQL, set the limit of what it sees.

Multi-AZ choices and readable standbys

RDS offers two kinds of Multi-AZ deployment for PostgreSQL and MySQL. A Multi-AZ instance keeps one standby that serves no traffic until a failover. A Multi-AZ DB cluster keeps two standbys that also serve reads, behind a reader endpoint, so a report can read there without a separate read replica. The sync writes through the writer endpoint in both cases and reconnects there after a failover.

Embeddings on the same RDS instance

For semantic search over the list text, RDS for PostgreSQL can hold vectors as well as rows. Current engine versions ship the pgvector extension; the master user enables it with create extension vector. Then choose Postgres with pgvector as the destination kind, with the same connection string, and each row gains an embedding of the text columns you pick, generated during the sync by OpenAI, Azure OpenAI or a local model. A purchase request is embedded again only after someone edits one of those columns. The SQL Server and MySQL engines get no vectors from SPSync, and nor does the plain PostgreSQL destination.

RDS or Aurora for this?

SPSync writes to both the same way, so the choice is the one you would make for any workload. A single RDS instance is the cheaper home for a few lists read by one report. Aurora pays off when many readers hit the tables at once and you want replicas that share storage with the writer. Switching later means a new connection string and a full load, which rebuilds every table from SharePoint.

Where the cost comes from

AWS does not charge for data coming into the instance, so the sync’s writes cost nothing in transfer, wherever it runs. The costs it adds are storage, for the tables and the rows SharePoint users delete (kept with sp_deleted_at set), and backup storage beyond the free allowance as those tables grow. Reads are a different matter: a Power BI gateway or a laptop outside AWS pulling large result sets pays for data leaving the region, so model the report on aggregates or keep the gateway in AWS.

RDS limits that change the setup

Why does the first connection to RDS fail?

Four messages account for most failed first runs, and each points at one setting:

Once a run succeeds, the run history in SPSync shows each run’s start, finish and item counts. On the database side, sp_synced_at on every row records when the sync last wrote it, so select max(sp_synced_at) from procurement.fin_purchase_requests shows the newest change that has arrived. It only moves when an item changes, so a quiet list looks old there even though the sync ran on time.

What it is used for

Report builders move a Power BI model off the SharePoint connector and onto RDS tables. App developers use the instance as an application read replica for something already hosted on AWS. Teams with a Power Automate flow copying rows into a database retire the flow. Analysts get joins across lists in plain SQL.

Steps

  1. Sign in with delegated access, or have an admin grant Sites.Selected on the sites you need for unattended runs.
  2. Create the sync’s login and schema, then put the RDS connection string, with its TLS settings, in an environment variable.
  3. Choose the lists, libraries or whole site.
  4. The first run builds the schema and loads every item. Later runs move only the changes, plus a reconcile, weekly by default.
  5. Connect Power BI, Metabase or your app to the instance or a read replica.

RDS storage and IOPS are provisioned and billed by AWS, separately from SPSync.

Desktop, Azure or command line

All three editions run the same engine, write the same tables and use the same licence key, so the choice comes down to who looks after the sync.

Desktop app

For macOS and Windows. Sign in with Microsoft, tick the lists, choose a database and sync from your own machine on the schedule you set. Delegated sign-in needs no admin unless your tenant requires admin consent for apps.

In your Azure

SPSync in your Azure, a managed application for your own subscription, is deployed with you on a call while the Marketplace listing is prepared. Once deployed it is always on, so the sync keeps running with every laptop closed.

Command line

Every replication is a YAML file you can review and commit. Run it from a server, a container or a CI pipeline, and re-run it safely any time.

Frequently asked questions

Which RDS engines does SPSync support?

SQL Server, PostgreSQL, MySQL and MariaDB on RDS. Each one is written the way SPSync writes that engine anywhere else, so pick the engine your AWS estate already runs.

Does the RDS instance need a public endpoint?

No. SPSync connects as a client on the engine's own port, so a private instance works from any machine inside the VPC or with a route into it. A public endpoint also works if your security group rules allow the sync host.

Can SPSync use IAM database authentication instead of a password?

No. SPSync signs in with a database username and password on every RDS engine. IAM authentication tokens are not supported.

Does a Multi-AZ failover interrupt the sync?

The endpoint name stays the same through a failover, so the next run connects to the new primary. A run in progress at that moment fails, and the next scheduled run catches up from the last one that completed.

Talk to us about Amazon RDS (SQL Server, Postgres, MySQL engines)

SPSync is licensed per installation and per target family, with Standard Support included. Tell us which targets you need and how many machines, and we will size it with you. See how licensing works.

Set it up

Sign in to SharePoint with the SharePoint connection guide, then follow the SQL Server setup guide, PostgreSQL setup guide or MySQL and MariaDB setup guide to create the database user and open the connection for Amazon RDS (SQL Server, Postgres, MySQL engines). If the connection test fails, the troubleshooting guide lists the errors people hit and what fixes them.

Script it

Everything you can click, you can script. The CLI, YAML config and automation below reproduce this scenario exactly. See the CLI reference.

spsync auth login --client-id 00000000-0000-0000-0000-000000000000
spsync export-config --output replication.yaml --pipeline-name amazon_rds --prefix amazon_rds --destination-kind postgres --connection-string-env SPSYNC_DB_CONNECTION --dataset-name amazon_rds --client-id 00000000-0000-0000-0000-000000000000 --site-url https://contoso.sharepoint.com/sites/example --all-lists
spsync doctor replication.yaml
spsync run replication.yaml
pipeline_name: amazon_rds
prefix: amazon_rds
auth:
  mode: delegated
  client_id: 00000000-0000-0000-0000-000000000000
  tenant_id: common
destination:
  kind: postgres
  connection_string_env: SPSYNC_DB_CONNECTION
  dataset_name: amazon_rds
scopes:
  - site_url: https://contoso.sharepoint.com/sites/example
    mode: entire_site
reconcile_interval_days: 7
create_reporting_views: false

Cron (PostgreSQL nightly):

# every night at 02:00
0 2 * * * cd /opt/spsync && SPSYNC_DB_CONNECTION="$(cat /etc/spsync/db.env)" uv run spsync run replication.yaml >> /var/log/spsync/run.log 2>&1

GitHub Actions:

# .github/workflows/spsync-run.yml
name: SPSync
on:
  schedule:
    - cron: "0 2 * * *"
  workflow_dispatch: {}
jobs:
  run:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v3
      - run: uv sync
      - run: uv run spsync run replication.yaml
        env:
          SPSYNC_DB_CONNECTION: ${{ secrets.SPSYNC_DB_CONNECTION }}
          SPSYNC_LICENCE_KEY: ${{ secrets.SPSYNC_LICENCE_KEY }}

Azure DevOps:

# azure-pipelines.yml
schedules:
  - cron: "0 2 * * *"
    branches: { include: [main] }
    always: true
steps:
  - script: uv sync
  - script: uv run spsync run replication.yaml
    env:
      SPSYNC_DB_CONNECTION: $(SPSYNC_DB_CONNECTION)
      SPSYNC_LICENCE_KEY: $(SPSYNC_LICENCE_KEY)

Windows (PowerShell + Task Scheduler):

# Windows Task Scheduler action (see /docs/cli/automation/)
$env:SPSYNC_DB_CONNECTION = (Get-Content C:\spsync\db.env)
uv run spsync run C:\spsync\replication.yaml
schtasks /Create /SC DAILY /ST 02:00 /TN "SPSync" /TR "powershell -File C:\spsync\run.ps1"

macOS (launchd):

# ~/Library/LaunchAgents/io.spsync.run.plist, then: launchctl load ~/Library/LaunchAgents/io.spsync.run.plist

Secrets (licence key, connection strings) come from the environment, so never write them into the YAML. See scheduling and secrets.

Book a call

Book a call to talk through your use case, see further walkthroughs or demos, and set up a trial in your own environment.