{"id":13582287,"url":"https://github.com/mbrt/gmailctl","last_synced_at":"2025-05-14T21:04:19.634Z","repository":{"id":33260014,"uuid":"143890171","full_name":"mbrt/gmailctl","owner":"mbrt","description":"Declarative configuration for Gmail filters","archived":false,"fork":false,"pushed_at":"2025-05-05T16:11:07.000Z","size":4075,"stargazers_count":2026,"open_issues_count":17,"forks_count":78,"subscribers_count":20,"default_branch":"master","last_synced_at":"2025-05-05T17:38:05.968Z","etag":null,"topics":["cli","filters","gmail","gmail-filter","go","jsonnet","labels"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mbrt.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2018-08-07T15:05:31.000Z","updated_at":"2025-05-05T16:10:23.000Z","dependencies_parsed_at":"2023-09-26T03:51:47.523Z","dependency_job_id":"8f886052-b9d3-4035-a1f6-cde013e6132f","html_url":"https://github.com/mbrt/gmailctl","commit_stats":{"total_commits":579,"total_committers":34,"mean_commits":"17.029411764705884","dds":0.4421416234887737,"last_synced_commit":"f713dc603cbca2879fcff0c67eb877677d987765"},"previous_names":[],"tags_count":27,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mbrt%2Fgmailctl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mbrt%2Fgmailctl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mbrt%2Fgmailctl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mbrt%2Fgmailctl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mbrt","download_url":"https://codeload.github.com/mbrt/gmailctl/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254227605,"owners_count":22035668,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","filters","gmail","gmail-filter","go","jsonnet","labels"],"created_at":"2024-08-01T15:02:34.482Z","updated_at":"2025-05-14T21:04:19.564Z","avatar_url":"https://github.com/mbrt.png","language":"Go","readme":"# gmailctl\n[![Go Report Card](https://goreportcard.com/badge/github.com/mbrt/gmailctl)](https://goreportcard.com/report/github.com/mbrt/gmailctl)\n![Go](https://github.com/mbrt/gmailctl/workflows/Go/badge.svg)\n\nThis utility helps you generate and maintain Gmail filters in a declarative way.\nIt has a [Jsonnet](https://jsonnet.org/) configuration file that aims to be\nsimpler to write and maintain than using the Gmail web interface, to categorize,\nlabel, archive and manage your inbox automatically.\n\n## Table of contents\n- [gmailctl](#gmailctl)\n  - [Table of contents](#table-of-contents)\n  - [Motivation](#motivation)\n  - [Install](#install)\n  - [Usage](#usage)\n    - [Migrate from another solution](#migrate-from-another-solution)\n    - [Other commands](#other-commands)\n  - [Configuration](#configuration)\n    - [Search operators](#search-operators)\n    - [Logic operators](#logic-operators)\n    - [Reusing filters](#reusing-filters)\n    - [Actions](#actions)\n    - [Labels](#labels)\n    - [Tests](#tests)\n  - [Tips and tricks](#tips-and-tricks)\n    - [Chain filtering](#chain-filtering)\n    - [To me](#to-me)\n    - [Directly to me](#directly-to-me)\n    - [Automatic labels](#automatic-labels)\n    - [Multiple Gmail accounts](#multiple-gmail-accounts)\n  - [Known issues](#known-issues)\n    - [Apply filters to existing emails](#apply-filters-to-existing-emails)\n    - [OAuth2 authentication errors](#oauth2-authentication-errors)\n    - [YAML config is unsupported](#yaml-config-is-unsupported)\n  - [Comparison with existing projects](#comparison-with-existing-projects)\n  - [Footnotes](#footnotes)\n\n## Motivation\n\nIf you use Gmail and have to maintain (like me) a lot of filters (to apply\nlabels, get rid of spam or categorize your emails), then you probably have (like\nme) a very long list of messy filters. At a certain point one of your messages\ngot mislabled and you try to understand why. You scroll through that horrible\nmess of filters, you wish you could find-and-replace stuff, test the changes on\nyour filters before applying them, refactor some filters together... in a way\ntreat them like you treat your code!\n\nGmail allows one to import and export filters in XML format. This can be used to\nmaintain them in some better way... but dear Lord, no! Not by hand! That's what\nmost other tools do: providing some kind of DSL that generate XML filters that\ncan be imported in your settings... by hand [this is the approach of the popular\n[antifuchs/gmail-britta](https://github.com/antifuchs/gmail-britta) for\nexample].\n\nGmail happens to have also a neat API that we can use to automate the import\nstep as well, so to eliminate all manual, slow tasks to be done with the Gmail\nsettings.\n\nThis project then exists to provide to your Gmail filters:\n\n1. Maintainability;\n2. An easy to understand, declarative, composable language;\n3. A builtin query simplifier, to keep the size of your filters down (Gmail has\n   a limit of 1500 chars per filter);\n4. Ability to review your changes before applying them;\n5. Automatic update of the settings (no manual import) in seconds.\n\n## Install\n\ngmailctl is written in Go and requires a recent version (see [go.mod](go.mod)).\nMake sure to setup your [`$GOPATH`](https://golang.org/doc/code.html#GOPATH)\ncorrectly and include its `bin` subdirectory in your `$PATH`.\n\n```\ngo install github.com/mbrt/gmailctl/cmd/gmailctl@latest\n```\n\nAlternatively, if you're on macOS, you can install easily via Homebrew or Macports:\n\n```\n# Install with Homebrew\nbrew install gmailctl\n```\n```\n# Install with Macports\nsudo port install gmailctl\n```\n\nOn Fedora Linux, you can install from the official repositories:\n\n```\nsudo dnf install gmailctl\n```\n\nYou can also choose to install the snap:\n\n```\nsudo snap install gmailctl\n```\n\nIf so, make sure to configure xdg-mime to open the config file with your favorite\neditor. For example, if you'd like to use `vim`:\n\n```\nxdg-mime default vim.desktop text/x-csrc\n```\n\nOnce installed, run the init process:\n\n```\ngmailctl init\n```\n\nThis will guide you through setting up the Gmail APIs and update your\nsettings without leaving your command line.\n\n## Usage\n\n[![asciicast](https://asciinema.org/a/1NIWhzeJNcrN7cCe7mGjWQQnx.svg)](https://asciinema.org/a/1NIWhzeJNcrN7cCe7mGjWQQnx)\n\nThe easiest way to use gmailctl is to run `gmailctl edit`. This will open the\nlocal `.gmailctl/config.jsonnet` file in your editor. After you exit the editor\nthe configuration is applied to Gmail. See [Configuration](#configuration) for\nthe configuration file format. This is the preferred way if you want to start\nyour filters from scratch.\n\n**NOTE:** It's recommended to backup your current configuration before you apply\nthe generated one for the first time. Your current filters will be wiped and\nreplaced with the ones specified in the config file. The diff you'll get during\nthe first run will probably be pretty big, but from that point on, all changes\nshould generate a small and simple to review diff.\n\n### Migrate from another solution\n\nIf you want to preserve your current filters and migrate to a more sane\nconfiguration gradually, you can try to use the `download` command. This will\nlook up at your currently configured filters in Gmail and try to create a\nconfiguration file matching the current state.\n\n**NOTE:** This functionality is experimental. It's recommended to download the\nfilters and check that they correspond to the remote ones before making any\nchanges, to avoid surprises. Also note that the configuration file will be quite\nugly, as expressions won't be reconstructed properly, but it should serve as a\nstarting point if you are migrating from other systems.\n\nExample of usage:\n\n```bash\n# download the filters to the default configuration file\ngmailctl download \u003e ~/.gmailctl/config.jsonnet\n# check that the diff is empty and no errors are present\ngmailctl diff\n# happy editing!\ngmailctl edit\n```\n\nOften you'll see imported filters with the `isEscaped: true` marker. This tells\ngmailctl to not escape or quote the expression, as it might contain operators\nthat have to be interpreted as-is by Gmail. This happens when the `download`\ncommand was unable to map the filter to native gmailctl expressions. It's\nrecommended to manually port the filter to regular gmailctl operators before\ndoing any changes, to avoid unexpected results. Example of such conversion:\n\n```jsonnet\n{\n  from: \"{foo bar baz}\",\n  isEscaped: true,\n}\n```\n\nCan be translated into:\n\n```jsonnet\n{\n  or: [\n    {from: \"foo\"},\n    {from: \"bar\"},\n    {from: \"baz\"},\n  ],\n}\n```\n\n### Other commands\n\nAll the available commands (you can also check with `gmailctl help`):\n\n```\n  apply       Apply a configuration file to Gmail settings\n  debug       Shows an annotated version of the configuration\n  diff        Shows a diff between the local configuration and Gmail settings\n  download    Download filters from Gmail to a local config file\n  edit        Edit the configuration and apply it to Gmail\n  export      Export filters into the Gmail XML format\n  help        Help about any command\n  init        Initialize the Gmail configuration\n  test        Execute config tests\n```\n\n## Configuration\n\n**NOTE:** Despite the name, the configuration format is stable at `v1alpha3`.\nIf you are looking for the deprecated versions `v1alpha1`, or `v1alpha2`,\nplease refer to [docs/v1alpha1.md](docs/v1alpha1.md) and\n[docs/v1alpha2.md](docs/v1alpha2.md).\n\nThe configuration file is written in Jsonnet, that is a very powerful\nconfiguration language, derived from JSON. It adds functionality such as\ncomments, variables, references, arithmetic and logic operations, functions,\nconditionals, importing other files, parameterizations and so on. For more\ndetails on the language, please refer to [the official\ntutorial](https://jsonnet.org/learning/tutorial.html).\n\nSimple example:\n\n```jsonnet\n// Local variables help reuse config fragments\nlocal me = {\n  or: [\n    { to: 'pippo@gmail.com' },\n    { to: 'pippo@hotmail.com' },\n  ],\n};\n\n// The exported configuration starts here\n{\n  version: 'v1alpha3',\n  // Optional author information (used in exports).\n  author: {\n    name: 'Pippo Pluto',\n    email: 'pippo@gmail.com'\n  },\n  rules: [\n    {\n      filter: {\n        and: [\n          { list: 'geeks@newsletter.com' },\n          { not: me },  // Reference to the local variable 'me'\n        ],\n      },\n      actions: {\n        archive: true,\n        labels: ['news'],\n      },\n    },\n  ],\n}\n```\n\nThe Jsonnet configuration file contains mandatory version information, optional\nauthor metadata and a list of rules. Rules specify a filter expression and a set\nof actions that will be applied if the filter matches.\n\nFilter operators are prefix of the operands they apply to. In the example above,\nthe filter applies to emails that come from the mail list 'geeks@newsletter.com'\nAND the recipient is not 'me' (which can be 'pippo@gmail.com' OR\n'pippo@hotmail.com').\n\nWe will see all the features of the configuration file in the following\nsections.\n\n### Search operators\n\nSearch operators are the same as the ones you find in the Gmail filter\ninterface:\n\n* `from`: the mail comes from the given address\n* `to`: the mail is delivered to the given address\n* `subject`: the subject contains the given words\n* `has`: the mail contains the given words\n\nIn addition to those visible in the Gmail interface, you can specify natively\nthe following common operators:\n\n* `list`: the mail is directed to the given mail list\n* `cc`: the mail has the given address as CC destination\n* `bcc`: the mail has the given address as BCC destination\n* `replyto`: the mail has the given address as Reply-To destination\n\nOne more special function is given if you need to use less common operators\u003csup\nid=\"a1\"\u003e[1](#f1)\u003c/sup\u003e, or want to compose your query manually:\n\n* `query`: passes the given contents verbatim to the Gmail filter, without\n  escaping or interpreting the contents in any way.\n\nExample:\n\n```jsonnet\n{\n  version: 'v1alpha3',\n  rules: [\n    {\n      filter: { subject: 'important mail' },\n      actions: {\n        markImportant: true,\n      },\n    },\n    {\n      filter: {\n        query: 'dinner AROUND 5 friday has:spreadsheet',\n      },\n      actions: {\n        delete: true,\n      },\n    },\n  ],\n}\n```\n\n### Logic operators\n\nFilters can contain only one expression. If you want to combine multiple of them\nin the same rule, you have to use logic operators (and, or, not). These\noperators do what you expect:\n\n* `and`: is true only if all the sub-expressions are also true\n* `or`: is true if one or more sub-expressions are true\n* `not`: is true if the sub-expression is false.\n\nExample:\n\n```jsonnet\n{\n  version: 'v1alpha3',\n  rules: [\n    {\n      filter: {\n        or: [\n          { from: 'foo' },\n          {\n            and: [\n              { list: 'bar' },\n              { not: { to: 'baz' } },\n            ],\n          },\n        ],\n      },\n      actions: {\n        markImportant: true,\n      },\n    },\n  ],\n}\n```\n\nThis composite filter marks the incoming mail as important if:\n\n* the message comes from \"foo\", _or_\n* it is coming from the mailing list \"bar\" _and_ _not_ directed to \"baz\"\n\n### Reusing filters\n\nFilters can be named and referenced in other filters. This allows reusing\nconcepts and so avoid repetition. Note that this is not a gmailctl functionality\nbut comes directly from the fact that we rely on Jsonnet.\n\nExample:\n\n```jsonnet\nlocal toMe = {\n  or: [\n    { to: 'myself@gmail.com' },\n    { to: 'myself@yahoo.com' },\n  ],\n};\nlocal notToMe = { not: toMe };\n\n{\n  version: 'v1alpha3',\n  rules: [\n    {\n      filter: {\n        and: [\n          { from: 'foobar' },\n          notToMe,\n        ],\n      },\n      actions: {\n        delete: true,\n      },\n    },\n    {\n      filter: toMe,\n      actions: {\n        labels: ['directed'],\n      },\n    },\n  ],\n}\n```\n\nIn this example, two named filters are defined. The `toMe` filter gives a name\nto emails directed to 'myself@gmail.com' or to 'myself@yahoo.com'. The `notToMe`\nfilter negates the `toMe` filter, with a `not` operator. Similarly, the two\nrules reference the two named filters above. The `name` reference is basically\ncopying the definition of the filter in place.\n\nThe example is effectively equivalent to this one:\n\n```jsonnet\n{\n  version: 'v1alpha3',\n  rules: [\n    {\n      filter: {\n        and: [\n          { from: 'foobar' },\n          {\n            not: {\n              or: [\n                { to: 'myself@gmail.com' },\n                { to: 'myself@yahoo.com' },\n              ],\n            },\n          },\n        ],\n      },\n      actions: {\n        delete: true,\n      },\n    },\n    {\n      filter: {\n        or: [\n          { to: 'myself@gmail.com' },\n          { to: 'myself@yahoo.com' },\n        ],\n      },\n      actions: {\n        labels: ['directed'],\n      },\n    },\n  ],\n}\n```\nRelying on Jsonnet also allows [importing code and raw data](https://jsonnet.org/learning/tutorial.html#imports) from other files\u003csup id=\"a3\"\u003e[3](#f3)\u003c/sup\u003e.\n\n### Actions\n\nEvery rule is a composition of a filter and a set of actions. Those actions will\nbe applied to all the incoming emails that pass the rule's filter. These actions\nare the same as the ones in the Gmail interface:\n\n* `archive: true`: the message will skip the inbox;\n* `delete: true`: the message will go directly to the trash can;\n* `markRead: true`: the message will be mark as read automatically;\n* `star: true`: star the message;\n* `markSpam: false`: do never mark these messages as spam. Note that setting this\n  field to `true` is _not_ supported by Gmail (I don't know why);\n* `markImportant: true`: always mark the message as important, overriding Gmail\n  heuristics;\n* `markImportant: false`: do never mark the message as important, overriding\n  Gmail heuristics;\n* `category: \u003cCATEGORY\u003e`: force the message into a specific category (supported\n  categories are \"personal\", \"social\", \"updates\", \"forums\", \"promotions\");\n* `labels: [list, of, labels]`: an array of labels to apply to the message. Note\n  that these labels have to be already present in your settings (they won't be\n  created automatically), and you can specify multiple labels (normally Gmail\n  allows only one label per filter).\n* `forward: 'forward@to.com'`: forward the message to another email address. The\n  forwarding address must be already in your settings (Forwarding and POP/IMAP \u003e\n  Add a forwarding address). Gmail allows no more than 20 forwarding filters.\n  Only one address can be specified for one filter.\n\nExample:\n\n```jsonnet\n{\n  version: 'v1alpha3',\n  rules: [\n    {\n      filter: { from: 'love@gmail.com' },\n      actions: {\n        markImportant: true,\n        category: 'personal',\n        labels: ['family', 'P1'],\n      },\n    },\n  ],\n}\n```\n\n### Labels\n\nYou can optionally manage your labels with gmailctl. The config contains a\n`labels` section. Adding labels in there will opt you in to full label\nmanagement as well. If you prefer to manage your labels through the GMail web\ninterface, you can by all means still do so by simply omitting the `labels`\nsection from the config.\n\nExample:\n\n```jsonnet\n{\n  version: 'v1alpha3',\n  // optional\n  labels: [\n    { name: 'family' },\n    { name: 'friends' },\n  ],\n  rules: [\n    {\n      filter: { from: 'love@gmail.com' },\n      actions: {\n        labels: ['family'],\n      },\n    },\n  ],\n}\n```\n\nTo make this work, your credentials need to contain permissions for labels\nmanagement as well. If you configured gmailctl before this functionality was\navailable, you probably need to update your 'Scopes for Google API' in the\n'OAuth content screen' by adding `https://www.googleapis.com/auth/gmail.labels`.\nIf you don't know how to do this, just reset and re-create your credentials\nfollowing the steps in:\n\n```\n$ gmailctl init --reset\n$ gmailctl init\n```\n\nIf you want to update your existing config to include your existing labels, the\nbest way to get started is to use the `download` command and copy paste the\n`labels` field into your config:\n\n```\n$ gmailctl download \u003e /tmp/cfg.jsonnet\n$ gmailctl edit\n```\n\nAfter the import, verify that your current config does not contain unwanted\nchanges with `gmailctl diff`.\n\nManaging the color of a label is optional. If you specify it, it will be\nenforced; if you don't, the existing color will be left intact. This is useful\nto people who want to keep setting the colors with the Gmail UI. You can find\nthe list of supported colors\n[here](https://developers.google.com/gmail/api/v1/reference/users/labels).\n\nExample:\n\n```jsonnet\n{\n  version: 'v1alpha3',\n  labels: [\n    {\n      name: 'family',\n      color: {\n        background: \"#fad165\",\n        text: \"#000000\",\n      },\n    },\n  ],\n  rules: [ // ...\n  ],\n}\n```\n\nNote that renaming labels is not supported because there's no way to tell the\ndifference between a rename and a deletion. This distinction is important\nbecause deleting a label and creating it with a new name would remove it from\nall the messages. This is a surprising behavior for some users, so it's\ncurrently gated by a confirmation prompt (for the `edit` command), or by the\n`--remove-labels` flag (for the `apply` command). If you want to rename a label,\nplease do so through the GMail interface and then change your gmailctl config.\n\n### Tests\n\nYou can optionally add unit tests to your configuration. The tests will be\nexecuted before applying any changes to the upstream Gmail filters or by running\nthe dedicated `test` subcommand. Tests results can be ignored by passing the\n`--yolo` command line option.\n\nTests can be added by using the `tests` field of the main configuration object:\n\n```jsonnet\n{\n  version: 'v1alpha3',\n  rules: [ /* ... */ ],\n  tests: [\n    // you tests here.\n  ],\n}\n```\n\nA test object looks like this:\n\n```jsonnet\n{\n  // Reported when the test fails.\n  name: \"the name of the test\",\n  // A list of messages to test against.\n  messages: [\n    { /* message object */ },\n    // ... more messages\n  ],\n  // The actions that should be applied to the messages, according the config.\n  actions: {\n    // Same as the Actions object in the filters.\n  },\n}\n```\n\nA message object is similar to a filter, but it doesn't allow arbitrary\nexpressions, uses arrays of strings for certain fields (e.g. the `to` field),\nand has some additional fields (like `body`) to represent an email as faithfully\nas possible. This is the list of fields:\n\n* `from: \u003cstring\u003e`: the sender of the email.\n* `to: [\u003clist\u003e]`: a list of recipients of the email.\n* `cc: [\u003clist\u003e]`: a list of emails in cc.\n* `bcc: [\u003clist\u003e]`: a list of emails in bcc.\n* `replyto: \u003cstring\u003e`: the email listed in the Reply-To field.\n* `lists: [\u003clist\u003e]`: a list of mailing lists.\n* `subject: \u003cstring\u003e`: the subject of the email.\n* `body: \u003cstring\u003e`: the body of the email.\n\nAll the fields are optional. Remember that each message object represent one\nemail and that the `messages` field of a test is an array of messages. A common\nmistake is to provide an array of messages thinking that they are only one.\nExample:\n\n```jsonnet\n{\n  // ...\n  tests: [\n    messages: [\n      { from: \"foobar\" },\n      { to: [\"me\"] },\n    ],\n    actions: {\n      // ...\n    },\n  ],\n}\n```\n\nThis doesn't represent one message from \"foobar\" to \"me\", but two messages, one\nfrom \"foobar\" and the other to \"me\". The correct representation for that would\nbe instead:\n\n```jsonnet\n{\n  // ...\n  tests: [\n    messages: [\n      {\n        from: \"foobar\",\n        to: \"me\",\n      },\n    ],\n    actions: {\n      // ...\n    },\n  ],\n}\n```\n\n**NOTE:** Not all filters are supported in tests. Arbitrary `query` expressions\nand filters with `isEscaped: true` are ignored by the tests. Warnings are\ngenerated when this happens. Keep in mind that in that case your tests might\nyield incorrect results.\n\n## Tips and tricks\n\n### Chain filtering\n\nGmail filters are _all_ applied to a mail, if they match, in a non-specified\norder. So having some if-else alternative is pretty hard to encode by hand. For\nexample sometimes you get interesting stuff from a mail list, but also a lot of\ngarbage too. So, to put some emails with certain contents in one label and the\nrest somewhere else, you'd have to make multiple filters. Gmail filters however\nlack if-else constructs, so a way to simulate that is to declare a sequence of\nfilters, where each one negates the previous alternatives.\n\nFor example you want to:\n\n* mark the email as important if directed to you;\n* or if it's coming from a list of favourite addresses, label as interesting;\n* or if it's directed to a certain alias, archive it.\n\nLuckily you don't have to do that by hand, thanks to the utility library coming\nwith `gmailctl`. There's a `chainFilters` function that does exactly that: takes\na list of rules and chains them together, so if the first matches, the others\nare not applied, otherwise the second is checked, and so on...\n\n```jsonnet\n// Import the standard library\nlocal lib = import 'gmailctl.libsonnet';\n\nlocal favourite = {\n  or: [\n    { from: 'foo@bar.com' },\n    { from: 'baz@bar.com' },\n    { list: 'wow@list.com' },\n  ],\n};\n\n{\n  version: 'v1alpha3',\n  rules: [\n           // ... Other filters applied in any order\n         ]\n\n         // And a chain of filters\n         + lib.chainFilters([\n           // All directed emails will be marked as important\n           {\n             filter: { to: 'myself@gmail.com' },\n             actions: { markImportant: true },\n           },\n           // Otherwise, if they come from interesting senders, apply a label\n           {\n             filter: favourite,\n             actions: { labels: ['interesting'] },\n           },\n           // Otherwise, if they are directed to my spam alias, archive\n           {\n             filter: { to: 'myself+spam@gmail.com' },\n             actions: { archive: true },\n           },\n         ]),\n}\n```\n\n### To me\n\nGmail gives you the possibility to write literally `to:me` in a filter, to match\nincoming emails where you are the recipient. This is going to mostly work as\nintended, except that it will also match emails directed to `me@example.com`.\nThe risk you are getting an email where you are not one of the recipients, but a\n`me@example.com` is, is pretty low, but if you are paranoid you might consider\nusing your full email instead. The config is also easier to read in my opinion.\nYou can also save some typing by introducing a local variable like this:\n\n```jsonnet\n// Local variable, referenced in all your config.\nlocal me = 'myemail@gmail.com';\n\n{\n  version: 'v1alpha3',\n  rules: [\n    {\n      // Save typing here.\n      filter: { to: me },\n      actions: {\n        markImportant: true,\n      },\n    },\n  ],\n}\n```\n\n### Directly to me\n\nIf you need to match emails that are to you directly, (i.e. you are not in CC,\nor BCC, but only in the TO field), then the default Gmail filter `to:\nmymail@gmail.com` is not what you are looking for. This filter in fact\n(surprisingly) matches all the recipient fields (TO, CC, BCC). To make this work\nthe intended way we have to pull out this trick:\n\n```jsonnet\nlocal directlyTo(recipient) = {\n  and: [\n    { to: recipient },\n    { not: { cc: recipient } },\n    { not: { bcc: recipient } },\n  ],\n};\n```\n\nSo, from all emails where your mail is a recipient, we remove the ones where\nyour mail is in the CC field.\n\nThis trick is conveniently provided by the `gmailctl` library, so you can use it\nfor example in this way:\n\n```jsonnet\n// Import the standard library\nlocal lib = import 'gmailctl.libsonnet';\nlocal me = 'pippo@gmail.com';\n{\n  version: 'v1alpha3',\n  rules: [\n    {\n      filter: lib.directlyTo(me),\n      actions: { markImportant: true },\n    },\n  ],\n}\n```\n\n### Automatic labels\n\nIf you opted in for labels management, you will find yourself often having to\nboth add a filter and a label to your config. To alleviate this problem, you can\nuse the utility function `lib.rulesLabels` provided with the gmailctl standard\nlibrary. With that you can avoid providing the labels referenced by filters.\nThey will be automatically added to the list of labels.\n\nExample:\n\n```jsonnet\nlocal lib = import 'gmailctl.libsonnet';\nlocal rules = [\n  {\n    filter: { to: 'myself@gmail.com' },\n    actions: { labels: ['directed'] },\n  },\n  {\n    filter: { from: 'foobar' },\n    actions: { labels: ['lists/foobar'] },\n  },\n  {\n    filter: { list: 'baz' },\n    actions: { labels: ['lists/baz', 'wow'] },\n  },\n];\n\n// the config\n{\n  version: 'v1alpha3',\n  rules: rules,\n  labels: lib.rulesLabels(rules) + [{ name: l } for l in [\n    'manual-label1',\n    'priority',\n    'priority/p1',\n  ]],\n}\n```\n\nThe resulting list of labels will be:\n\n```jsonnet\nlabels: [\n  // Automatically added\n  { name: 'directed' },\n  { name: 'lists' },            // Implied parent label\n  { name: 'lists/baz' },\n  { name: 'lists/foobar' },\n  { name: 'wow' },\n  // Manually added\n  { name: 'manual-label1' },\n  { name: 'priority' },\n  { name: 'priority/p1' },\n]\n```\n\nNote that there's no need to specify the label `lists`, because even if it's not\nused in any filter, it's the parent of a label that is used.\n\nThings to keep in mind / gotchas:\n\n* Removing the last filter referencing a label will delete the label.\n* The only thing managed by the function is the list of labels names. You need\n  to apply some transformations yourself if you want other properties (e.g. the\n  color).\n* If you have labels that are not referenced by any filters (maybe archive\n  labels, or labels applied manually). You have to remember to specify them\n  manually in the list.\n\nThanks to [legeana](https://github.com/legeana) for the idea!\n\n### Multiple Gmail accounts\n\nIf you need to manage two or more accounts, it's useful to setup bash aliases\nthis way:\n\n```bash\nalias gmailctlu1='gmailctl --config=$HOME/.gmailctlu1'\nalias gmailctlu2='gmailctl --config=$HOME/.gmailctlu2'\n```\n\nYou will then be able to configure both accounts separately by using one or\nthe other alias.\n\n## Known issues\n\n### Apply filters to existing emails\n\ngmailctl doesn't support this functionality for security reasons. The project\ncurrently needs only very basic permissisons, and applying filters to existing\nemails requires full Gmail access. Bugs in gmailctl or in your configuration\nwon't screw up your old emails in any way, so this is an important safety\nfeature. If you really want to do this, you can manually export your rules with\n`gmailctl export \u003e filters.xml`, upload them by using the Gmail Settings UI and\nselect the \"apply new filters to existing email\" checkbox.\n\n### OAuth2 authentication errors\n\nGmail APIs require strict controls, even if you are only accessing your own\ndata. If you're getting errors similar to:\n\n```\noauth2: cannot fetch token: 400 Bad Request\nResponse: {\n  \"error\": \"invalid_grant\",\n  \"error_description\": \"Bad Request\"\n}\n```\n\nit's likely your auth token expired. Try refreshing it with:\n\n```bash\n$ gmailctl init --refresh-expired\n```\n\nand follow the instructions on screen.\n\nIf this doesn't help, retry the authorization workflow from the start:\n\n```bash\n$ gmailctl init --reset\n$ gmailctl init\n```\n\n### YAML config is unsupported\n\ngmailctl recently deprecated older config versions (`v1alpha1`, `v1alpha2`).\nThere's however a migration tool to port those into the latest Jsonnet format.\nTo convert your config:\n\n```bash\n$ go run github.com/mbrt/gmailctl/cmd/gmailctl-config-migrate \\\n    ~/.gmailct/config.yaml \u003e /tmp/gmailctl-config.jsonnet\n```\n\n**Note:** Adjust your paths if you're not keeping your config file in the\ndefault directory.\n\nConfirm that the new config file doesn't have errors, nor shows diffs with your\nremote filters.\n\n```bash\n$ gmailctl diff -f /tmp/gmailctl-config.jsonnet\n```\n\nIf everything looks good, replace the old with the new config:\n\n```bash\n$ mv /tmp/gmailctl-config.jsonnet ~/.gmailctl/config.jsonnet\n$ rm ~/.gmailctl/config.yaml\n```\n\n## Comparison with existing projects\n\n[gmail-britta](https://github.com/antifuchs/gmail-britta) has similar\nmotivations and is quite popular. The difference between that project and\nthis one are:\n\n* `gmail-britta` uses a custom DSL (versus Jsonnet in `gmailctl`)\n* `gmail-britta` is imperative because it allows you to write arbitrary Ruby\n  code in your filters (versus pure declarative for `gmailctl`)\n* `gmail-britta` allows one to write complex chains of filters, but they feel\n  very hardcoded and fails to provide easy ways to write reasonably easy filters\n  \u003csup id=\"a2\"\u003e[2](#f2)\u003c/sup\u003e.\n* `gmail-britta` exports only to the Gmail XML format. You have to import the\n  filters yourself by using the Gmail web interface, manually delete the filters\n  you updated and import only the new ones. This process becomes tedious very\n  quickly and you will resort to quickly avoid using the tool when in a hurry.\n  `gmailctl` provides you this possibility, but also allows you to review your\n  changes and update the filters by using the Gmail APIs, without you having to\n  do anything manually.\n* `gmailctl` tries to workaround certain limitations in Gmail (like applying\n  multiple labels with the same filter) and provide a generic query language to\n  Gmail, `gmail-britta` focuses on writing chain filtering and archiving in very\n  few lines.\n\nIn short `gmailctl` takes the declarative approach to Gmail filters\nconfiguration, hoping it stays simpler to read and maintain, doesn't attempt to\nsimplify complex scenarios with shortcuts (again, hoping the configuration\nbecomes more readable) and provides automatic and fast updates to the filters\nthat will save you time while you are iterating through new versions of your\nfilters.\n\n## Footnotes\n\n\u003cb id=\"f1\"\u003e1\u003c/b\u003e: See [Search operators you can use with\nGmail](https://support.google.com/mail/answer/7190?hl=en) [↩](#a1).\n\n\u003cb id=\"f2\"\u003e2\u003c/b\u003e: Try to write the equivalent of this filter with `gmail-britta` [↩](#a2)\n\n```jsonnet\nlocal spam = {\n  or: [\n    { from: 'pippo@gmail.com' },\n    { from: 'pippo@hotmail.com' },\n    { subject: 'buy this' },\n    { subject: 'buy that' },\n  ],\n};\n{\n  version: 'v1alpha3',\n  rules: [\n    {\n      filter: spam,\n      actions: { delete: true },\n    },\n  ],\n}\n```\n\nIt becomes something like this:\n\n```ruby\n#!/usr/bin/env ruby\n\n# NOTE: This file requires the latest master (30/07/2018) of gmail-britta.\n# The Ruby repos are not up to date\n\nrequire 'rubygems'\nrequire 'gmail-britta'\n\nSPAM_EMAILS = %w{foo@gmail.com bar@hotmail.com}\nSPAM_SUBJECTS = ['\"buy this\"', '\"buy my awesome product\"']\n\nputs(GmailBritta.filterset(:me =\u003e MY_EMAILS) do\n       # Spam\n       filter {\n         has [{:or =\u003e \"from:(#{SPAM_EMAILS.join(\"|\")})\"}]\n         delete_it\n       }\n       filter {\n         has [{:or =\u003e \"subject:(#{SPAM_SUBJECTS.join(\"|\")})\"}]\n         delete_it\n       }\n     end.generate)\n```\n\nNot the most readable configuration I would say. Note: You also have to make\nsure to quote the terms correctly when they contain spaces.\n\nSo what about nesting expressions?\n\n```jsonnet\nlocal me = 'pippo@gmail.com';\nlocal spam = {\n  or: [\n    { from: 'foo@gmail.com' },\n    { from: 'bar@hotmail.com' },\n    { subject: 'buy this' },\n    { subject: 'buy that' },\n  ],\n};\n{\n  version: 'v1alpha3',\n  rules: [\n    {\n      filter: {\n        and: [\n          { to: me },\n          { from: 'friend@mail.com' },\n          { not: spam },\n        ],\n      },\n      actions: { delete: true },\n    },\n  ],\n}\n```\n\nThe reality is that you have to manually build the Gmail expressions yourself.\n\n\u003cb id=\"f3\"\u003e3\u003c/b\u003e: Import variables from  a `.libjsonnet` file [↩](#a3)\n\nFile: `spam.libjsonnet`\n```jsonnet\n{\n  or: [\n    { from: 'foo@gmail.com' },\n    { from: 'bar@hotmail.com' },\n    { subject: 'buy this' },\n    { subject: 'buy that' },\n  ],\n}\n```\n\nFile `config.jsonnet`\n```jsonnet\nlocal spam_filter = import 'spam.libjsonnet';\n{\n  version: 'v1alpha3',\n  rules: [\n    {\n      filter: spam_filter,\n      actions: { delete: true },\n    },\n  ],\n}\n```\n","funding_links":[],"categories":["Go","Go (134)","Repositories"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmbrt%2Fgmailctl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmbrt%2Fgmailctl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmbrt%2Fgmailctl/lists"}