Self-hosted service
EFForg/action-center-platform avatar
EFForg/action-center-platform

EFF Action Center: a self-hosted campaign platform for petitions, calls and congressional messages

The EFF Action Center Platform

549 stars74 forksRubyAGPL-3.0

At a glance

What is it?
The Action Center Platform is the EFF maintained online organizing tool that powers act.eff.org, where administrators build targeted campaigns and users sign petitions, place phone calls to their representatives and send congressional messages. It is a Ruby on Rails application with a Docker Compose deployment, but it depends on a long list of third party services including Twilio, SmartyStreets, Phantom of the Capitol and the Google Civic Information API, and it is AGPL-3.0 licensed.
Who is it for?
The Action Center Platform fits a digital advocacy organisation that wants to run its own campaigns, control its own data and keep the same feature set as act.eff.org, and it fits a team that already has the third party credentials these campaigns depend on. It does not fit a small team starting from scratch, because the address lookup, phone calling and congressional delivery are all delegated to paid external services that must be configured before anything works.
Can I use it commercially?
Yes, with strict conditions. AGPL-3.0 is a network copyleft licence: if people use a modified version over a network, for example as a hosted service, you must offer them its source code under the same licence.
Is it still maintained?
Yes. The repository last received commits 178 days ago.
What is it written in?
Mainly Ruby, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the Action Center is for and who runs it

The Action Center Platform is an online organizing tool maintained by EFF. Administrators can create targeted campaigns where users sign petitions, contact legislators and engage on social media.

That is the whole product in one sentence, and the design follows from it. This is not a forum or a CMS, it is a campaign authoring tool. An administrator builds a campaign, chooses which action type it is, sets the recipients, the message and the branding, and then the tool handles the mechanics of collecting sign-ups and delivering the resulting contact to a legislator, a phone bank, or a local institution.

The project is written in Ruby on Rails, with the primary language recorded as Ruby. The repository layout confirms a conventional Rails application: `app/`, `config/`, `db/`, `lib/`, `public/`, `script/`, `spec/`, `features/`, `vendor/`, plus a `config.ru`, a `Rakefile`, a `Gemfile` with a lockfile, a `Dockerfile`, a `docker-compose.yml.example` and a `varnish.vcl`. The presence of `features/` alongside `spec/` is worth noting, because it means the project carries both RSpec-style unit and feature tests, and a `varnish.vcl` implies a deployment behind Varnish rather than a bare Rails server.

The stack choices are also visible in the tree. There is a `package.json` and a `yarn.lock` next to the Gemfile, with a small JavaScript dependency set, and the Sass linting and ESLint configuration sit alongside the RuboCop configuration. The `.ruby-gemset` and `.ruby-version` files show a Bundler-era Ruby setup, and the `.ebextensions/` directory is an Elastic Beanstalk deployment artefact that survives in the tree even though the documented deployment path is Docker.

The project is AGPL-3.0 licensed, the repository publishes no GitHub releases, and the last push was on 2026-04-28.

The five action types and the shared campaign furniture

Administrators can create four types of actions according to the project description, though five are then described in detail.

Call Action connects a user to a political leader by phone, where they leave a message or sometimes speak to an aid. The user is shown a script to read, and enters their phone number and email. When they submit that information they are called and the process begins.

Congress Action is a four-part form. Page one asks for the user's street address and zip code. Based on that, page two displays the user's representatives, and the user selects which representatives they wish to contact and then fills in whatever required fields each selected representative has. Page three asks the user to customise the message to be delivered. Page four is the thank you page with share links for social media. When creating the action, the admin can choose House, Senate, both or specific legislators.

Petition Action collects an email address and sometimes location data, and can optionally target local institutions such as universities, with signatures broken down by institution.

Tweet Action invites a user to join a tweet action using their Twitter account.

Email Action either emails a target directly or submits comments to a congressperson through Phantom of the Capitol.

The shared elements are what turn these into a coherent campaign tool. Every action ends on a thank you page inviting the user to share it socially. An admin can add partner organizations, customise the share messages and the thank you email, and choose a banner image from the library. The title of the action form is always Take Action, which is a small consistency detail that matters when a site embeds several campaigns.

The four-page Congress Action flow is the most interesting design decision here, and it is the one with a usability cost. Asking for a street address and zip before showing the user who their representatives are is a friction step, but it is the step that makes the whole thing work, since representative lookup depends on the address and the Civic Information API constraints mean the experience has to be the same for every user.

Getting it running with Docker Compose, and the accounts you need first

The documented setup path is Docker with Docker Compose, and the instructions are a numbered sequence. You install Docker and Docker Compose, clone the repository, copy `docker-compose.yml.example` to `docker-compose.yml` and `.env.example` to `.env`, and fill in the variables in `.env` according to the instructions in that file. Then you build the image with `sudo docker-compose build` and run the application with `sudo docker-compose up`.

Once it is running you open a shell inside the container with `sudo docker-compose exec app bash`, and from there two rake tasks do the initial work. If you are not running migrations automatically, `rake db:migrate` migrates the database, and `rake congress:update` populates the CongressMember table.

That CongressMember table matters more than it looks. It is the local copy of the legislators that the campaign actions target, and the third party integrations section explains that the SmartyStreets API key and id are what allow Congress members to be looked up for users. Without that table populated, an address lookup has nothing to return.

The environment file is where the real work is, and the sections in it map directly onto the integrations. The standard Rails variables are `secret_key_base`, `devise_secret_key`, `force_ssl` and `time_zone`. Database configuration is `db_username`, `db_password`, `db_port`, `db_host` and `DB_AUTO_MIGRATE`, the last of which controls whether migrations run every time the container starts. Social media handles and the four Twitter OAuth variables are there for the tweet action, and the comment says they are only needed if you want to auto-fetch user avatars when a tweet target is added as an individual.

Mail settings, S3 storage with the region and bucket, and the third party integration block with the congress forms url, the Google Civic Information API url and key, the SmartyStreets id and token, the call tool url and key, and a supporters API integration round it out.

The third party dependencies that decide whether a campaign works

This is the part of the project that deserves the most attention before you deploy it, because the Action Center is a thin layer over a set of external services and every one of them is a point of failure.

Amazon S3 key and key id exist so admins can upload images for the ActionPages. The SmartyStreets API key and id allow Congress members to be looked up for users, which is the address to representative step. Phantom of the Capitol whitelisting on the server side is what allows users to submit e-messages to Congress. Call Congress provides a url and an API key and connects calls between citizens and their congress person using the Twilio API. And the Google Civic Information API url and key provides representative information, used when a user needs to find their representatives to complete a state-level email action.

The documentation is candid about the Civic Information limitations, and it links the data guidelines, including the instruction that developers using the API should make every effort to ensure all users are met with the same experience, and that holdbacks, A/B testing or similar experiments are not allowed. That constraint reaches into the product design rather than just the integration, because a campaign platform that wants to test a different flow for some users cannot use that API for address lookups.

So the practical architecture is that the Action Center owns the campaign authoring, the pages, the sign-up forms and the thank you flows, while address resolution, phone delivery and congressional message submission all happen elsewhere. The `.env.example` file confirms it in configuration terms, with `google_civic_api_url` pointing at a real v2 representatives endpoint, `congress_forms_url` pointing at a Phantom instance, `call_tool_url` at a call service, and `smarty_streets_id` and `smarty_streets_token` as the lookup credentials.

The consequence for an adopter is that you are running a frontend and a workflow engine, and you are renting the hard parts. That is a legitimate and common choice for advocacy tooling, since it avoids building and maintaining an address database and a telephony bridge. It also means that a campaign's reach depends on someone else's quota and uptime.

Admin accounts, background jobs and the cron tasks behind the campaigns

User self-registration is disabled, and administrators are created with rake tasks rather than through the web interface. You create the user with `rake users:create[[email protected]]`, then grant admin access with `rake users:add_admin[[email protected]]`, and the documentation notes that the square brackets are required. A new user has to complete an email confirmation before they can log in, and administrators reach the admin features by clicking `admin` in the nav.

The reverse operations are equally explicit, `rake users:remove_admin[[email protected]]` to take the privilege away, and `rake users:list_admins` to see who currently holds it. That is a small but telling design: admin rights are not a database flag you flip by hand, they are a rake task you run, which keeps the audit trail in the shell history rather than in a UI.

Background work runs on DelayedJob, which Action Center uses to perform certain tasks outside of a web request context. The documentation points at that project for how to run a worker, and notes that the docker-compose example shows how to create a service which processes the job queue indefinitely.

That last clause is the operational detail people miss. A Rails app with a queue and no worker looks like a working app whose actions quietly never finish, and the compose file's worker service is what closes that gap.

Alongside the queue there is cron work. The documentation mentions automating other tasks such as `rake signatures:deduplicate` and `rake congress:update` to run occasionally, and says the compose example illustrates a service that runs these commands periodically using cron. The Dockerfile supports that directly, copying `docker/crontab` into `/etc/cron.d/crontab` and setting its permissions, and it installs the `cron` package in the apt layer.

So the two timers have different jobs. The DelayedJob worker handles the work triggered by a user action in the moment, and the cron entry handles the housekeeping that keeps the data set fresh and the signature list clean.

Embedding an action on another site

Embedding is deliberately crude, and that is a feature for a campaign tool. You include a script tag and a link on the page where you want the action to appear:

html
    <script type="text/javascript" src="https://act.eff.org/action/embed"></script>
    <a class="action-center-widget" href="https://act.eff.org/action/shut-the-nsa-s-backdoor-to-the-internet">Take part in the action!</a>

The href on the link has to point at the action page you want to embed, and you can add `?nosignup=1` to the URL to leave the newsletter signup fields out of the action, which matters for a partner site that runs its own signup.

The embedded widget is configurable through a JavaScript object named ac_embed, and the documentation lists the optional parameters. There is a css property to specify a CSS file url, with the constraint that the file must be globally available, on the Internet rather than local or staging. There is a width property to specify a width manually, and a no_css property to remove the default styles.

The global availability constraint on the CSS is the detail that trips people up. You cannot point the widget at a stylesheet on your own staging server, because the page that loads it may be on a different origin, and the documentation is explicit that the file has to be reachable on the public Internet. In practice that means an extra deployment step for a partner site, and it means the embedded widget inherits the look of the Action Center unless you host and serve your own stylesheet.

A third party campaign that asks for a street address, a phone number and an email, and then delivers a phone call or a message to a legislator, is also a significant amount of personal data in a page you do not control, which is worth weighing before embedding one on a site that is not yours.

Where the Action Center is the wrong choice, and the licence position

The first limit is the dependency surface, and it is the reason this is a project for an organisation with some operational capacity rather than a side project. Before a single campaign can be created you need credentials for S3, SmartyStreets, Twilio through Call Congress, Phantom of the Capitol and the Google Civic Information API, and a server to run the Rails app, the DelayedJob worker and the cron service. That is a lot of moving parts for an organisation whose primary asset is a website.

The second limit is the address lookup dependency in particular, because the Civic Information API's own guidelines constrain how you may use it, and a campaign tool that wants to run experiments or personalise the experience is in tension with a provider that expects the same experience for every user.

The third is the Rails version. The Dockerfile is built on ruby:3.3-bullseye, which is a Ruby 3.3 image on a Debian base, and the project carries a `.ruby-version` and a Gemfile.lock. That is a modern enough stack, but it means you are running a Rails application that depends on a set of external services whose APIs can change, and there is no published release history to pin to, since the repository publishes no GitHub releases. The last push was on 2026-04-28, so the tree is ahead of any tag you might find.

Compare that with a hosted campaign platform with the same feature set. The difference in approach is that a hosted service gives you the whole stack, the phone bridge and the address database for a monthly fee, and you get no code to run, while the Action Center gives you full control of the code, the data and the deployment, at the cost of every integration and every uptime problem being yours.

The licence is AGPL-3.0, with the licence file in the repository root, and that is the part to think about carefully rather than skim. AGPL-3.0 includes the network use clause, so a modified version offered to users over a network has source availability obligations that reach past the server side. For an organisation running its own campaigns on its own site, that is usually not a problem. For a consultancy that hosts a modified Action Center for clients, it is a serious question for a lawyer, and it is the main reason to read the LICENSE file before building a service on top of this rather than after.

Editorial conclusion

The Action Center Platform fits a digital advocacy organisation that wants to run its own campaigns, control its own data and keep the same feature set as act.eff.org, and it fits a team that already has the third party credentials these campaigns depend on. It does not fit a small team starting from scratch, because the address lookup, phone calling and congressional delivery are all delegated to paid external services that must be configured before anything works. Before you deploy it, read the notable dependencies list and get SmartyStreets, Twilio, Phantom of the Capitol, Google Civic Information and S3 credentials in hand, run the two rake tasks to create your first admin, and decide how you will run the DelayedJob worker, since that is the piece that quietly stops campaigns from completing without it.

Frequently asked questions

What is the EFF Action Center Platform?

It is an online organizing tool maintained by EFF that powers act.eff.org. Administrators create targeted campaigns where users sign petitions, contact legislators and engage on social media, and it is a Ruby on Rails application with a Docker Compose deployment.

How do I run the Action Center locally?

Install Docker and Docker Compose, clone the repository, copy `docker-compose.yml.example` to `docker-compose.yml` and `.env.example` to `.env`, fill in the environment variables, then build with `sudo docker-compose build` and run with `sudo docker-compose up`. Inside the container, run `rake db:migrate` if migrations are not automatic and `rake congress:update` to populate the CongressMember table.

What third party services does the Action Center depend on?

Amazon S3 for admin image uploads, SmartyStreets for looking up Congress members, Phantom of the Capitol for submitting e-messages to Congress, Call Congress with the Twilio API for phone actions, and the Google Civic Information API for representative information. Keys for each are configured in the .env file.

How do I create an admin user in the Action Center?

User self-registration is disabled, so you create the user with `rake users:create[[email protected]]` and then grant admin access with `rake users:add_admin[[email protected]]`, including the square brackets. The new user must complete an email confirmation before logging in, and `rake users:list_admins` shows who currently holds the privilege.

What licence does the Action Center Platform use?

The project is released under AGPL-3.0, with the licence text in the repository root. The repository publishes no GitHub releases, it is not archived, and the last push was on 2026-04-28.

Official sources

  1. EFForg/action-center-platform on GitHub
  2. Issues
  3. License: AGPL-3.0
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/efforg-action-center-platform.svg)](https://hysenlabs.com/projects/efforg-action-center-platform)

Was this useful?